Merge lp:~kissiel/checkbox/fix-1397109-garbage-collection into lp:checkbox

Proposed by Maciej Kisielewski
Status: Rejected
Rejected by: Maciej Kisielewski
Proposed branch: lp:~kissiel/checkbox/fix-1397109-garbage-collection
Merge into: lp:checkbox
Diff against target: 318 lines (+190/-11)
5 files modified
checkbox-touch/checkbox-touch.qml (+58/-1)
checkbox-touch/components/CheckboxTouchApplication.qml (+12/-0)
checkbox-touch/py/checkbox_touch.py (+53/-0)
checkbox-touch/tests/autopilot/checkbox_touch/__init__.py (+20/-0)
checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py (+47/-10)
To merge this branch: bzr merge lp:~kissiel/checkbox/fix-1397109-garbage-collection
Reviewer Review Type Date Requested Status
Checkbox Developers Pending
Review via email: mp+260564@code.launchpad.net

Description of the change

This MR brings old and forgotten branch of session garbage collection in Checkbox-Touch.

dec9897 checkbox-touch: add removal of 'bootstrapping' flag
2f1c137 checkbox-touch: add session_garbage_collect function
cf8d3ba checkbox-touch: make exporting of results mark session as 'submitted'
ead21d9 checkbox-touch: make garbage collection a two step process
10ea1ba checkbox-touch: add garbage collection api to CBT app component
4752fca checkbox-touch: run garbage collection when CBT starts
db81a46 checkbox-touch:autopilot: move select_two_tests_and_quit to base
fca9857 checkbox-touch:autopilot: add tests for session garbage collection

To post a comment you must log in.
Revision history for this message
Maciej Kisielewski (kissiel) wrote :

<spineau> kissiel: but I got a situation with 3 red buttons and no subtotals per flags (which could be super nice)
<spineau> kissiel: So I'm wondering if we could instead list them with checkboxes and have buttons like [remove selected sessions] and a [cancel] to just ignore the request for cleanup
<kissiel> spineau, yep, probably :)
<spineau> kissiel: that would leave only 1 red element which is less scary :)
<kissiel> haha :)
<kissiel> spineau, in that case I think I'll use ordinary listview
<spineau> kissiel: still in the popup or a new page?
<kissiel> spineau, I think popup looks better
<spineau> kissiel: +1

Revision history for this message
Maciej Kisielewski (kissiel) wrote :

Unmerged revisions

3817. By Maciej Kisielewski

checkbox-touch:autopilot: add tests for session garbage collection

This patch adds three tests for the session garbage collection functionality:
 - check if the dialog is shown
 - check if 'do not remove' doesn't remove
 - check if 'remove all' actually removes all

The latter two tests only check 'for self symptoms', i.e. after removal the
test only checks if the dialog proposing removal is no longer shown. And for
the one that doesn't remove it checks if the dialog still pops up.

Signed-off-by: Maciej Kisielewski <email address hidden>

3816. By Maciej Kisielewski

checkbox-touch:autopilot: move select_two_tests_and_quit to base

This patch moves the function that selects two tests and quits (the setting up
function for some tests) to the ClickAppTestCase base.

Signed-off-by: Maciej Kisielewski <email address hidden>

3815. By Maciej Kisielewski

checkbox-touch: run garbage collection when CBT starts

This patch makes checkbox-touch run the garbage collection step each time the
application starts.

Signed-off-by: Maciej Kisielewski <email address hidden>

3814. By Maciej Kisielewski

checkbox-touch: add garbage collection api to CBT app component

This patch adds necessary functions to CheckboxTouchApplication component that
forward calls to python.

Signed-off-by: Maciej Kisielewski <email address hidden>

3813. By Maciej Kisielewski

checkbox-touch: make garbage collection a two step process

This patch adds function that checks if there are collection candidates and
saves them in CheckboxTouchApplication state. This lets clients prompt for
confirmation before removing them.

Signed-off-by: Maciej Kisielewski <email address hidden>

3812. By Maciej Kisielewski

checkbox-touch: make exporting of results mark session as 'submitted'

Signed-off-by: Maciej Kisielewski <email address hidden>

3811. By Maciej Kisielewski

checkbox-touch: add session_garbage_collect function

This patch adds the backend function that looks for old and useless sessions
and removes them.

Signed-off-by: Maciej Kisielewski <email address hidden>

3810. By Maciej Kisielewski

checkbox-touch: add removal of 'bootstrapping' flag

This patch makes Checkbox Touch remove 'bootstrapping' flag once user selects
test to run.

Signed-off-by: Maciej Kisielewski <email address hidden>

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-05-27 06:51:23 +0000
+++ checkbox-touch/checkbox-touch.qml 2015-05-29 11:12:57 +0000
@@ -131,7 +131,64 @@
131 "checkbox_touch" : applicationVersion,131 "checkbox_touch" : applicationVersion,
132 "plainbox" : plainboxVersion132 "plainbox" : plainboxVersion
133 };133 };
134 resumeOrStartSession(appSettings["providersDir"]);134 var resumeWithProvidersDir = function() {
135 resumeOrStartSession(appSettings["providersDir"]);
136 };
137 getGarbageCollectionCandidates(function(oldStorages) {
138 var buttons = [];
139 var flagToButtonText = {
140 "incomplete": i18n.tr("Remove incomplete sessions"),
141 "submitted": i18n.tr("Remove submitted sessions"),
142 }
143 var candidateFlags = [];
144 var totalCandidatesCount = 0;
145 for (var flag in flagToButtonText) {
146 if (oldStorages[flag] && oldStorages[flag].length > 0) {
147 candidateFlags.push(flag);
148 // The next bit is somewhat convoluted because we need to capture flag variable
149 // see http://stackoverflow.com/questions/12930272/javascript-closures-vs-anonymous-functions
150 // for full explanation of closures in JS
151 var newButton = {
152 "text": flagToButtonText[flag],
153 "color": UbuntuColors.red,
154 "objectName": "removeAllOldSessionsButton",
155 "onClicked": (function(flag) {
156 return function() {
157 console.log("Removing sessions marked with '"+ flag + "' flag");
158 sessionGarbageCollect([flag], resumeWithProvidersDir);
159 }
160
161 })(flag)};
162 totalCandidatesCount += oldStorages[flag].length;
163 buttons.push(newButton);
164 }
165 }
166 if (buttons.length > 1) {
167 //there is more than one button, so append 'remove all' button
168 buttons.push({
169 "text": i18n.tr("Remove all old sessions"),
170 "color": UbuntuColors.red,
171 "onClicked": function() {
172 sessionGarbageCollect(candidateFlags, resumeWithProvidersDir);
173 }
174 });
175 }
176 if (buttons.length > 0) {
177 buttons.push({
178 "text": i18n.tr("Do not remove sessions"),
179 "color": UbuntuColors.green,
180 "objectName": "doNotRemoveButton",
181 "onClicked": function() {
182 sessionGarbageCollect([], resumeWithProvidersDir);
183 }
184 });
185 CbtDialogLogic.showDialog(mainView, i18n.tr("Do you want to clear old sessions storage?\n"
186 + " Number of sessions qualifying for removal: ") +
187 totalCandidatesCount, buttons);
188 } else {
189 sessionGarbageCollect([], resumeWithProvidersDir);
190 }
191 });
135 }192 }
136 onSessionReady: {193 onSessionReady: {
137 welcomePage.enableButton()194 welcomePage.enableButton()
138195
=== modified file 'checkbox-touch/components/CheckboxTouchApplication.qml'
--- checkbox-touch/components/CheckboxTouchApplication.qml 2015-05-27 06:51:23 +0000
+++ checkbox-touch/components/CheckboxTouchApplication.qml 2015-05-29 11:12:57 +0000
@@ -85,6 +85,18 @@
85 });85 });
86 }86 }
8787
88 function getGarbageCollectionCandidates(continuation) {
89 request("get_garbage_collection_candidates", [], continuation, function(error) {
90 console.error("Unable to get garbage collection candidates");
91 });
92 }
93
94 function sessionGarbageCollect(flags, continuation) {
95 request("session_garbage_collect", [flags], continuation, function(error) {
96 console.error("Unable to run session garbage collection");
97 });
98 }
99
88 function getTestplans(continuation) {100 function getTestplans(continuation) {
89 request("get_testplans", [], continuation, function(error) {101 request("get_testplans", [], continuation, function(error) {
90 console.error("Unable to get testplans");102 console.error("Unable to get testplans");
91103
=== modified file 'checkbox-touch/py/checkbox_touch.py'
--- checkbox-touch/py/checkbox_touch.py 2015-05-28 10:04:37 +0000
+++ checkbox-touch/py/checkbox_touch.py 2015-05-29 11:12:57 +0000
@@ -262,6 +262,7 @@
262 self.desired_test_ids = frozenset()262 self.desired_test_ids = frozenset()
263 self.test_plan_id = ""263 self.test_plan_id = ""
264 self.resume_candidate_storage = None264 self.resume_candidate_storage = None
265 self._garbage_collection_candidates = None
265 self.session_storage_repo = None266 self.session_storage_repo = None
266 self.timestamp = datetime.datetime.utcnow().isoformat()267 self.timestamp = datetime.datetime.utcnow().isoformat()
267 self.config = PlainBoxConfig()268 self.config = PlainBoxConfig()
@@ -408,6 +409,55 @@
408 }409 }
409410
410 @view411 @view
412 def get_garbage_collection_candidates(self):
413 """
414 Returns a dict keyed by flag type with value being list of ids of all
415 sessions available in the storage.
416 """
417 # every session *should* have at least one flag set (altough logically
418 # those flags are mutally exclusive
419 self._init_session_storage_repo()
420 self._garbage_collection_candidates = collections.defaultdict(list)
421 for storage in self.session_storage_repo.get_storage_list():
422 data = storage.load_checkpoint()
423 if len(data) == 0:
424 continue
425 try:
426 metadata = SessionPeekHelper().peek(data)
427 if (metadata.app_id == 'checkbox-touch'):
428 _logger.debug(_("Found an existing session storage: %s"),
429 storage.id)
430 # some sessions might be appended to more than one list
431 for flag in metadata.flags:
432 self._garbage_collection_candidates[flag].append(
433 storage)
434 except SessionResumeError as exc:
435 _logger.info(_("Exception raised when trying to gather garbage"
436 " collection candidates: %s"), str(exc))
437 return { flag: [storage.id for storage in storages] for
438 flag, storages in self._garbage_collection_candidates.items()}
439 @view
440 def session_garbage_collect(self, flags):
441 """
442 Removes all session with any flag matching ``flags`` or having
443 'bootstrapping' flag set.
444 """
445 if self._garbage_collection_candidates is None:
446 _logger.warning(_("session_garbage_collect called before "
447 "get_garbage_collection_candidates"))
448 return
449 # if there are candidates with 'bootstrapping', remove them
450 if 'bootstrapping' in self._garbage_collection_candidates.keys():
451 flags.append('bootstrapping')
452 # we build a set of sessions marked for removal so we don't try to
453 # re-remove any of them
454 to_remove = set()
455 for flag in flags:
456 to_remove |= set(self._garbage_collection_candidates[flag])
457 while to_remove:
458 to_remove.pop().remove()
459
460 @view
411 def get_testplans(self):461 def get_testplans(self):
412 all_units = self.manager.default_device_context.unit_list462 all_units = self.manager.default_device_context.unit_list
413 return {463 return {
@@ -519,6 +569,7 @@
519 self.context.state.update_desired_job_list(desired_job_list)569 self.context.state.update_desired_job_list(desired_job_list)
520 _logger.info("Run job list: %s", self.context.state.run_list)570 _logger.info("Run job list: %s", self.context.state.run_list)
521 self.context.state.metadata.flags.add('incomplete')571 self.context.state.metadata.flags.add('incomplete')
572 self.context.state.metadata.flags.remove('bootstrapping')
522 self._checkpoint()573 self._checkpoint()
523574
524 @view575 @view
@@ -627,6 +678,8 @@
627 output_file = os.path.join(dirname, filename)678 output_file = os.path.join(dirname, filename)
628 with open(output_file, 'wb') as stream:679 with open(output_file, 'wb') as stream:
629 self._export_session_to_stream(output_format, option_list, stream)680 self._export_session_to_stream(output_format, option_list, stream)
681 self.context.state.metadata.flags.add(SessionMetaData.FLAG_SUBMITTED)
682 self._checkpoint()
630 return output_file683 return output_file
631684
632 def _get_user_directory_documents(self):685 def _get_user_directory_documents(self):
633686
=== modified file 'checkbox-touch/tests/autopilot/checkbox_touch/__init__.py'
--- checkbox-touch/tests/autopilot/checkbox_touch/__init__.py 2015-05-07 13:39:27 +0000
+++ checkbox-touch/tests/autopilot/checkbox_touch/__init__.py 2015-05-29 11:12:57 +0000
@@ -33,6 +33,15 @@
33 self.launch_application()33 self.launch_application()
34 self.assertThat(self.main_view.visible, Eventually(Equals(True)))34 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
3535
36 def skipSessionRemoval(self):
37 """Answers 'do not remove' on session removal popup if it's shown."""
38 try:
39 do_not_remove_btn = self.app.wait_select_single(
40 objectName='doNotRemoveButton', visible=True)
41 self.pointing_device.click_object(do_not_remove_btn)
42 except StateNotFoundError:
43 pass
44
36 def skipResumeIfShown(self):45 def skipResumeIfShown(self):
37 """Skip restart screen if presented."""46 """Skip restart screen if presented."""
38 # this doesn't use 'long wait' helper, because a 'welcome page' may be47 # this doesn't use 'long wait' helper, because a 'welcome page' may be
@@ -56,6 +65,7 @@
56 pass65 pass
5766
58 def start_and_select_tests(self, category_id, job_ids):67 def start_and_select_tests(self, category_id, job_ids):
68 self.skipSessionRemoval()
59 self.skipResumeIfShown()69 self.skipResumeIfShown()
60 welcome_page = self.long_wait_select_single(70 welcome_page = self.long_wait_select_single(
61 self.app, objectName='welcomePage', state='loaded')71 self.app, objectName='welcomePage', state='loaded')
@@ -83,6 +93,16 @@
83 objectName='continueButton')93 objectName='continueButton')
84 self.pointing_device.click_object(continue_btn)94 self.pointing_device.click_object(continue_btn)
8595
96 def select_two_tests_and_quit(self):
97 self.start_and_select_tests(
98 '2015.com.canonical.certification::normal', [
99 '2015.com.canonical.certification::autopilot/user-verify-1',
100 '2015.com.canonical.certification::autopilot/user-verify-2'])
101 # make sure that test is shown (therefore session has been started)
102 self.app.wait_select_single(
103 objectName='userInteractVerifyIntroPage', visible=True)
104 self.app.process.terminate()
105
86 def launch_application(self):106 def launch_application(self):
87 if platform.model() == 'Desktop':107 if platform.model() == 'Desktop':
88 self._launch_application_from_desktop()108 self._launch_application_from_desktop()
89109
=== modified file 'checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py'
--- checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-05-07 13:43:07 +0000
+++ checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-05-29 11:12:57 +0000
@@ -57,6 +57,7 @@
57 The tests that have outcome determined automatically should come57 The tests that have outcome determined automatically should come
58 in two flavours; one that passes and one that fails.58 in two flavours; one that passes and one that fails.
59 """59 """
60 self.skipSessionRemoval()
60 self.skipResumeIfShown()61 self.skipResumeIfShown()
61 welcome_page = self.long_wait_select_single(62 welcome_page = self.long_wait_select_single(
62 self.app, objectName='welcomePage', state='loaded')63 self.app, objectName='welcomePage', state='loaded')
@@ -121,19 +122,10 @@
121122
122123
123class SessionResumeTests(checkbox_touch.ClickAppTestCase):124class SessionResumeTests(checkbox_touch.ClickAppTestCase):
124 def select_two_tests_and_quit(self):
125 self.start_and_select_tests(
126 '2015.com.canonical.certification::normal', [
127 '2015.com.canonical.certification::autopilot/user-verify-1',
128 '2015.com.canonical.certification::autopilot/user-verify-2'])
129 # make sure that test is shown (therefore session has been started)
130 self.app.wait_select_single(
131 objectName='userInteractVerifyIntroPage', visible=True)
132 self.app.process.terminate()
133
134 def test_rerun_after_resume(self):125 def test_rerun_after_resume(self):
135 self.select_two_tests_and_quit()126 self.select_two_tests_and_quit()
136 self.launch_application()127 self.launch_application()
128 self.skipSessionRemoval()
137 self.assertThat(self.main_view.visible, Eventually(Equals(True)))129 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
138 # not doing long-wait, as the app was recently launched and it130 # not doing long-wait, as the app was recently launched and it
139 # *shouldn't* take long to relaunch it131 # *shouldn't* take long to relaunch it
@@ -152,6 +144,7 @@
152 def test_continue_after_resume(self):144 def test_continue_after_resume(self):
153 self.select_two_tests_and_quit()145 self.select_two_tests_and_quit()
154 self.launch_application()146 self.launch_application()
147 self.skipSessionRemoval()
155 self.assertThat(self.main_view.visible, Eventually(Equals(True)))148 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
156 resume_page = self.app.wait_select_single(149 resume_page = self.app.wait_select_single(
157 objectName='resumeSessionPage', visible=True)150 objectName='resumeSessionPage', visible=True)
@@ -168,6 +161,7 @@
168 def test_restart_after_resume(self):161 def test_restart_after_resume(self):
169 self.select_two_tests_and_quit()162 self.select_two_tests_and_quit()
170 self.launch_application()163 self.launch_application()
164 self.skipSessionRemoval()
171 self.assertThat(self.main_view.visible, Eventually(Equals(True)))165 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
172 resume_page = self.app.wait_select_single(166 resume_page = self.app.wait_select_single(
173 objectName='resumeSessionPage', visible=True)167 objectName='resumeSessionPage', visible=True)
@@ -177,3 +171,46 @@
177 welcome_page = self.app.wait_select_single(171 welcome_page = self.app.wait_select_single(
178 objectName='welcomePage')172 objectName='welcomePage')
179 self.assertThat(welcome_page.visible, Eventually(Equals(True)))173 self.assertThat(welcome_page.visible, Eventually(Equals(True)))
174
175
176class GarbageCollectionTests(checkbox_touch.ClickAppTestCase):
177 def test_garbage_collection_popup_shown(self):
178 """Ensure popup is shown when there are candidates for removal."""
179 # start a session, then quit the app, to create dangling incomplete
180 # session
181 self.select_two_tests_and_quit()
182 self.launch_application()
183 # check if the button from session garbage collect popup is visible
184 self.app.wait_select_single(
185 objectName="removeAllOldSessionsButton", visible=True)
186
187 def test_do_nor_remove_doesnt_remove(self):
188 self.select_two_tests_and_quit()
189 self.launch_application()
190 do_not_remove_btn = self.app.wait_select_single(
191 objectName="doNotRemoveButton", visible=True)
192 self.pointing_device.click_object(do_not_remove_btn)
193 # make sure the app behaves normally afterwards
194 self.skipResumeIfShown()
195 # quit the app
196 self.app.process.terminate()
197 self.launch_application()
198 # make sure the app still wants to garbage collect some sessions
199 # check if the button from session garbage collect popup is visible
200 self.app.wait_select_single(
201 objectName="removeAllOldSessionsButton", visible=True)
202
203 def test_remove_all_removes(self):
204 self.select_two_tests_and_quit()
205 self.launch_application()
206 remove_all_btn = self.app.wait_select_single(
207 objectName="removeAllOldSessionsButton", visible=True)
208 self.pointing_device.click_object(remove_all_btn)
209 # make sure the app behaves normally afterwards
210 self.skipResumeIfShown()
211 # quit the app
212 self.app.process.terminate()
213 self.launch_application()
214 # make sure that app doens't show garbage collection dialog
215 self.app.wait_select_single(
216 objectName='startTestButton', enabled=True)

Subscribers

People subscribed via source and target branches