Merge lp:~kissiel/checkbox/sa-in-cbt into lp:checkbox
- sa-in-cbt
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki (community) | Approve | ||
Review via email: mp+269634@code.launchpad.net |
Commit message
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_
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_
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_
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-
e507f79 plainbox:
a0e3297 plainbox:
37153b4 checkbox-touch: move embedded providers stuff to CBTApp.__init__
41c813e plainbox:
73adaa6 plainbox:
db9146c checkbox-
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
Preview Diff
1 | === modified file 'checkbox-touch/checkbox-touch.qml' |
2 | --- checkbox-touch/checkbox-touch.qml 2015-08-18 12:59:01 +0000 |
3 | +++ checkbox-touch/checkbox-touch.qml 2015-08-31 12:00:21 +0000 |
4 | @@ -155,7 +155,7 @@ |
5 | "checkbox_touch" : applicationVersion, |
6 | "plainbox" : plainboxVersion |
7 | }; |
8 | - resumeOrStartSession(appSettings["providersDir"]); |
9 | + resumeOrStartSession(); |
10 | } |
11 | onSessionReady: { |
12 | welcomePage.enableButton() |
13 | @@ -163,7 +163,8 @@ |
14 | Component.onCompleted: { |
15 | // register to py.initiated signal |
16 | py.onInitiated.connect(function() { |
17 | - construct("checkbox_touch.create_app_object", []); |
18 | + construct("checkbox_touch.create_app_object", [ |
19 | + appSettings["providersDir"]]); |
20 | }); |
21 | } |
22 | } |
23 | @@ -267,14 +268,14 @@ |
24 | |
25 | ResumeSessionPage { |
26 | id: resumeSessionPage |
27 | - onRerunLast: app.resumeSession(true, appSettings["providersDir"], processNextTest) |
28 | - onContinueSession: app.resumeSession(false, appSettings["providersDir"], processNextTest) |
29 | + onRerunLast: app.resumeSession(true, processNextTest) |
30 | + onContinueSession: app.resumeSession(false, processNextTest) |
31 | resumeText: i18n.tr("Checkbox did not finish completely.\nDo you want \ |
32 | to rerun last test, continue to the next test, or restart from the beginning?") |
33 | onRestartSession: { |
34 | pageStack.clear(); |
35 | pageStack.push(welcomePage); |
36 | - app.startSession(appSettings["providersDir"]); |
37 | + app.startSession(); |
38 | } |
39 | } |
40 | |
41 | @@ -407,10 +408,10 @@ |
42 | } else { |
43 | if (result.errors_encountered) { |
44 | ErrorLogic.showError(mainView, i18n.tr("Could not resume session."), |
45 | - app.startSession(appSettings["providersDir"]), |
46 | + app.startSession(), |
47 | i18n.tr("Start new session")); |
48 | } else { |
49 | - app.startSession(appSettings["providersDir"]); |
50 | + app.startSession(); |
51 | } |
52 | } |
53 | }); |
54 | @@ -502,7 +503,7 @@ |
55 | resultsPage.endTesting.connect(function() { |
56 | pageStack.clear(); |
57 | app.clearSession(function() { |
58 | - app.startSession(appSettings["providersDir"]); |
59 | + app.startSession(); |
60 | pageStack.push(welcomePage); |
61 | }); |
62 | }); |
63 | @@ -535,13 +536,15 @@ |
64 | } |
65 | |
66 | function performManualTest(test) { |
67 | - var manualIntroPage = Qt.createComponent(Qt.resolvedUrl("components/ManualIntroPage.qml")).createObject(); |
68 | - manualIntroPage.test = test |
69 | - manualIntroPage.testDone.connect(completeTest); |
70 | - manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); }); |
71 | - manualIntroPage.__customHeaderContents = progressHeader; |
72 | - progressHeader.update(test); |
73 | - pageStack.push(manualIntroPage); |
74 | + runTestActivity(test, function(test) { |
75 | + var manualIntroPage = Qt.createComponent(Qt.resolvedUrl("components/ManualIntroPage.qml")).createObject(); |
76 | + manualIntroPage.test = test |
77 | + manualIntroPage.testDone.connect(completeTest); |
78 | + manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); }); |
79 | + manualIntroPage.__customHeaderContents = progressHeader; |
80 | + progressHeader.update(test); |
81 | + pageStack.push(manualIntroPage); |
82 | + }); |
83 | } |
84 | |
85 | function performUserInteractVerifyTest(test) { |
86 | @@ -592,25 +595,29 @@ |
87 | } |
88 | |
89 | function performQmlTest(test) { |
90 | - var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlNativePage.qml")) |
91 | - console.log(comp.errorString()); |
92 | - var qmlNativePage = comp.createObject(); |
93 | - qmlNativePage.test = test |
94 | - qmlNativePage.testDone.connect(completeTest); |
95 | - qmlNativePage.__customHeaderContents = progressHeader; |
96 | - progressHeader.update(test); |
97 | - pageStack.push(qmlNativePage); |
98 | + runTestActivity(test, function(test) { |
99 | + var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlNativePage.qml")) |
100 | + console.log(comp.errorString()); |
101 | + var qmlNativePage = comp.createObject(); |
102 | + qmlNativePage.test = test |
103 | + qmlNativePage.testDone.connect(completeTest); |
104 | + qmlNativePage.__customHeaderContents = progressHeader; |
105 | + progressHeader.update(test); |
106 | + pageStack.push(qmlNativePage); |
107 | + }); |
108 | } |
109 | function performConfinedQmlTest(test) { |
110 | - var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlConfinedPage.qml")) |
111 | - console.log(comp.errorString()); |
112 | - var qmlNativePage = comp.createObject(); |
113 | - qmlNativePage.test = test |
114 | - qmlNativePage.applicationVersion = app.applicationVersion; |
115 | - qmlNativePage.testDone.connect(completeTest); |
116 | - qmlNativePage.__customHeaderContents = progressHeader; |
117 | - progressHeader.update(test); |
118 | - pageStack.push(qmlNativePage); |
119 | + runTestActivity(test, function(test) { |
120 | + var comp = Qt.createComponent(Qt.resolvedUrl("components/QmlConfinedPage.qml")) |
121 | + console.log(comp.errorString()); |
122 | + var qmlNativePage = comp.createObject(); |
123 | + qmlNativePage.test = test |
124 | + qmlNativePage.applicationVersion = app.applicationVersion; |
125 | + qmlNativePage.testDone.connect(completeTest); |
126 | + qmlNativePage.__customHeaderContents = progressHeader; |
127 | + progressHeader.update(test); |
128 | + pageStack.push(qmlNativePage); |
129 | + }); |
130 | } |
131 | |
132 | function showVerificationScreen(test) { |
133 | |
134 | === modified file 'checkbox-touch/components/CheckboxTouchApplication.qml' |
135 | --- checkbox-touch/components/CheckboxTouchApplication.qml 2015-07-29 20:50:05 +0000 |
136 | +++ checkbox-touch/components/CheckboxTouchApplication.qml 2015-08-31 12:00:21 +0000 |
137 | @@ -45,8 +45,8 @@ |
138 | // Starts session in plainbox and runs all necessary setup actions. |
139 | // Calling this function will signal sessionReady() once it's finished |
140 | // doing setup. |
141 | - function startSession(providersDir) { |
142 | - request("start_session", [providersDir], function(result) { |
143 | + function startSession() { |
144 | + request("start_session", [], function(result) { |
145 | sessionDir = result['session_dir']; |
146 | sessionReady(); |
147 | }, function(error) { |
148 | @@ -57,8 +57,8 @@ |
149 | i18n.tr("Quit")); |
150 | }); |
151 | } |
152 | - function resumeSession(rerunLastTest, providersDir, continuation) { |
153 | - request("resume_session", [rerunLastTest, providersDir], function(result) { |
154 | + function resumeSession(rerunLastTest, continuation) { |
155 | + request("resume_session", [rerunLastTest], function(result) { |
156 | if (!result["session_id"]) { |
157 | pageStack.pop(); |
158 | ErrorLogic.showError(mainView, |
159 | |
160 | === modified file 'checkbox-touch/py/checkbox_touch.py' |
161 | --- checkbox-touch/py/checkbox_touch.py 2015-07-31 13:07:18 +0000 |
162 | +++ checkbox-touch/py/checkbox_touch.py 2015-08-31 12:00:21 +0000 |
163 | @@ -32,7 +32,6 @@ |
164 | import abc |
165 | import collections |
166 | import datetime |
167 | -import itertools |
168 | import json |
169 | import logging |
170 | import os |
171 | @@ -43,23 +42,10 @@ |
172 | |
173 | from plainbox.abc import IJobResult |
174 | from plainbox.impl import pod |
175 | -from plainbox.impl.applogic import PlainBoxConfig |
176 | from plainbox.impl.clitools import ToolBase |
177 | from plainbox.impl.commands.inv_run import SilentUI |
178 | from plainbox.impl.result import JobResultBuilder |
179 | -from plainbox.impl.runner import JobRunner |
180 | -from plainbox.impl.secure.origin import Origin |
181 | -from plainbox.impl.secure.qualifiers import FieldQualifier |
182 | -from plainbox.impl.secure.qualifiers import OperatorMatcher |
183 | -from plainbox.impl.secure.qualifiers import select_jobs |
184 | -from plainbox.impl.session import SessionManager |
185 | -from plainbox.impl.session import SessionMetaData |
186 | -from plainbox.impl.session import SessionPeekHelper |
187 | -from plainbox.impl.session import SessionResumeError |
188 | -from plainbox.impl.session.storage import SessionStorageRepository |
189 | -from plainbox.impl.unit.job import JobDefinition |
190 | -from plainbox.impl.unit.validators import compute_value_map |
191 | -from plainbox.public import get_providers |
192 | +from plainbox.impl.session.assistant import SessionAssistant |
193 | import plainbox |
194 | |
195 | from embedded_providers import EmbeddedProvider1PlugInCollection |
196 | @@ -149,25 +135,22 @@ |
197 | |
198 | __version__ = (1, 2, 1, 'final', 0) |
199 | |
200 | - def __init__(self): |
201 | + def __init__(self, providers_dir): |
202 | if plainbox.__version__ < (0, 22): |
203 | raise SystemExit("plainbox 0.22 required, you have {}".format( |
204 | ToolBase.format_version_tuple(plainbox.__version__))) |
205 | - # adjust_logging(logging.INFO, ['checkbox.touch'], True) |
206 | - self.manager = None |
207 | - self.context = None |
208 | - self.runner = None |
209 | - self.index = 0 # NOTE: next test index |
210 | - # NOTE: This may also have "" representing None |
211 | - self.desired_category_ids = frozenset() |
212 | - self.desired_test_ids = frozenset() |
213 | - self.test_plan_id = "" |
214 | - self.resume_candidate_storage = None |
215 | - self.session_storage_repo = None |
216 | - self.timestamp = datetime.datetime.utcnow().isoformat() |
217 | - self.config = PlainBoxConfig() |
218 | + self.assistant = SessionAssistant('checkbox-converged') |
219 | + self.ui = CheckboxTouchUI() |
220 | + self.index = 0 |
221 | self._password = None |
222 | - self.ui = CheckboxTouchUI() |
223 | + self._timestamp = None |
224 | + self._latest_session = None |
225 | + self.resume_candidate_storage = None |
226 | + self.assistant.use_alternate_repository( |
227 | + self._get_app_cache_directory()) |
228 | + self.assistant.select_providers( |
229 | + '*', |
230 | + additional_providers=self._get_embedded_providers(providers_dir)) |
231 | |
232 | def __repr__(self): |
233 | return "app" |
234 | @@ -182,186 +165,101 @@ |
235 | } |
236 | |
237 | @view |
238 | - def start_session(self, providers_dir): |
239 | - if self.manager is not None: |
240 | - _logger.warning("start_session() should not be called twice!") |
241 | - else: |
242 | - self._init_session_storage_repo() |
243 | - self.manager = SessionManager.create(self.session_storage_repo) |
244 | - self.manager.add_local_device_context() |
245 | - self.context = self.manager.default_device_context |
246 | - # Add some all providers into the context |
247 | - for provider in self._get_default_providers(providers_dir): |
248 | - self.context.add_provider(provider) |
249 | - # Fill in the meta-data |
250 | - self.context.state.metadata.app_id = 'checkbox-touch' |
251 | - self.context.state.metadata.title = 'Checkbox Touch Session' |
252 | - self.context.state.metadata.flags.add('bootstrapping') |
253 | - # Checkpoint the session so that we have something to see |
254 | - self._checkpoint() |
255 | - |
256 | - # Prepare custom execution controller list |
257 | - from plainbox.impl.ctrl import UserJobExecutionController |
258 | - from sudo_with_pass_ctrl import \ |
259 | - RootViaSudoWithPassExecutionController |
260 | - controllers = [ |
261 | - RootViaSudoWithPassExecutionController( |
262 | - self.context.provider_list, self._password_provider), |
263 | - UserJobExecutionController(self.context.provider_list), |
264 | - ] |
265 | - self.runner = JobRunner( |
266 | - self.manager.storage.location, |
267 | - self.context.provider_list, |
268 | - # TODO: tie this with well-known-dirs helper |
269 | - os.path.join(self.manager.storage.location, 'io-logs'), |
270 | - execution_ctrl_list=controllers) |
271 | - app_cache_dir = self._get_app_cache_directory() |
272 | - if not os.path.exists(app_cache_dir): |
273 | - os.makedirs(app_cache_dir) |
274 | - with open(os.path.join(app_cache_dir, 'session_id'), |
275 | - 'w') as f: |
276 | - f.write(self.manager.storage.id) |
277 | + def start_session(self): |
278 | + """Start a new session.""" |
279 | + self.assistant.start_new_session('Checkbox Converged session') |
280 | + self._timestamp = datetime.datetime.utcnow().isoformat() |
281 | return { |
282 | - 'session_id': self.manager.storage.id, |
283 | - 'session_dir': self.manager.storage.location |
284 | + 'session_id': self.assistant.get_session_id(), |
285 | + 'session_dir': self.assistant.get_session_dir() |
286 | } |
287 | |
288 | @view |
289 | - def resume_session(self, rerun_last_test, providers_dir): |
290 | - all_units = list(itertools.chain( |
291 | - *[p.unit_list for p in self._get_default_providers( |
292 | - providers_dir)])) |
293 | - try: |
294 | - self.manager = SessionManager.load_session( |
295 | - all_units, self.resume_candidate_storage) |
296 | - except IOError as exc: |
297 | - _logger.info("Exception raised when trying to resume" |
298 | - "session: %s", str(exc)) |
299 | - return { |
300 | - 'session_id': None |
301 | - } |
302 | - self.context = self.manager.default_device_context |
303 | - metadata = self.context.state.metadata |
304 | + def resume_session(self, rerun_last_test): |
305 | + """ |
306 | + Resume latest sesssion. |
307 | + |
308 | + :param rerun_last_test: |
309 | + A bool stating whether runtime should repeat the test, that the app |
310 | + was executing when it was interrupted. |
311 | + """ |
312 | + metadata = self.assistant.resume_session(self._latest_session) |
313 | app_blob = json.loads(metadata.app_blob.decode("UTF-8")) |
314 | - self.runner = JobRunner( |
315 | - self.manager.storage.location, |
316 | - self.context.provider_list, |
317 | - os.path.join(self.manager.storage.location, 'io-logs')) |
318 | self.index = app_blob['index_in_run_list'] |
319 | - self._init_test_plan_id(app_blob['test_plan_id']) |
320 | - _logger.error(self.context.state.run_list) |
321 | - _logger.error(self.index) |
322 | + self.test_plan_id = app_blob['test_plan_id'] |
323 | + self.assistant.select_test_plan(self.test_plan_id) |
324 | + self.assistant.bootstrap() |
325 | + |
326 | if not rerun_last_test: |
327 | # Skip current test |
328 | test = self.get_next_test()['result'] |
329 | test['outcome'] = 'skip' |
330 | self.register_test_result(test) |
331 | return { |
332 | - 'session_id': self.manager.storage.id |
333 | + 'session_id': self._latest_session |
334 | } |
335 | |
336 | @view |
337 | def clear_session(self): |
338 | - self.manager = None |
339 | - self.context = None |
340 | - self.runner = None |
341 | + """Reset app-custom state info about the session.""" |
342 | self.index = 0 |
343 | - self.timestamp = datetime.datetime.utcnow().isoformat() |
344 | - self.desired_category_ids = frozenset() |
345 | - self.desired_test_ids = frozenset() |
346 | - self.resume_candidate_storage = None |
347 | - self.session_storage_repo = None |
348 | - os.unlink(os.path.join(self._get_app_cache_directory(), 'session_id')) |
349 | + self._timestamp = datetime.datetime.utcnow().isoformat() |
350 | |
351 | @view |
352 | def is_session_resumable(self): |
353 | - """ |
354 | - Checks whether given session is resumable |
355 | - """ |
356 | - resumable = False |
357 | - try: |
358 | - with open(os.path.join(self._get_app_cache_directory(), |
359 | - 'session_id')) as f: |
360 | - session_id = f.readline().rstrip('\n') |
361 | - except (OSError, IOError): |
362 | - session_id = None |
363 | - self._init_session_storage_repo() |
364 | - for storage in self.session_storage_repo.get_storage_list(): |
365 | - data = storage.load_checkpoint() |
366 | - if len(data) == 0: |
367 | - continue |
368 | - try: |
369 | - metadata = SessionPeekHelper().peek(data) |
370 | - if (metadata.app_id == 'checkbox-touch' |
371 | - and storage.id == session_id |
372 | - and SessionMetaData.FLAG_INCOMPLETE in |
373 | - metadata.flags): |
374 | - self.resume_candidate_storage = storage |
375 | - resumable = True |
376 | - except SessionResumeError as exc: |
377 | - _logger.info("Exception raised when trying to resume" |
378 | - "session: %s", str(exc)) |
379 | - return { |
380 | - 'resumable': False, |
381 | - 'errors_encountered': True |
382 | - } |
383 | - return { |
384 | - 'resumable': resumable, |
385 | - 'errors_encountered': False |
386 | - } |
387 | + """Check whether there is a session that can be resumed.""" |
388 | + for session_id, session_md in self.assistant.get_resumable_sessions(): |
389 | + # we're interested in the latest session only, this is why we |
390 | + # return early |
391 | + self._latest_session = session_id |
392 | + return { |
393 | + 'resumable': True, |
394 | + 'error_encountered': False, |
395 | + } |
396 | + else: |
397 | + return { |
398 | + 'resumable': False, |
399 | + 'error_encountered': False, |
400 | + } |
401 | |
402 | @view |
403 | def get_testplans(self): |
404 | - all_units = self.manager.default_device_context.unit_list |
405 | + """Get the list of available test plans.""" |
406 | + test_plan_units = [self.assistant.get_test_plan(tp_id) for tp_id in |
407 | + self.assistant.get_test_plans()] |
408 | return { |
409 | 'testplan_info_list': [{ |
410 | - "mod_id": unit.id, |
411 | - "mod_name": unit.name, |
412 | + "mod_id": tp.id, |
413 | + "mod_name": tp.name, |
414 | "mod_selected": False, |
415 | - } for unit in all_units if unit.Meta.name == 'test plan'] |
416 | + } for tp in test_plan_units] |
417 | } |
418 | |
419 | @view |
420 | def remember_testplan(self, test_plan_id): |
421 | - self.context.invalidate_shared('potential_job_list') |
422 | - self.context.invalidate_shared('potential_category_map') |
423 | - self._init_test_plan_id(test_plan_id) |
424 | + """Pick the test plan as the one in force.""" |
425 | + self.test_plan_id = test_plan_id |
426 | + self.assistant.select_test_plan(test_plan_id) |
427 | + self.assistant.bootstrap() |
428 | |
429 | @view |
430 | def get_categories(self): |
431 | - """ |
432 | - Get categories selection data. |
433 | - """ |
434 | - potential_job_list = self.context.compute_shared( |
435 | - 'potential_job_list', select_jobs, |
436 | - self.context.state.job_list, [self.test_plan.get_qualifier()]) |
437 | - potential_category_map = self.context.compute_shared( |
438 | - 'potential_category_map', |
439 | - self.test_plan.get_effective_category_map, potential_job_list) |
440 | - id_map = self.context.compute_shared( |
441 | - 'id_map', compute_value_map, self.context, 'id') |
442 | + """Get categories selection data.""" |
443 | category_info_list = [{ |
444 | "mod_id": category.id, |
445 | "mod_name": category.name, |
446 | "mod_selected": True, |
447 | } for category in ( |
448 | - id_map[category_id][0] |
449 | - for category_id in set(potential_category_map.values()) |
450 | + self.assistant.get_category(category_id) |
451 | + for category_id in self.assistant.get_participating_categories() |
452 | )] |
453 | - category_info_list.sort(key=lambda ci: ci['mod_name']) |
454 | - return { |
455 | - 'category_info_list': category_info_list |
456 | - } |
457 | + return {'category_info_list': category_info_list} |
458 | |
459 | @view |
460 | def remember_categories(self, selected_id_list): |
461 | - """ |
462 | - Save category selection |
463 | - """ |
464 | - self.desired_category_ids = frozenset(selected_id_list) |
465 | - self.context.invalidate_shared('subset_job_list') |
466 | - self.context.invalidate_shared('effective_category_map') |
467 | - _logger.info("Selected categories: %s", self.desired_category_ids) |
468 | + """Save category selection.""" |
469 | + _logger.info("Selected categories: %s", selected_id_list) |
470 | + self.assistant.filter_jobs_by_categories(selected_id_list) |
471 | |
472 | @view |
473 | def get_available_tests(self): |
474 | @@ -371,89 +269,65 @@ |
475 | The response object will contain only tests with category matching |
476 | previously set list. Tests are sorted by (category, name) |
477 | """ |
478 | - subset_job_list = self.context.compute_shared( |
479 | - 'subset_job_list', select_jobs, |
480 | - self.context.state.job_list, [ |
481 | - # Select everything the test plan selected |
482 | - self.test_plan.get_qualifier(), |
483 | - # Except jobs not matching the selected group of categories |
484 | - FieldQualifier( |
485 | - JobDefinition.Meta.fields.category_id, |
486 | - OperatorMatcher(not_contains, self.desired_category_ids), |
487 | - Origin.get_caller_origin(), inclusive=False), |
488 | - ]) |
489 | - effective_category_map = self.context.compute_shared( |
490 | - 'effective_category_map', |
491 | - self.test_plan.get_effective_category_map, subset_job_list) |
492 | - for job_id, effective_category_id in effective_category_map.items(): |
493 | - job_state = self.context.state.job_state_map[job_id] |
494 | - job_state.effective_category_id = effective_category_id |
495 | - id_map = self.context.compute_shared( |
496 | - 'id_map', compute_value_map, self.context, 'id') |
497 | + category_names = { |
498 | + cat_id: self.assistant.get_category(cat_id).tr_name() for |
499 | + cat_id in self.assistant.get_participating_categories()} |
500 | + job_units = [self.assistant.get_job(job_id) for job_id in |
501 | + self.assistant.get_static_todo_list()] |
502 | test_info_list = [{ |
503 | - "mod_id": job_id, |
504 | - "mod_name": id_map[job_id][0].tr_summary(), |
505 | - "mod_group": id_map[category_id][0].tr_name(), |
506 | + "mod_id": job.id, |
507 | + "mod_name": job.tr_summary(), |
508 | + "mod_group": category_names[job.category_id], |
509 | "mod_selected": True, |
510 | - } for job_id, category_id in effective_category_map.items()] |
511 | + } for job in job_units] |
512 | test_info_list.sort(key=lambda ti: (ti['mod_group'], ti['mod_name'])) |
513 | - return { |
514 | - 'test_info_list': test_info_list |
515 | - } |
516 | + return {'test_info_list': test_info_list} |
517 | + |
518 | @view |
519 | def get_rerun_candidates(self): |
520 | + """Get all the tests that might be selected for rerunning.""" |
521 | def rerun_predicate(job_state): |
522 | return job_state.result.outcome in ( |
523 | IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH) |
524 | - id_map = self.context.compute_shared( |
525 | - 'id_map', compute_value_map, self.context, 'id') |
526 | rerun_candidates = [] |
527 | - for job in self.manager.state.run_list: |
528 | - if rerun_predicate(self.manager.state.job_state_map[job.id]): |
529 | + todo_list = self.assistant.get_static_todo_list() |
530 | + job_units = {job_id: self.assistant.get_job(job_id) for job_id |
531 | + in todo_list} |
532 | + job_states = {job_id: self.assistant.get_job_state(job_id) for job_id |
533 | + in todo_list} |
534 | + category_names = { |
535 | + cat_id: self.assistant.get_category(cat_id).tr_name() for cat_id |
536 | + in self.assistant.get_participating_categories()} |
537 | + for job_id, job_state in job_states.items(): |
538 | + if rerun_predicate(job_state): |
539 | rerun_candidates.append({ |
540 | - "mod_id": job.id, |
541 | - "mod_name": job.tr_summary(), |
542 | - "mod_group": id_map[job.category_id][0].tr_name(), |
543 | + "mod_id": job_id, |
544 | + "mod_name": job_units[job_id].tr_summary(), |
545 | + "mod_group": category_names[job_units[job_id].category_id], |
546 | "mod_selected": False |
547 | - |
548 | }) |
549 | return rerun_candidates |
550 | |
551 | @view |
552 | def remember_tests(self, selected_id_list): |
553 | - """ |
554 | - Save test selection |
555 | - """ |
556 | - self.desired_test_ids = frozenset(selected_id_list) |
557 | - _logger.info("Selected tests: %s", self.desired_test_ids) |
558 | + """Save test selection.""" |
559 | self.index = 0 |
560 | - self.context.invalidate_shared('desired_job_list') |
561 | - desired_job_list = self.context.compute_shared( |
562 | - 'desired_job_list', select_jobs, |
563 | - self.context.state.job_list, [ |
564 | - # Select everything the test plan selected |
565 | - self.test_plan.get_qualifier(), |
566 | - # Except all the jobs that weren't marked by the user |
567 | - FieldQualifier( |
568 | - JobDefinition.Meta.fields.id, |
569 | - OperatorMatcher(not_contains, self.desired_test_ids), |
570 | - Origin.get_caller_origin(), inclusive=False)]) |
571 | - _logger.info("Desired job list: %s", desired_job_list) |
572 | - self.context.state.update_desired_job_list(desired_job_list) |
573 | - _logger.info("Run job list: %s", self.context.state.run_list) |
574 | - self.context.state.metadata.flags.add('incomplete') |
575 | - self._checkpoint() |
576 | + self.assistant.use_alternate_selection(selected_id_list) |
577 | + self.assistant.update_app_blob(self._get_app_blob()) |
578 | + _logger.info("Selected tests: %s", selected_id_list) |
579 | + return |
580 | |
581 | @view |
582 | def get_next_test(self): |
583 | """ |
584 | - Get next text that is scheduled to run |
585 | - Returns test object or None if all tests are completed |
586 | + Get next text that is scheduled to run. |
587 | + |
588 | + :returns: |
589 | + Dictionary resembling JobDefinition or None if all tests are completed |
590 | """ |
591 | - if self.index < len(self.context.state.run_list): |
592 | - job = self.context.state.run_list[self.index] |
593 | - job_state = self.context.state.job_state_map[job.id] |
594 | - # support for description field splitted into 3 subfields |
595 | + todo_list = self.assistant.get_static_todo_list() |
596 | + if self.index < len(todo_list): |
597 | + job = self.assistant.get_job(todo_list[self.index]) |
598 | description = "" |
599 | if job.tr_purpose() is not None: |
600 | description = job.tr_purpose() + "\n" |
601 | @@ -472,97 +346,65 @@ |
602 | "user": job.user, |
603 | "qml_file": job.qml_file, |
604 | "start_time": time.time(), |
605 | - "test_number": self.index, |
606 | - "tests_count": len(self.context.state.run_list), |
607 | + "test_number": todo_list.index(job.id), |
608 | + "tests_count": len(todo_list), |
609 | "command": job.command, |
610 | "flags": job.get_flag_set() |
611 | } |
612 | - if not job_state.can_start(): |
613 | - test["outcome"] = "skip" |
614 | - test["comments"] = job_state.get_readiness_description() |
615 | - self.register_test_result(test) |
616 | - return self.get_next_test()["result"] |
617 | return test |
618 | else: |
619 | return {} |
620 | |
621 | @view |
622 | def register_test_result(self, test): |
623 | - """ |
624 | - Registers outcome of a test |
625 | - """ |
626 | + """Registers outcome of a test.""" |
627 | _logger.info("Storing test result: %s", test) |
628 | job_id = test['id'] |
629 | - job = self.context.state.job_state_map[job_id].job |
630 | builder_kwargs = { |
631 | 'outcome': test['outcome'], |
632 | - 'comments': test.get('comments', pod.UNSET) |
633 | + 'comments': test.get('comments', pod.UNSET), |
634 | + 'execution_duration': time.time() - test['start_time'] |
635 | } |
636 | - # some result may already have been saved if the job had some activity |
637 | - # to run, i.e. result object is available in the test object |
638 | try: |
639 | + # if we're registering skipped test as an outcome of resuming |
640 | + # session, the result field of the test object will be missing |
641 | builder_kwargs['io_log_filename'] = test['result'].io_log_filename |
642 | except KeyError: |
643 | - builder_kwargs['execution_duration'] = ( |
644 | - time.time() - test['start_time']) |
645 | + pass |
646 | + |
647 | result = JobResultBuilder(**builder_kwargs).get_result() |
648 | - self.context.state.update_job_result(job, result) |
649 | + self.assistant.use_job_result(job_id, result) |
650 | self.index += 1 |
651 | - self._checkpoint() |
652 | + self.assistant.update_app_blob(self._get_app_blob()) |
653 | |
654 | @view |
655 | def run_test_activity(self, test): |
656 | - """ |
657 | - Run command associated with given test |
658 | - """ |
659 | - job_id = test['id'] |
660 | - job_state = self.context.state.job_state_map[job_id] |
661 | - job = job_state.job |
662 | - self.context.state.running_job_name = job_id |
663 | - self._checkpoint() |
664 | - try: |
665 | - result = self.runner.run_job(job, job_state, self.config, self.ui) |
666 | - except OSError as exc: |
667 | - result = JobResultBuilder( |
668 | - outcome='fail', |
669 | - comment=str(exc), |
670 | - ).get_result() |
671 | - self.context.state.running_job_name = None |
672 | - self._checkpoint() |
673 | - test['outcome'] = result.outcome |
674 | - test['result'] = result |
675 | + """Run command associated with given test.""" |
676 | + plugins_handled_natively = ['qml'] |
677 | + res_builder = self.assistant.run_job( |
678 | + test['id'], self.ui, test['plugin'] in plugins_handled_natively) |
679 | + test['outcome'] = res_builder.outcome |
680 | + test['result'] = res_builder |
681 | return test |
682 | |
683 | @view |
684 | def get_results(self): |
685 | - """ |
686 | - Get results object |
687 | - """ |
688 | - self.context.state.metadata.flags.remove('incomplete') |
689 | - self._checkpoint() |
690 | - stats = collections.defaultdict(int) |
691 | - for job_state in self.context.state.job_state_map.values(): |
692 | - stats[job_state.result.outcome] += 1 |
693 | + """Get results object.""" |
694 | + self.assistant.finalize_session() |
695 | + stats = self.assistant.get_summary() |
696 | return { |
697 | 'totalPassed': stats[IJobResult.OUTCOME_PASS], |
698 | 'totalFailed': stats[IJobResult.OUTCOME_FAIL], |
699 | - 'totalSkipped': stats[IJobResult.OUTCOME_SKIP], |
700 | + 'totalSkipped': stats[IJobResult.OUTCOME_SKIP] + |
701 | + stats[IJobResult.OUTCOME_NOT_SUPPORTED] |
702 | } |
703 | |
704 | @view |
705 | def export_results(self, output_format, option_list): |
706 | - """ |
707 | - Export results to file |
708 | - """ |
709 | - # Export results in the user's Documents directory |
710 | + """Export results to file(s) in the user's 'Documents' directory..""" |
711 | dirname = self._get_user_directory_documents() |
712 | - exporter = self.manager.create_exporter(output_format, option_list) |
713 | - extension = exporter.unit.file_extension |
714 | - filename = ''.join(['submission_', self.timestamp, '.', extension]) |
715 | - output_file = os.path.join(dirname, filename) |
716 | - with open(output_file, 'wb') as stream: |
717 | - exporter.dump_from_session_manager(self.manager, stream) |
718 | - return output_file |
719 | + return self.assistant.export_to_file( |
720 | + output_format, option_list, dirname) |
721 | |
722 | def _get_user_directory_documents(self): |
723 | xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \ |
724 | @@ -601,11 +443,6 @@ |
725 | raise IOError("{} exists and is not a directory".format(path)) |
726 | return path |
727 | |
728 | - |
729 | - def _checkpoint(self): |
730 | - self.context.state.metadata.app_blob = self._get_app_blob() |
731 | - self.manager.checkpoint() |
732 | - |
733 | def _get_app_blob(self): |
734 | """ |
735 | Get json dump of with app-specific blob |
736 | @@ -615,51 +452,22 @@ |
737 | 'version': 1, |
738 | 'test_plan_id': self.test_plan_id, |
739 | 'index_in_run_list': self.index, |
740 | + 'session_timestamp': self._timestamp, |
741 | }).encode("UTF-8") |
742 | |
743 | - def _init_test_plan_id(self, test_plan_id): |
744 | - """ |
745 | - Validates and stores test_plan_id |
746 | - """ |
747 | - if not isinstance(test_plan_id, str): |
748 | - raise TypeError("test_plan_id must be a string") |
749 | - # Look up the test plan with the specified identifier |
750 | - id_map = self.context.compute_shared( |
751 | - 'id_map', compute_value_map, self.context, 'id') |
752 | - try: |
753 | - test_plan = id_map[test_plan_id][0] |
754 | - except KeyError: |
755 | - raise ValueError( |
756 | - "cannot find any unit with id: {!r}".format(test_plan_id)) |
757 | - if test_plan.Meta.name != 'test plan': |
758 | - raise ValueError( |
759 | - "unit {!r} is not a test plan".format(test_plan_id)) |
760 | - self.test_plan_id = test_plan_id |
761 | - self.test_plan = test_plan |
762 | - |
763 | - def _init_session_storage_repo(self): |
764 | - """ |
765 | - Init storage repository. |
766 | - """ |
767 | - self.session_storage_repo = SessionStorageRepository( |
768 | - self._get_app_cache_directory()) |
769 | - |
770 | - def _get_default_providers(self, providers_dir): |
771 | - """ |
772 | - Get providers |
773 | + def _get_embedded_providers(self, providers_dir): |
774 | + """ |
775 | + Get providers included with the app |
776 | |
777 | :param providers_dir: |
778 | Path within application tree from which to load providers |
779 | :returns: |
780 | list of loaded providers |
781 | """ |
782 | - provider_list = get_providers() |
783 | - # when running on ubuntu-touch device, APP_DIR env var is present |
784 | - # and points to touch application top directory |
785 | + provider_list = [] |
786 | app_root_dir = os.path.normpath(os.getenv( |
787 | 'APP_DIR', os.path.join(os.path.dirname(__file__), '..'))) |
788 | - path = os.path.join(app_root_dir, |
789 | - os.path.normpath(providers_dir)) |
790 | + path = os.path.join(app_root_dir, os.path.normpath(providers_dir)) |
791 | _logger.info("Loading all providers from %s", path) |
792 | if os.path.exists(path): |
793 | embedded_providers = EmbeddedProvider1PlugInCollection(path) |
794 | @@ -683,7 +491,7 @@ |
795 | |
796 | |
797 | def get_qml_logger(default_level): |
798 | - logging_level = collections.defaultdict(lambda:logging.INFO, { |
799 | + logging_level = collections.defaultdict(lambda: logging.INFO, { |
800 | "debug": logging.DEBUG, |
801 | "warning": logging.WARNING, |
802 | "warn": logging.WARN, |
803 | |
804 | === modified file 'checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py' |
805 | --- checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-07-23 08:25:48 +0000 |
806 | +++ checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-08-31 12:00:21 +0000 |
807 | @@ -62,41 +62,44 @@ |
808 | ] |
809 | self.process_sequence_of_clicks_on_pages(next_steps) |
810 | self.skip_test('manualIntroPage') |
811 | - next_steps = [ |
812 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
813 | - ('testVerificationPage', 'passButton'), |
814 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
815 | - ('testVerificationPage', 'failButton') |
816 | - ] |
817 | - self.process_sequence_of_clicks_on_pages(next_steps) |
818 | - self.skip_test('userInteractVerifyIntroPage') |
819 | - next_steps = [ |
820 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
821 | - ('testVerificationPage', 'passButton'), |
822 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
823 | - ('testVerificationPage', 'failButton') |
824 | - ] |
825 | - self.process_sequence_of_clicks_on_pages(next_steps) |
826 | - self.skip_test('userInteractVerifyIntroPage') |
827 | - next_steps = [ |
828 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
829 | - ('userInteractSummary', 'continueButton'), |
830 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
831 | - ('userInteractSummary', 'continueButton'), |
832 | - ] |
833 | - self.process_sequence_of_clicks_on_pages(next_steps) |
834 | - self.skip_test('userInteractVerifyIntroPage') |
835 | - next_steps = [ |
836 | - ('userInteractVerifyIntroPage', 'startTestButton'), |
837 | - ('testVerificationPage', 'passButton'), |
838 | - ] |
839 | - self.process_sequence_of_clicks_on_pages(next_steps) |
840 | # now we use long_wait because we have a long test to wait for (>10s) |
841 | self.long_wait_select_single( |
842 | - self.app, objectName='qmlNativePage', visible=True) |
843 | + self.app, objectName='userInteractVerifyIntroPage', visible=True) |
844 | + next_steps = [ |
845 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
846 | + ('testVerificationPage', 'passButton'), |
847 | + ] |
848 | + self.process_sequence_of_clicks_on_pages(next_steps) |
849 | next_steps = [ |
850 | ('qmlNativePage', 'continueButton'), |
851 | ('qmlTestPage', 'passButton'), |
852 | + ] |
853 | + self.process_sequence_of_clicks_on_pages(next_steps) |
854 | + next_steps = [ |
855 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
856 | + ('userInteractSummary', 'continueButton'), |
857 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
858 | + ('userInteractSummary', 'continueButton'), |
859 | + ] |
860 | + self.process_sequence_of_clicks_on_pages(next_steps) |
861 | + self.skip_test('userInteractVerifyIntroPage') |
862 | + next_steps = [ |
863 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
864 | + ('testVerificationPage', 'passButton'), |
865 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
866 | + ('testVerificationPage', 'failButton') |
867 | + ] |
868 | + self.process_sequence_of_clicks_on_pages(next_steps) |
869 | + self.skip_test('userInteractVerifyIntroPage') |
870 | + next_steps = [ |
871 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
872 | + ('testVerificationPage', 'passButton'), |
873 | + ('userInteractVerifyIntroPage', 'startTestButton'), |
874 | + ('testVerificationPage', 'failButton') |
875 | + ] |
876 | + self.process_sequence_of_clicks_on_pages(next_steps) |
877 | + self.skip_test('userInteractVerifyIntroPage') |
878 | + next_steps = [ |
879 | ('rerunSelectionPage', 'continueButton') |
880 | ] |
881 | self.process_sequence_of_clicks_on_pages(next_steps) |
882 | |
883 | === modified file 'plainbox/plainbox/impl/session/assistant.py' |
884 | --- plainbox/plainbox/impl/session/assistant.py 2015-08-28 13:11:25 +0000 |
885 | +++ plainbox/plainbox/impl/session/assistant.py 2015-08-31 12:00:21 +0000 |
886 | @@ -371,7 +371,10 @@ |
887 | self.session_available(self._manager.storage.id) |
888 | _logger.debug("New session created: %s", title) |
889 | UsageExpectation.of(self).allowed_calls = { |
890 | - self.select_test_plan: "select the test plan to execute" |
891 | + self.select_test_plan: "select the test plan to execute", |
892 | + self.get_session_id: "to get the id of currently running session", |
893 | + self.get_session_dir: ("to get the path where current session is" |
894 | + "stored"), |
895 | } |
896 | |
897 | @raises(KeyError, UnexpectedMethodCall) |
898 | @@ -412,8 +415,9 @@ |
899 | execution_ctrl_list=self._execution_ctrl_list) |
900 | self.session_available(self._manager.storage.id) |
901 | _logger.debug("Session resumed: %s", session_id) |
902 | - UsageExpectation.of(self).allowed_calls = ( |
903 | - self._get_allowed_calls_in_normal_state()) |
904 | + UsageExpectation.of(self).allowed_calls = { |
905 | + self.select_test_plan: "to save test plan selection", |
906 | + } |
907 | return self._resume_candidates[session_id][1] |
908 | |
909 | @raises(UnexpectedMethodCall) |
910 | @@ -556,6 +560,8 @@ |
911 | to actually allowing the user to know what jobs are available. |
912 | """ |
913 | UsageExpectation.of(self).enforce() |
914 | + UsageExpectation.of(self).allowed_calls = ( |
915 | + self._get_allowed_calls_in_normal_state()) |
916 | return [unit.id for unit in self._context.unit_list |
917 | if unit.Meta.name == 'test plan'] |
918 | |
919 | @@ -1036,6 +1042,32 @@ |
920 | |
921 | return stats |
922 | |
923 | + @raises(UnexpectedMethodCall) |
924 | + def finalize_session(self) -> None: |
925 | + """ |
926 | + Finish the execution of the current session. |
927 | + |
928 | + :raises UnexpectedMethodCall: |
929 | + If the call is made at an unexpected time. Do not catch this error. |
930 | + It is a bug in your program. The error message will indicate what |
931 | + is the likely cause. |
932 | + |
933 | + Mark the session as complete, which prohibits running (or rerunning) |
934 | + any job. |
935 | + """ |
936 | + UsageExpectation.of(self).enforce() |
937 | + if SessionMetaData.FLAG_SUBMITTED not in self._metadata.flags: |
938 | + _logger.warning("Finalizing session that hasn't been submitted " |
939 | + "anywhere: %s", self._manager.storage.id) |
940 | + self._metadata.flags.remove(SessionMetaData.FLAG_INCOMPLETE) |
941 | + self._manager.checkpoint() |
942 | + UsageExpectation.of(self).allowed_calls = { |
943 | + self.export_to_transport: "to export the results and send them", |
944 | + self.export_to_file: "to export the results to a file", |
945 | + self.get_resumable_sessions: "to get resume candidates", |
946 | + self.start_new_session: "to create a new session", |
947 | + } |
948 | + |
949 | @raises(KeyError, TransportError, UnexpectedMethodCall) |
950 | def export_to_transport( |
951 | self, exporter_id: str, transport: ISessionStateTransport |
952 | @@ -1186,6 +1218,8 @@ |
953 | # XXX: should this be available right off the bat or should we wait |
954 | # until all of the mandatory jobs have been executed. |
955 | self.export_to_transport: "to export the results and send them", |
956 | + self.export_to_file: "to export the results to a file", |
957 | + self.finalize_session: "to mark the session as complete", |
958 | } |
959 | |
960 |
let there be light