Merge lp:~kissiel/checkbox/sa-in-cbt into lp:checkbox

Proposed by Maciej Kisielewski
Status: Merged
Approved by: Zygmunt Krynicki
Approved revision: 3997
Merged at revision: 3970
Proposed branch: lp:~kissiel/checkbox/sa-in-cbt
Merge into: lp:checkbox
Diff against target: 958 lines (+248/-396)
5 files modified
checkbox-touch/checkbox-touch.qml (+39/-32)
checkbox-touch/components/CheckboxTouchApplication.qml (+4/-4)
checkbox-touch/py/checkbox_touch.py (+135/-327)
checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py (+33/-30)
plainbox/plainbox/impl/session/assistant.py (+37/-3)
To merge this branch: bzr merge lp:~kissiel/checkbox/sa-in-cbt
Reviewer Review Type Date Requested Status
Zygmunt Krynicki (community) Approve
Review via email: mp+269634@code.launchpad.net

Description of the change

This MR brings a small revolution to Checkbox {Touch,Converged}.

It replaces all the internal logic related to running plainbox core with calls to SessionAssistant.
It also smuggles some fixes to SessionAssistant along the way.
Then, it makes Autopilot happy with the New Order,
to finally clean up pep8 issues, embetter existing and add missing docstrings.

a6b1280 checkbox-touch: run run_test_activity for ALL plugins
e30f8f1 checkbox-touch: [move2SA] use SA in __init__
7de6bda checkbox-touch: [move2SA] use SA in start_session
861c36a checkbox-touch: [move2SA] use SA in is_session_resumable
bedf971 checkbox-touch: [move2SA] use SA in get_testplans
7e1e3a2 checkbox-touch: [move2SA] use SA in remember_testplan
037f79a checkbox-touch: [move2SA] use SA in get_categories
a853d86 checkbox-touch: [move2SA] use SA in remember_categories
9e08a76 checkbox-touch: [move2SA] use SA in get_available tests
2fe8eeb checkbox-touch: [move2SA] use SA in get_rerun_candidates
2fcc534 checkbox-touch: [move2SA] use SA in remember_tests
3a6c5cb checkbox-touch: [move2SA] use SA in get_next_test
d8fc496 checkbox-touch: [move2SA] use SA in register_test_result
2fd2af9 checkbox-touch: [move2SA] use SA in run_test_activity
952d973 checkbox-touch: [move2SA] use SA in get_results
225b491 checkbox-touch: [move2SA] use SA in export_results
f66a3b6 checkbox-touch: add timestamp to app_blob
dfce594 checkbox-touch: [move2SA] remove _checkpoint method
d3f7f14 checkbox-touch: add a method for loading embedded providers
85e9416 checkbox-touch:autopilot: rearrange order of full-scenario to match SA sorting
e507f79 plainbox:session:assistant: correct UsageExpectations after session is started
a0e3297 plainbox:session:assistant: allow export_to_file call in 'normal' state
37153b4 checkbox-touch: move embedded providers stuff to CBTApp.__init__
41c813e plainbox:session:assistant: require select_test_plan after resume
73adaa6 plainbox:session:assistant: add finalize_session method
db9146c checkbox-touch:[move2SA] use SA in clear_session
d24664e checkbox-touch: remove unnecessary methods
e45942c checkbox-touch: remove import of no longer used modules
dc61ce2 checkbox-touch: fix PEP8 issues
70148a8 checkbox-touch: upgrade docstrings

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

let there be light

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'checkbox-touch/checkbox-touch.qml'
--- checkbox-touch/checkbox-touch.qml 2015-08-18 12:59:01 +0000
+++ checkbox-touch/checkbox-touch.qml 2015-08-31 12:00:21 +0000
@@ -155,7 +155,7 @@
155 "checkbox_touch" : applicationVersion,155 "checkbox_touch" : applicationVersion,
156 "plainbox" : plainboxVersion156 "plainbox" : plainboxVersion
157 };157 };
158 resumeOrStartSession(appSettings["providersDir"]);158 resumeOrStartSession();
159 }159 }
160 onSessionReady: {160 onSessionReady: {
161 welcomePage.enableButton()161 welcomePage.enableButton()
@@ -163,7 +163,8 @@
163 Component.onCompleted: {163 Component.onCompleted: {
164 // register to py.initiated signal164 // register to py.initiated signal
165 py.onInitiated.connect(function() {165 py.onInitiated.connect(function() {
166 construct("checkbox_touch.create_app_object", []);166 construct("checkbox_touch.create_app_object", [
167 appSettings["providersDir"]]);
167 });168 });
168 }169 }
169 }170 }
@@ -267,14 +268,14 @@
267268
268 ResumeSessionPage {269 ResumeSessionPage {
269 id: resumeSessionPage270 id: resumeSessionPage
270 onRerunLast: app.resumeSession(true, appSettings["providersDir"], processNextTest)271 onRerunLast: app.resumeSession(true, processNextTest)
271 onContinueSession: app.resumeSession(false, appSettings["providersDir"], processNextTest)272 onContinueSession: app.resumeSession(false, processNextTest)
272 resumeText: i18n.tr("Checkbox did not finish completely.\nDo you want \273 resumeText: i18n.tr("Checkbox did not finish completely.\nDo you want \
273 to rerun last test, continue to the next test, or restart from the beginning?")274 to rerun last test, continue to the next test, or restart from the beginning?")
274 onRestartSession: {275 onRestartSession: {
275 pageStack.clear();276 pageStack.clear();
276 pageStack.push(welcomePage);277 pageStack.push(welcomePage);
277 app.startSession(appSettings["providersDir"]);278 app.startSession();
278 }279 }
279 }280 }
280281
@@ -407,10 +408,10 @@
407 } else {408 } else {
408 if (result.errors_encountered) {409 if (result.errors_encountered) {
409 ErrorLogic.showError(mainView, i18n.tr("Could not resume session."),410 ErrorLogic.showError(mainView, i18n.tr("Could not resume session."),
410 app.startSession(appSettings["providersDir"]),411 app.startSession(),
411 i18n.tr("Start new session"));412 i18n.tr("Start new session"));
412 } else {413 } else {
413 app.startSession(appSettings["providersDir"]);414 app.startSession();
414 }415 }
415 }416 }
416 });417 });
@@ -502,7 +503,7 @@
502 resultsPage.endTesting.connect(function() {503 resultsPage.endTesting.connect(function() {
503 pageStack.clear();504 pageStack.clear();
504 app.clearSession(function() {505 app.clearSession(function() {
505 app.startSession(appSettings["providersDir"]);506 app.startSession();
506 pageStack.push(welcomePage);507 pageStack.push(welcomePage);
507 });508 });
508 });509 });
@@ -535,13 +536,15 @@
535 }536 }
536537
537 function performManualTest(test) {538 function performManualTest(test) {
538 var manualIntroPage = Qt.createComponent(Qt.resolvedUrl("components/ManualIntroPage.qml")).createObject();539 runTestActivity(test, function(test) {
539 manualIntroPage.test = test540 var manualIntroPage = Qt.createComponent(Qt.resolvedUrl("components/ManualIntroPage.qml")).createObject();
540 manualIntroPage.testDone.connect(completeTest);541 manualIntroPage.test = test
541 manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); });542 manualIntroPage.testDone.connect(completeTest);
542 manualIntroPage.__customHeaderContents = progressHeader;543 manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); });
543 progressHeader.update(test);544 manualIntroPage.__customHeaderContents = progressHeader;
544 pageStack.push(manualIntroPage);545 progressHeader.update(test);
546 pageStack.push(manualIntroPage);
547 });
545 }548 }
546549
547 function performUserInteractVerifyTest(test) {550 function performUserInteractVerifyTest(test) {
@@ -592,25 +595,29 @@
592 }595 }
593596
594 function performQmlTest(test) {597 function performQmlTest(test) {
595 var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlNativePage.qml"))598 runTestActivity(test, function(test) {
596 console.log(comp.errorString());599 var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlNativePage.qml"))
597 var qmlNativePage = comp.createObject();600 console.log(comp.errorString());
598 qmlNativePage.test = test601 var qmlNativePage = comp.createObject();
599 qmlNativePage.testDone.connect(completeTest);602 qmlNativePage.test = test
600 qmlNativePage.__customHeaderContents = progressHeader;603 qmlNativePage.testDone.connect(completeTest);
601 progressHeader.update(test);604 qmlNativePage.__customHeaderContents = progressHeader;
602 pageStack.push(qmlNativePage);605 progressHeader.update(test);
606 pageStack.push(qmlNativePage);
607 });
603 }608 }
604 function performConfinedQmlTest(test) {609 function performConfinedQmlTest(test) {
605 var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlConfinedPage.qml"))610 runTestActivity(test, function(test) {
606 console.log(comp.errorString());611 var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlConfinedPage.qml"))
607 var qmlNativePage = comp.createObject();612 console.log(comp.errorString());
608 qmlNativePage.test = test613 var qmlNativePage = comp.createObject();
609 qmlNativePage.applicationVersion = app.applicationVersion;614 qmlNativePage.test = test
610 qmlNativePage.testDone.connect(completeTest);615 qmlNativePage.applicationVersion = app.applicationVersion;
611 qmlNativePage.__customHeaderContents = progressHeader;616 qmlNativePage.testDone.connect(completeTest);
612 progressHeader.update(test);617 qmlNativePage.__customHeaderContents = progressHeader;
613 pageStack.push(qmlNativePage);618 progressHeader.update(test);
619 pageStack.push(qmlNativePage);
620 });
614 }621 }
615622
616 function showVerificationScreen(test) {623 function showVerificationScreen(test) {
617624
=== modified file 'checkbox-touch/components/CheckboxTouchApplication.qml'
--- checkbox-touch/components/CheckboxTouchApplication.qml 2015-07-29 20:50:05 +0000
+++ checkbox-touch/components/CheckboxTouchApplication.qml 2015-08-31 12:00:21 +0000
@@ -45,8 +45,8 @@
45 // Starts session in plainbox and runs all necessary setup actions.45 // Starts session in plainbox and runs all necessary setup actions.
46 // Calling this function will signal sessionReady() once it's finished46 // Calling this function will signal sessionReady() once it's finished
47 // doing setup.47 // doing setup.
48 function startSession(providersDir) {48 function startSession() {
49 request("start_session", [providersDir], function(result) {49 request("start_session", [], function(result) {
50 sessionDir = result['session_dir'];50 sessionDir = result['session_dir'];
51 sessionReady();51 sessionReady();
52 }, function(error) {52 }, function(error) {
@@ -57,8 +57,8 @@
57 i18n.tr("Quit"));57 i18n.tr("Quit"));
58 });58 });
59 }59 }
60 function resumeSession(rerunLastTest, providersDir, continuation) {60 function resumeSession(rerunLastTest, continuation) {
61 request("resume_session", [rerunLastTest, providersDir], function(result) {61 request("resume_session", [rerunLastTest], function(result) {
62 if (!result["session_id"]) {62 if (!result["session_id"]) {
63 pageStack.pop();63 pageStack.pop();
64 ErrorLogic.showError(mainView,64 ErrorLogic.showError(mainView,
6565
=== modified file 'checkbox-touch/py/checkbox_touch.py'
--- checkbox-touch/py/checkbox_touch.py 2015-07-31 13:07:18 +0000
+++ checkbox-touch/py/checkbox_touch.py 2015-08-31 12:00:21 +0000
@@ -32,7 +32,6 @@
32import abc32import abc
33import collections33import collections
34import datetime34import datetime
35import itertools
36import json35import json
37import logging36import logging
38import os37import os
@@ -43,23 +42,10 @@
4342
44from plainbox.abc import IJobResult43from plainbox.abc import IJobResult
45from plainbox.impl import pod44from plainbox.impl import pod
46from plainbox.impl.applogic import PlainBoxConfig
47from plainbox.impl.clitools import ToolBase45from plainbox.impl.clitools import ToolBase
48from plainbox.impl.commands.inv_run import SilentUI46from plainbox.impl.commands.inv_run import SilentUI
49from plainbox.impl.result import JobResultBuilder47from plainbox.impl.result import JobResultBuilder
50from plainbox.impl.runner import JobRunner48from plainbox.impl.session.assistant import SessionAssistant
51from plainbox.impl.secure.origin import Origin
52from plainbox.impl.secure.qualifiers import FieldQualifier
53from plainbox.impl.secure.qualifiers import OperatorMatcher
54from plainbox.impl.secure.qualifiers import select_jobs
55from plainbox.impl.session import SessionManager
56from plainbox.impl.session import SessionMetaData
57from plainbox.impl.session import SessionPeekHelper
58from plainbox.impl.session import SessionResumeError
59from plainbox.impl.session.storage import SessionStorageRepository
60from plainbox.impl.unit.job import JobDefinition
61from plainbox.impl.unit.validators import compute_value_map
62from plainbox.public import get_providers
63import plainbox49import plainbox
6450
65from embedded_providers import EmbeddedProvider1PlugInCollection51from embedded_providers import EmbeddedProvider1PlugInCollection
@@ -149,25 +135,22 @@
149135
150 __version__ = (1, 2, 1, 'final', 0)136 __version__ = (1, 2, 1, 'final', 0)
151137
152 def __init__(self):138 def __init__(self, providers_dir):
153 if plainbox.__version__ < (0, 22):139 if plainbox.__version__ < (0, 22):
154 raise SystemExit("plainbox 0.22 required, you have {}".format(140 raise SystemExit("plainbox 0.22 required, you have {}".format(
155 ToolBase.format_version_tuple(plainbox.__version__)))141 ToolBase.format_version_tuple(plainbox.__version__)))
156 # adjust_logging(logging.INFO, ['checkbox.touch'], True)142 self.assistant = SessionAssistant('checkbox-converged')
157 self.manager = None143 self.ui = CheckboxTouchUI()
158 self.context = None144 self.index = 0
159 self.runner = None
160 self.index = 0 # NOTE: next test index
161 # NOTE: This may also have "" representing None
162 self.desired_category_ids = frozenset()
163 self.desired_test_ids = frozenset()
164 self.test_plan_id = ""
165 self.resume_candidate_storage = None
166 self.session_storage_repo = None
167 self.timestamp = datetime.datetime.utcnow().isoformat()
168 self.config = PlainBoxConfig()
169 self._password = None145 self._password = None
170 self.ui = CheckboxTouchUI()146 self._timestamp = None
147 self._latest_session = None
148 self.resume_candidate_storage = None
149 self.assistant.use_alternate_repository(
150 self._get_app_cache_directory())
151 self.assistant.select_providers(
152 '*',
153 additional_providers=self._get_embedded_providers(providers_dir))
171154
172 def __repr__(self):155 def __repr__(self):
173 return "app"156 return "app"
@@ -182,186 +165,101 @@
182 }165 }
183166
184 @view167 @view
185 def start_session(self, providers_dir):168 def start_session(self):
186 if self.manager is not None:169 """Start a new session."""
187 _logger.warning("start_session() should not be called twice!")170 self.assistant.start_new_session('Checkbox Converged session')
188 else:171 self._timestamp = datetime.datetime.utcnow().isoformat()
189 self._init_session_storage_repo()
190 self.manager = SessionManager.create(self.session_storage_repo)
191 self.manager.add_local_device_context()
192 self.context = self.manager.default_device_context
193 # Add some all providers into the context
194 for provider in self._get_default_providers(providers_dir):
195 self.context.add_provider(provider)
196 # Fill in the meta-data
197 self.context.state.metadata.app_id = 'checkbox-touch'
198 self.context.state.metadata.title = 'Checkbox Touch Session'
199 self.context.state.metadata.flags.add('bootstrapping')
200 # Checkpoint the session so that we have something to see
201 self._checkpoint()
202
203 # Prepare custom execution controller list
204 from plainbox.impl.ctrl import UserJobExecutionController
205 from sudo_with_pass_ctrl import \
206 RootViaSudoWithPassExecutionController
207 controllers = [
208 RootViaSudoWithPassExecutionController(
209 self.context.provider_list, self._password_provider),
210 UserJobExecutionController(self.context.provider_list),
211 ]
212 self.runner = JobRunner(
213 self.manager.storage.location,
214 self.context.provider_list,
215 # TODO: tie this with well-known-dirs helper
216 os.path.join(self.manager.storage.location, 'io-logs'),
217 execution_ctrl_list=controllers)
218 app_cache_dir = self._get_app_cache_directory()
219 if not os.path.exists(app_cache_dir):
220 os.makedirs(app_cache_dir)
221 with open(os.path.join(app_cache_dir, 'session_id'),
222 'w') as f:
223 f.write(self.manager.storage.id)
224 return {172 return {
225 'session_id': self.manager.storage.id,173 'session_id': self.assistant.get_session_id(),
226 'session_dir': self.manager.storage.location174 'session_dir': self.assistant.get_session_dir()
227 }175 }
228176
229 @view177 @view
230 def resume_session(self, rerun_last_test, providers_dir):178 def resume_session(self, rerun_last_test):
231 all_units = list(itertools.chain(179 """
232 *[p.unit_list for p in self._get_default_providers(180 Resume latest sesssion.
233 providers_dir)]))181
234 try:182 :param rerun_last_test:
235 self.manager = SessionManager.load_session(183 A bool stating whether runtime should repeat the test, that the app
236 all_units, self.resume_candidate_storage)184 was executing when it was interrupted.
237 except IOError as exc:185 """
238 _logger.info("Exception raised when trying to resume"186 metadata = self.assistant.resume_session(self._latest_session)
239 "session: %s", str(exc))
240 return {
241 'session_id': None
242 }
243 self.context = self.manager.default_device_context
244 metadata = self.context.state.metadata
245 app_blob = json.loads(metadata.app_blob.decode("UTF-8"))187 app_blob = json.loads(metadata.app_blob.decode("UTF-8"))
246 self.runner = JobRunner(
247 self.manager.storage.location,
248 self.context.provider_list,
249 os.path.join(self.manager.storage.location, 'io-logs'))
250 self.index = app_blob['index_in_run_list']188 self.index = app_blob['index_in_run_list']
251 self._init_test_plan_id(app_blob['test_plan_id'])189 self.test_plan_id = app_blob['test_plan_id']
252 _logger.error(self.context.state.run_list)190 self.assistant.select_test_plan(self.test_plan_id)
253 _logger.error(self.index)191 self.assistant.bootstrap()
192
254 if not rerun_last_test:193 if not rerun_last_test:
255 # Skip current test194 # Skip current test
256 test = self.get_next_test()['result']195 test = self.get_next_test()['result']
257 test['outcome'] = 'skip'196 test['outcome'] = 'skip'
258 self.register_test_result(test)197 self.register_test_result(test)
259 return {198 return {
260 'session_id': self.manager.storage.id199 'session_id': self._latest_session
261 }200 }
262201
263 @view202 @view
264 def clear_session(self):203 def clear_session(self):
265 self.manager = None204 """Reset app-custom state info about the session."""
266 self.context = None
267 self.runner = None
268 self.index = 0205 self.index = 0
269 self.timestamp = datetime.datetime.utcnow().isoformat()206 self._timestamp = datetime.datetime.utcnow().isoformat()
270 self.desired_category_ids = frozenset()
271 self.desired_test_ids = frozenset()
272 self.resume_candidate_storage = None
273 self.session_storage_repo = None
274 os.unlink(os.path.join(self._get_app_cache_directory(), 'session_id'))
275207
276 @view208 @view
277 def is_session_resumable(self):209 def is_session_resumable(self):
278 """210 """Check whether there is a session that can be resumed."""
279 Checks whether given session is resumable211 for session_id, session_md in self.assistant.get_resumable_sessions():
280 """212 # we're interested in the latest session only, this is why we
281 resumable = False213 # return early
282 try:214 self._latest_session = session_id
283 with open(os.path.join(self._get_app_cache_directory(),215 return {
284 'session_id')) as f:216 'resumable': True,
285 session_id = f.readline().rstrip('\n')217 'error_encountered': False,
286 except (OSError, IOError):218 }
287 session_id = None219 else:
288 self._init_session_storage_repo()220 return {
289 for storage in self.session_storage_repo.get_storage_list():221 'resumable': False,
290 data = storage.load_checkpoint()222 'error_encountered': False,
291 if len(data) == 0:223 }
292 continue
293 try:
294 metadata = SessionPeekHelper().peek(data)
295 if (metadata.app_id == 'checkbox-touch'
296 and storage.id == session_id
297 and SessionMetaData.FLAG_INCOMPLETE in
298 metadata.flags):
299 self.resume_candidate_storage = storage
300 resumable = True
301 except SessionResumeError as exc:
302 _logger.info("Exception raised when trying to resume"
303 "session: %s", str(exc))
304 return {
305 'resumable': False,
306 'errors_encountered': True
307 }
308 return {
309 'resumable': resumable,
310 'errors_encountered': False
311 }
312224
313 @view225 @view
314 def get_testplans(self):226 def get_testplans(self):
315 all_units = self.manager.default_device_context.unit_list227 """Get the list of available test plans."""
228 test_plan_units = [self.assistant.get_test_plan(tp_id) for tp_id in
229 self.assistant.get_test_plans()]
316 return {230 return {
317 'testplan_info_list': [{231 'testplan_info_list': [{
318 "mod_id": unit.id,232 "mod_id": tp.id,
319 "mod_name": unit.name,233 "mod_name": tp.name,
320 "mod_selected": False,234 "mod_selected": False,
321 } for unit in all_units if unit.Meta.name == 'test plan']235 } for tp in test_plan_units]
322 }236 }
323237
324 @view238 @view
325 def remember_testplan(self, test_plan_id):239 def remember_testplan(self, test_plan_id):
326 self.context.invalidate_shared('potential_job_list')240 """Pick the test plan as the one in force."""
327 self.context.invalidate_shared('potential_category_map')241 self.test_plan_id = test_plan_id
328 self._init_test_plan_id(test_plan_id)242 self.assistant.select_test_plan(test_plan_id)
243 self.assistant.bootstrap()
329244
330 @view245 @view
331 def get_categories(self):246 def get_categories(self):
332 """247 """Get categories selection data."""
333 Get categories selection data.
334 """
335 potential_job_list = self.context.compute_shared(
336 'potential_job_list', select_jobs,
337 self.context.state.job_list, [self.test_plan.get_qualifier()])
338 potential_category_map = self.context.compute_shared(
339 'potential_category_map',
340 self.test_plan.get_effective_category_map, potential_job_list)
341 id_map = self.context.compute_shared(
342 'id_map', compute_value_map, self.context, 'id')
343 category_info_list = [{248 category_info_list = [{
344 "mod_id": category.id,249 "mod_id": category.id,
345 "mod_name": category.name,250 "mod_name": category.name,
346 "mod_selected": True,251 "mod_selected": True,
347 } for category in (252 } for category in (
348 id_map[category_id][0]253 self.assistant.get_category(category_id)
349 for category_id in set(potential_category_map.values())254 for category_id in self.assistant.get_participating_categories()
350 )]255 )]
351 category_info_list.sort(key=lambda ci: ci['mod_name'])256 return {'category_info_list': category_info_list}
352 return {
353 'category_info_list': category_info_list
354 }
355257
356 @view258 @view
357 def remember_categories(self, selected_id_list):259 def remember_categories(self, selected_id_list):
358 """260 """Save category selection."""
359 Save category selection261 _logger.info("Selected categories: %s", selected_id_list)
360 """262 self.assistant.filter_jobs_by_categories(selected_id_list)
361 self.desired_category_ids = frozenset(selected_id_list)
362 self.context.invalidate_shared('subset_job_list')
363 self.context.invalidate_shared('effective_category_map')
364 _logger.info("Selected categories: %s", self.desired_category_ids)
365263
366 @view264 @view
367 def get_available_tests(self):265 def get_available_tests(self):
@@ -371,89 +269,65 @@
371 The response object will contain only tests with category matching269 The response object will contain only tests with category matching
372 previously set list. Tests are sorted by (category, name)270 previously set list. Tests are sorted by (category, name)
373 """271 """
374 subset_job_list = self.context.compute_shared(272 category_names = {
375 'subset_job_list', select_jobs,273 cat_id: self.assistant.get_category(cat_id).tr_name() for
376 self.context.state.job_list, [274 cat_id in self.assistant.get_participating_categories()}
377 # Select everything the test plan selected275 job_units = [self.assistant.get_job(job_id) for job_id in
378 self.test_plan.get_qualifier(),276 self.assistant.get_static_todo_list()]
379 # Except jobs not matching the selected group of categories
380 FieldQualifier(
381 JobDefinition.Meta.fields.category_id,
382 OperatorMatcher(not_contains, self.desired_category_ids),
383 Origin.get_caller_origin(), inclusive=False),
384 ])
385 effective_category_map = self.context.compute_shared(
386 'effective_category_map',
387 self.test_plan.get_effective_category_map, subset_job_list)
388 for job_id, effective_category_id in effective_category_map.items():
389 job_state = self.context.state.job_state_map[job_id]
390 job_state.effective_category_id = effective_category_id
391 id_map = self.context.compute_shared(
392 'id_map', compute_value_map, self.context, 'id')
393 test_info_list = [{277 test_info_list = [{
394 "mod_id": job_id,278 "mod_id": job.id,
395 "mod_name": id_map[job_id][0].tr_summary(),279 "mod_name": job.tr_summary(),
396 "mod_group": id_map[category_id][0].tr_name(),280 "mod_group": category_names[job.category_id],
397 "mod_selected": True,281 "mod_selected": True,
398 } for job_id, category_id in effective_category_map.items()]282 } for job in job_units]
399 test_info_list.sort(key=lambda ti: (ti['mod_group'], ti['mod_name']))283 test_info_list.sort(key=lambda ti: (ti['mod_group'], ti['mod_name']))
400 return {284 return {'test_info_list': test_info_list}
401 'test_info_list': test_info_list285
402 }
403 @view286 @view
404 def get_rerun_candidates(self):287 def get_rerun_candidates(self):
288 """Get all the tests that might be selected for rerunning."""
405 def rerun_predicate(job_state):289 def rerun_predicate(job_state):
406 return job_state.result.outcome in (290 return job_state.result.outcome in (
407 IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH)291 IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH)
408 id_map = self.context.compute_shared(
409 'id_map', compute_value_map, self.context, 'id')
410 rerun_candidates = []292 rerun_candidates = []
411 for job in self.manager.state.run_list:293 todo_list = self.assistant.get_static_todo_list()
412 if rerun_predicate(self.manager.state.job_state_map[job.id]):294 job_units = {job_id: self.assistant.get_job(job_id) for job_id
295 in todo_list}
296 job_states = {job_id: self.assistant.get_job_state(job_id) for job_id
297 in todo_list}
298 category_names = {
299 cat_id: self.assistant.get_category(cat_id).tr_name() for cat_id
300 in self.assistant.get_participating_categories()}
301 for job_id, job_state in job_states.items():
302 if rerun_predicate(job_state):
413 rerun_candidates.append({303 rerun_candidates.append({
414 "mod_id": job.id,304 "mod_id": job_id,
415 "mod_name": job.tr_summary(),305 "mod_name": job_units[job_id].tr_summary(),
416 "mod_group": id_map[job.category_id][0].tr_name(),306 "mod_group": category_names[job_units[job_id].category_id],
417 "mod_selected": False307 "mod_selected": False
418
419 })308 })
420 return rerun_candidates309 return rerun_candidates
421310
422 @view311 @view
423 def remember_tests(self, selected_id_list):312 def remember_tests(self, selected_id_list):
424 """313 """Save test selection."""
425 Save test selection
426 """
427 self.desired_test_ids = frozenset(selected_id_list)
428 _logger.info("Selected tests: %s", self.desired_test_ids)
429 self.index = 0314 self.index = 0
430 self.context.invalidate_shared('desired_job_list')315 self.assistant.use_alternate_selection(selected_id_list)
431 desired_job_list = self.context.compute_shared(316 self.assistant.update_app_blob(self._get_app_blob())
432 'desired_job_list', select_jobs,317 _logger.info("Selected tests: %s", selected_id_list)
433 self.context.state.job_list, [318 return
434 # Select everything the test plan selected
435 self.test_plan.get_qualifier(),
436 # Except all the jobs that weren't marked by the user
437 FieldQualifier(
438 JobDefinition.Meta.fields.id,
439 OperatorMatcher(not_contains, self.desired_test_ids),
440 Origin.get_caller_origin(), inclusive=False)])
441 _logger.info("Desired job list: %s", desired_job_list)
442 self.context.state.update_desired_job_list(desired_job_list)
443 _logger.info("Run job list: %s", self.context.state.run_list)
444 self.context.state.metadata.flags.add('incomplete')
445 self._checkpoint()
446319
447 @view320 @view
448 def get_next_test(self):321 def get_next_test(self):
449 """322 """
450 Get next text that is scheduled to run323 Get next text that is scheduled to run.
451 Returns test object or None if all tests are completed324
325 :returns:
326 Dictionary resembling JobDefinition or None if all tests are completed
452 """327 """
453 if self.index < len(self.context.state.run_list):328 todo_list = self.assistant.get_static_todo_list()
454 job = self.context.state.run_list[self.index]329 if self.index < len(todo_list):
455 job_state = self.context.state.job_state_map[job.id]330 job = self.assistant.get_job(todo_list[self.index])
456 # support for description field splitted into 3 subfields
457 description = ""331 description = ""
458 if job.tr_purpose() is not None:332 if job.tr_purpose() is not None:
459 description = job.tr_purpose() + "\n"333 description = job.tr_purpose() + "\n"
@@ -472,97 +346,65 @@
472 "user": job.user,346 "user": job.user,
473 "qml_file": job.qml_file,347 "qml_file": job.qml_file,
474 "start_time": time.time(),348 "start_time": time.time(),
475 "test_number": self.index,349 "test_number": todo_list.index(job.id),
476 "tests_count": len(self.context.state.run_list),350 "tests_count": len(todo_list),
477 "command": job.command,351 "command": job.command,
478 "flags": job.get_flag_set()352 "flags": job.get_flag_set()
479 }353 }
480 if not job_state.can_start():
481 test["outcome"] = "skip"
482 test["comments"] = job_state.get_readiness_description()
483 self.register_test_result(test)
484 return self.get_next_test()["result"]
485 return test354 return test
486 else:355 else:
487 return {}356 return {}
488357
489 @view358 @view
490 def register_test_result(self, test):359 def register_test_result(self, test):
491 """360 """Registers outcome of a test."""
492 Registers outcome of a test
493 """
494 _logger.info("Storing test result: %s", test)361 _logger.info("Storing test result: %s", test)
495 job_id = test['id']362 job_id = test['id']
496 job = self.context.state.job_state_map[job_id].job
497 builder_kwargs = {363 builder_kwargs = {
498 'outcome': test['outcome'],364 'outcome': test['outcome'],
499 'comments': test.get('comments', pod.UNSET)365 'comments': test.get('comments', pod.UNSET),
366 'execution_duration': time.time() - test['start_time']
500 }367 }
501 # some result may already have been saved if the job had some activity
502 # to run, i.e. result object is available in the test object
503 try:368 try:
369 # if we're registering skipped test as an outcome of resuming
370 # session, the result field of the test object will be missing
504 builder_kwargs['io_log_filename'] = test['result'].io_log_filename371 builder_kwargs['io_log_filename'] = test['result'].io_log_filename
505 except KeyError:372 except KeyError:
506 builder_kwargs['execution_duration'] = (373 pass
507 time.time() - test['start_time'])374
508 result = JobResultBuilder(**builder_kwargs).get_result()375 result = JobResultBuilder(**builder_kwargs).get_result()
509 self.context.state.update_job_result(job, result)376 self.assistant.use_job_result(job_id, result)
510 self.index += 1377 self.index += 1
511 self._checkpoint()378 self.assistant.update_app_blob(self._get_app_blob())
512379
513 @view380 @view
514 def run_test_activity(self, test):381 def run_test_activity(self, test):
515 """382 """Run command associated with given test."""
516 Run command associated with given test383 plugins_handled_natively = ['qml']
517 """384 res_builder = self.assistant.run_job(
518 job_id = test['id']385 test['id'], self.ui, test['plugin'] in plugins_handled_natively)
519 job_state = self.context.state.job_state_map[job_id]386 test['outcome'] = res_builder.outcome
520 job = job_state.job387 test['result'] = res_builder
521 self.context.state.running_job_name = job_id
522 self._checkpoint()
523 try:
524 result = self.runner.run_job(job, job_state, self.config, self.ui)
525 except OSError as exc:
526 result = JobResultBuilder(
527 outcome='fail',
528 comment=str(exc),
529 ).get_result()
530 self.context.state.running_job_name = None
531 self._checkpoint()
532 test['outcome'] = result.outcome
533 test['result'] = result
534 return test388 return test
535389
536 @view390 @view
537 def get_results(self):391 def get_results(self):
538 """392 """Get results object."""
539 Get results object393 self.assistant.finalize_session()
540 """394 stats = self.assistant.get_summary()
541 self.context.state.metadata.flags.remove('incomplete')
542 self._checkpoint()
543 stats = collections.defaultdict(int)
544 for job_state in self.context.state.job_state_map.values():
545 stats[job_state.result.outcome] += 1
546 return {395 return {
547 'totalPassed': stats[IJobResult.OUTCOME_PASS],396 'totalPassed': stats[IJobResult.OUTCOME_PASS],
548 'totalFailed': stats[IJobResult.OUTCOME_FAIL],397 'totalFailed': stats[IJobResult.OUTCOME_FAIL],
549 'totalSkipped': stats[IJobResult.OUTCOME_SKIP],398 'totalSkipped': stats[IJobResult.OUTCOME_SKIP] +
399 stats[IJobResult.OUTCOME_NOT_SUPPORTED]
550 }400 }
551401
552 @view402 @view
553 def export_results(self, output_format, option_list):403 def export_results(self, output_format, option_list):
554 """404 """Export results to file(s) in the user's 'Documents' directory.."""
555 Export results to file
556 """
557 # Export results in the user's Documents directory
558 dirname = self._get_user_directory_documents()405 dirname = self._get_user_directory_documents()
559 exporter = self.manager.create_exporter(output_format, option_list)406 return self.assistant.export_to_file(
560 extension = exporter.unit.file_extension407 output_format, option_list, dirname)
561 filename = ''.join(['submission_', self.timestamp, '.', extension])
562 output_file = os.path.join(dirname, filename)
563 with open(output_file, 'wb') as stream:
564 exporter.dump_from_session_manager(self.manager, stream)
565 return output_file
566408
567 def _get_user_directory_documents(self):409 def _get_user_directory_documents(self):
568 xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \410 xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \
@@ -601,11 +443,6 @@
601 raise IOError("{} exists and is not a directory".format(path))443 raise IOError("{} exists and is not a directory".format(path))
602 return path444 return path
603445
604
605 def _checkpoint(self):
606 self.context.state.metadata.app_blob = self._get_app_blob()
607 self.manager.checkpoint()
608
609 def _get_app_blob(self):446 def _get_app_blob(self):
610 """447 """
611 Get json dump of with app-specific blob448 Get json dump of with app-specific blob
@@ -615,51 +452,22 @@
615 'version': 1,452 'version': 1,
616 'test_plan_id': self.test_plan_id,453 'test_plan_id': self.test_plan_id,
617 'index_in_run_list': self.index,454 'index_in_run_list': self.index,
455 'session_timestamp': self._timestamp,
618 }).encode("UTF-8")456 }).encode("UTF-8")
619457
620 def _init_test_plan_id(self, test_plan_id):458 def _get_embedded_providers(self, providers_dir):
621 """459 """
622 Validates and stores test_plan_id460 Get providers included with the app
623 """
624 if not isinstance(test_plan_id, str):
625 raise TypeError("test_plan_id must be a string")
626 # Look up the test plan with the specified identifier
627 id_map = self.context.compute_shared(
628 'id_map', compute_value_map, self.context, 'id')
629 try:
630 test_plan = id_map[test_plan_id][0]
631 except KeyError:
632 raise ValueError(
633 "cannot find any unit with id: {!r}".format(test_plan_id))
634 if test_plan.Meta.name != 'test plan':
635 raise ValueError(
636 "unit {!r} is not a test plan".format(test_plan_id))
637 self.test_plan_id = test_plan_id
638 self.test_plan = test_plan
639
640 def _init_session_storage_repo(self):
641 """
642 Init storage repository.
643 """
644 self.session_storage_repo = SessionStorageRepository(
645 self._get_app_cache_directory())
646
647 def _get_default_providers(self, providers_dir):
648 """
649 Get providers
650461
651 :param providers_dir:462 :param providers_dir:
652 Path within application tree from which to load providers463 Path within application tree from which to load providers
653 :returns:464 :returns:
654 list of loaded providers465 list of loaded providers
655 """466 """
656 provider_list = get_providers()467 provider_list = []
657 # when running on ubuntu-touch device, APP_DIR env var is present
658 # and points to touch application top directory
659 app_root_dir = os.path.normpath(os.getenv(468 app_root_dir = os.path.normpath(os.getenv(
660 'APP_DIR', os.path.join(os.path.dirname(__file__), '..')))469 'APP_DIR', os.path.join(os.path.dirname(__file__), '..')))
661 path = os.path.join(app_root_dir,470 path = os.path.join(app_root_dir, os.path.normpath(providers_dir))
662 os.path.normpath(providers_dir))
663 _logger.info("Loading all providers from %s", path)471 _logger.info("Loading all providers from %s", path)
664 if os.path.exists(path):472 if os.path.exists(path):
665 embedded_providers = EmbeddedProvider1PlugInCollection(path)473 embedded_providers = EmbeddedProvider1PlugInCollection(path)
@@ -683,7 +491,7 @@
683491
684492
685def get_qml_logger(default_level):493def get_qml_logger(default_level):
686 logging_level = collections.defaultdict(lambda:logging.INFO, {494 logging_level = collections.defaultdict(lambda: logging.INFO, {
687 "debug": logging.DEBUG,495 "debug": logging.DEBUG,
688 "warning": logging.WARNING,496 "warning": logging.WARNING,
689 "warn": logging.WARN,497 "warn": logging.WARN,
690498
=== modified file 'checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py'
--- checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-07-23 08:25:48 +0000
+++ checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-08-31 12:00:21 +0000
@@ -62,41 +62,44 @@
62 ]62 ]
63 self.process_sequence_of_clicks_on_pages(next_steps)63 self.process_sequence_of_clicks_on_pages(next_steps)
64 self.skip_test('manualIntroPage')64 self.skip_test('manualIntroPage')
65 next_steps = [
66 ('userInteractVerifyIntroPage', 'startTestButton'),
67 ('testVerificationPage', 'passButton'),
68 ('userInteractVerifyIntroPage', 'startTestButton'),
69 ('testVerificationPage', 'failButton')
70 ]
71 self.process_sequence_of_clicks_on_pages(next_steps)
72 self.skip_test('userInteractVerifyIntroPage')
73 next_steps = [
74 ('userInteractVerifyIntroPage', 'startTestButton'),
75 ('testVerificationPage', 'passButton'),
76 ('userInteractVerifyIntroPage', 'startTestButton'),
77 ('testVerificationPage', 'failButton')
78 ]
79 self.process_sequence_of_clicks_on_pages(next_steps)
80 self.skip_test('userInteractVerifyIntroPage')
81 next_steps = [
82 ('userInteractVerifyIntroPage', 'startTestButton'),
83 ('userInteractSummary', 'continueButton'),
84 ('userInteractVerifyIntroPage', 'startTestButton'),
85 ('userInteractSummary', 'continueButton'),
86 ]
87 self.process_sequence_of_clicks_on_pages(next_steps)
88 self.skip_test('userInteractVerifyIntroPage')
89 next_steps = [
90 ('userInteractVerifyIntroPage', 'startTestButton'),
91 ('testVerificationPage', 'passButton'),
92 ]
93 self.process_sequence_of_clicks_on_pages(next_steps)
94 # now we use long_wait because we have a long test to wait for (>10s)65 # now we use long_wait because we have a long test to wait for (>10s)
95 self.long_wait_select_single(66 self.long_wait_select_single(
96 self.app, objectName='qmlNativePage', visible=True)67 self.app, objectName='userInteractVerifyIntroPage', visible=True)
68 next_steps = [
69 ('userInteractVerifyIntroPage', 'startTestButton'),
70 ('testVerificationPage', 'passButton'),
71 ]
72 self.process_sequence_of_clicks_on_pages(next_steps)
97 next_steps = [73 next_steps = [
98 ('qmlNativePage', 'continueButton'),74 ('qmlNativePage', 'continueButton'),
99 ('qmlTestPage', 'passButton'),75 ('qmlTestPage', 'passButton'),
76 ]
77 self.process_sequence_of_clicks_on_pages(next_steps)
78 next_steps = [
79 ('userInteractVerifyIntroPage', 'startTestButton'),
80 ('userInteractSummary', 'continueButton'),
81 ('userInteractVerifyIntroPage', 'startTestButton'),
82 ('userInteractSummary', 'continueButton'),
83 ]
84 self.process_sequence_of_clicks_on_pages(next_steps)
85 self.skip_test('userInteractVerifyIntroPage')
86 next_steps = [
87 ('userInteractVerifyIntroPage', 'startTestButton'),
88 ('testVerificationPage', 'passButton'),
89 ('userInteractVerifyIntroPage', 'startTestButton'),
90 ('testVerificationPage', 'failButton')
91 ]
92 self.process_sequence_of_clicks_on_pages(next_steps)
93 self.skip_test('userInteractVerifyIntroPage')
94 next_steps = [
95 ('userInteractVerifyIntroPage', 'startTestButton'),
96 ('testVerificationPage', 'passButton'),
97 ('userInteractVerifyIntroPage', 'startTestButton'),
98 ('testVerificationPage', 'failButton')
99 ]
100 self.process_sequence_of_clicks_on_pages(next_steps)
101 self.skip_test('userInteractVerifyIntroPage')
102 next_steps = [
100 ('rerunSelectionPage', 'continueButton')103 ('rerunSelectionPage', 'continueButton')
101 ]104 ]
102 self.process_sequence_of_clicks_on_pages(next_steps)105 self.process_sequence_of_clicks_on_pages(next_steps)
103106
=== modified file 'plainbox/plainbox/impl/session/assistant.py'
--- plainbox/plainbox/impl/session/assistant.py 2015-08-28 13:11:25 +0000
+++ plainbox/plainbox/impl/session/assistant.py 2015-08-31 12:00:21 +0000
@@ -371,7 +371,10 @@
371 self.session_available(self._manager.storage.id)371 self.session_available(self._manager.storage.id)
372 _logger.debug("New session created: %s", title)372 _logger.debug("New session created: %s", title)
373 UsageExpectation.of(self).allowed_calls = {373 UsageExpectation.of(self).allowed_calls = {
374 self.select_test_plan: "select the test plan to execute"374 self.select_test_plan: "select the test plan to execute",
375 self.get_session_id: "to get the id of currently running session",
376 self.get_session_dir: ("to get the path where current session is"
377 "stored"),
375 }378 }
376379
377 @raises(KeyError, UnexpectedMethodCall)380 @raises(KeyError, UnexpectedMethodCall)
@@ -412,8 +415,9 @@
412 execution_ctrl_list=self._execution_ctrl_list)415 execution_ctrl_list=self._execution_ctrl_list)
413 self.session_available(self._manager.storage.id)416 self.session_available(self._manager.storage.id)
414 _logger.debug("Session resumed: %s", session_id)417 _logger.debug("Session resumed: %s", session_id)
415 UsageExpectation.of(self).allowed_calls = (418 UsageExpectation.of(self).allowed_calls = {
416 self._get_allowed_calls_in_normal_state())419 self.select_test_plan: "to save test plan selection",
420 }
417 return self._resume_candidates[session_id][1]421 return self._resume_candidates[session_id][1]
418422
419 @raises(UnexpectedMethodCall)423 @raises(UnexpectedMethodCall)
@@ -556,6 +560,8 @@
556 to actually allowing the user to know what jobs are available.560 to actually allowing the user to know what jobs are available.
557 """561 """
558 UsageExpectation.of(self).enforce()562 UsageExpectation.of(self).enforce()
563 UsageExpectation.of(self).allowed_calls = (
564 self._get_allowed_calls_in_normal_state())
559 return [unit.id for unit in self._context.unit_list565 return [unit.id for unit in self._context.unit_list
560 if unit.Meta.name == 'test plan']566 if unit.Meta.name == 'test plan']
561567
@@ -1036,6 +1042,32 @@
10361042
1037 return stats1043 return stats
10381044
1045 @raises(UnexpectedMethodCall)
1046 def finalize_session(self) -> None:
1047 """
1048 Finish the execution of the current session.
1049
1050 :raises UnexpectedMethodCall:
1051 If the call is made at an unexpected time. Do not catch this error.
1052 It is a bug in your program. The error message will indicate what
1053 is the likely cause.
1054
1055 Mark the session as complete, which prohibits running (or rerunning)
1056 any job.
1057 """
1058 UsageExpectation.of(self).enforce()
1059 if SessionMetaData.FLAG_SUBMITTED not in self._metadata.flags:
1060 _logger.warning("Finalizing session that hasn't been submitted "
1061 "anywhere: %s", self._manager.storage.id)
1062 self._metadata.flags.remove(SessionMetaData.FLAG_INCOMPLETE)
1063 self._manager.checkpoint()
1064 UsageExpectation.of(self).allowed_calls = {
1065 self.export_to_transport: "to export the results and send them",
1066 self.export_to_file: "to export the results to a file",
1067 self.get_resumable_sessions: "to get resume candidates",
1068 self.start_new_session: "to create a new session",
1069 }
1070
1039 @raises(KeyError, TransportError, UnexpectedMethodCall)1071 @raises(KeyError, TransportError, UnexpectedMethodCall)
1040 def export_to_transport(1072 def export_to_transport(
1041 self, exporter_id: str, transport: ISessionStateTransport1073 self, exporter_id: str, transport: ISessionStateTransport
@@ -1186,6 +1218,8 @@
1186 # XXX: should this be available right off the bat or should we wait1218 # XXX: should this be available right off the bat or should we wait
1187 # until all of the mandatory jobs have been executed.1219 # until all of the mandatory jobs have been executed.
1188 self.export_to_transport: "to export the results and send them",1220 self.export_to_transport: "to export the results and send them",
1221 self.export_to_file: "to export the results to a file",
1222 self.finalize_session: "to mark the session as complete",
1189 }1223 }
11901224
11911225

Subscribers

People subscribed via source and target branches