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
1=== modified file 'checkbox-touch/checkbox-touch.qml'
2--- checkbox-touch/checkbox-touch.qml 2015-05-27 06:51:23 +0000
3+++ checkbox-touch/checkbox-touch.qml 2015-05-29 11:12:57 +0000
4@@ -131,7 +131,64 @@
5 "checkbox_touch" : applicationVersion,
6 "plainbox" : plainboxVersion
7 };
8- resumeOrStartSession(appSettings["providersDir"]);
9+ var resumeWithProvidersDir = function() {
10+ resumeOrStartSession(appSettings["providersDir"]);
11+ };
12+ getGarbageCollectionCandidates(function(oldStorages) {
13+ var buttons = [];
14+ var flagToButtonText = {
15+ "incomplete": i18n.tr("Remove incomplete sessions"),
16+ "submitted": i18n.tr("Remove submitted sessions"),
17+ }
18+ var candidateFlags = [];
19+ var totalCandidatesCount = 0;
20+ for (var flag in flagToButtonText) {
21+ if (oldStorages[flag] && oldStorages[flag].length > 0) {
22+ candidateFlags.push(flag);
23+ // The next bit is somewhat convoluted because we need to capture flag variable
24+ // see http://stackoverflow.com/questions/12930272/javascript-closures-vs-anonymous-functions
25+ // for full explanation of closures in JS
26+ var newButton = {
27+ "text": flagToButtonText[flag],
28+ "color": UbuntuColors.red,
29+ "objectName": "removeAllOldSessionsButton",
30+ "onClicked": (function(flag) {
31+ return function() {
32+ console.log("Removing sessions marked with '"+ flag + "' flag");
33+ sessionGarbageCollect([flag], resumeWithProvidersDir);
34+ }
35+
36+ })(flag)};
37+ totalCandidatesCount += oldStorages[flag].length;
38+ buttons.push(newButton);
39+ }
40+ }
41+ if (buttons.length > 1) {
42+ //there is more than one button, so append 'remove all' button
43+ buttons.push({
44+ "text": i18n.tr("Remove all old sessions"),
45+ "color": UbuntuColors.red,
46+ "onClicked": function() {
47+ sessionGarbageCollect(candidateFlags, resumeWithProvidersDir);
48+ }
49+ });
50+ }
51+ if (buttons.length > 0) {
52+ buttons.push({
53+ "text": i18n.tr("Do not remove sessions"),
54+ "color": UbuntuColors.green,
55+ "objectName": "doNotRemoveButton",
56+ "onClicked": function() {
57+ sessionGarbageCollect([], resumeWithProvidersDir);
58+ }
59+ });
60+ CbtDialogLogic.showDialog(mainView, i18n.tr("Do you want to clear old sessions storage?\n"
61+ + " Number of sessions qualifying for removal: ") +
62+ totalCandidatesCount, buttons);
63+ } else {
64+ sessionGarbageCollect([], resumeWithProvidersDir);
65+ }
66+ });
67 }
68 onSessionReady: {
69 welcomePage.enableButton()
70
71=== modified file 'checkbox-touch/components/CheckboxTouchApplication.qml'
72--- checkbox-touch/components/CheckboxTouchApplication.qml 2015-05-27 06:51:23 +0000
73+++ checkbox-touch/components/CheckboxTouchApplication.qml 2015-05-29 11:12:57 +0000
74@@ -85,6 +85,18 @@
75 });
76 }
77
78+ function getGarbageCollectionCandidates(continuation) {
79+ request("get_garbage_collection_candidates", [], continuation, function(error) {
80+ console.error("Unable to get garbage collection candidates");
81+ });
82+ }
83+
84+ function sessionGarbageCollect(flags, continuation) {
85+ request("session_garbage_collect", [flags], continuation, function(error) {
86+ console.error("Unable to run session garbage collection");
87+ });
88+ }
89+
90 function getTestplans(continuation) {
91 request("get_testplans", [], continuation, function(error) {
92 console.error("Unable to get testplans");
93
94=== modified file 'checkbox-touch/py/checkbox_touch.py'
95--- checkbox-touch/py/checkbox_touch.py 2015-05-28 10:04:37 +0000
96+++ checkbox-touch/py/checkbox_touch.py 2015-05-29 11:12:57 +0000
97@@ -262,6 +262,7 @@
98 self.desired_test_ids = frozenset()
99 self.test_plan_id = ""
100 self.resume_candidate_storage = None
101+ self._garbage_collection_candidates = None
102 self.session_storage_repo = None
103 self.timestamp = datetime.datetime.utcnow().isoformat()
104 self.config = PlainBoxConfig()
105@@ -408,6 +409,55 @@
106 }
107
108 @view
109+ def get_garbage_collection_candidates(self):
110+ """
111+ Returns a dict keyed by flag type with value being list of ids of all
112+ sessions available in the storage.
113+ """
114+ # every session *should* have at least one flag set (altough logically
115+ # those flags are mutally exclusive
116+ self._init_session_storage_repo()
117+ self._garbage_collection_candidates = collections.defaultdict(list)
118+ for storage in self.session_storage_repo.get_storage_list():
119+ data = storage.load_checkpoint()
120+ if len(data) == 0:
121+ continue
122+ try:
123+ metadata = SessionPeekHelper().peek(data)
124+ if (metadata.app_id == 'checkbox-touch'):
125+ _logger.debug(_("Found an existing session storage: %s"),
126+ storage.id)
127+ # some sessions might be appended to more than one list
128+ for flag in metadata.flags:
129+ self._garbage_collection_candidates[flag].append(
130+ storage)
131+ except SessionResumeError as exc:
132+ _logger.info(_("Exception raised when trying to gather garbage"
133+ " collection candidates: %s"), str(exc))
134+ return { flag: [storage.id for storage in storages] for
135+ flag, storages in self._garbage_collection_candidates.items()}
136+ @view
137+ def session_garbage_collect(self, flags):
138+ """
139+ Removes all session with any flag matching ``flags`` or having
140+ 'bootstrapping' flag set.
141+ """
142+ if self._garbage_collection_candidates is None:
143+ _logger.warning(_("session_garbage_collect called before "
144+ "get_garbage_collection_candidates"))
145+ return
146+ # if there are candidates with 'bootstrapping', remove them
147+ if 'bootstrapping' in self._garbage_collection_candidates.keys():
148+ flags.append('bootstrapping')
149+ # we build a set of sessions marked for removal so we don't try to
150+ # re-remove any of them
151+ to_remove = set()
152+ for flag in flags:
153+ to_remove |= set(self._garbage_collection_candidates[flag])
154+ while to_remove:
155+ to_remove.pop().remove()
156+
157+ @view
158 def get_testplans(self):
159 all_units = self.manager.default_device_context.unit_list
160 return {
161@@ -519,6 +569,7 @@
162 self.context.state.update_desired_job_list(desired_job_list)
163 _logger.info("Run job list: %s", self.context.state.run_list)
164 self.context.state.metadata.flags.add('incomplete')
165+ self.context.state.metadata.flags.remove('bootstrapping')
166 self._checkpoint()
167
168 @view
169@@ -627,6 +678,8 @@
170 output_file = os.path.join(dirname, filename)
171 with open(output_file, 'wb') as stream:
172 self._export_session_to_stream(output_format, option_list, stream)
173+ self.context.state.metadata.flags.add(SessionMetaData.FLAG_SUBMITTED)
174+ self._checkpoint()
175 return output_file
176
177 def _get_user_directory_documents(self):
178
179=== modified file 'checkbox-touch/tests/autopilot/checkbox_touch/__init__.py'
180--- checkbox-touch/tests/autopilot/checkbox_touch/__init__.py 2015-05-07 13:39:27 +0000
181+++ checkbox-touch/tests/autopilot/checkbox_touch/__init__.py 2015-05-29 11:12:57 +0000
182@@ -33,6 +33,15 @@
183 self.launch_application()
184 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
185
186+ def skipSessionRemoval(self):
187+ """Answers 'do not remove' on session removal popup if it's shown."""
188+ try:
189+ do_not_remove_btn = self.app.wait_select_single(
190+ objectName='doNotRemoveButton', visible=True)
191+ self.pointing_device.click_object(do_not_remove_btn)
192+ except StateNotFoundError:
193+ pass
194+
195 def skipResumeIfShown(self):
196 """Skip restart screen if presented."""
197 # this doesn't use 'long wait' helper, because a 'welcome page' may be
198@@ -56,6 +65,7 @@
199 pass
200
201 def start_and_select_tests(self, category_id, job_ids):
202+ self.skipSessionRemoval()
203 self.skipResumeIfShown()
204 welcome_page = self.long_wait_select_single(
205 self.app, objectName='welcomePage', state='loaded')
206@@ -83,6 +93,16 @@
207 objectName='continueButton')
208 self.pointing_device.click_object(continue_btn)
209
210+ def select_two_tests_and_quit(self):
211+ self.start_and_select_tests(
212+ '2015.com.canonical.certification::normal', [
213+ '2015.com.canonical.certification::autopilot/user-verify-1',
214+ '2015.com.canonical.certification::autopilot/user-verify-2'])
215+ # make sure that test is shown (therefore session has been started)
216+ self.app.wait_select_single(
217+ objectName='userInteractVerifyIntroPage', visible=True)
218+ self.app.process.terminate()
219+
220 def launch_application(self):
221 if platform.model() == 'Desktop':
222 self._launch_application_from_desktop()
223
224=== modified file 'checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py'
225--- checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-05-07 13:43:07 +0000
226+++ checkbox-touch/tests/autopilot/checkbox_touch/test_checkbox_touch.py 2015-05-29 11:12:57 +0000
227@@ -57,6 +57,7 @@
228 The tests that have outcome determined automatically should come
229 in two flavours; one that passes and one that fails.
230 """
231+ self.skipSessionRemoval()
232 self.skipResumeIfShown()
233 welcome_page = self.long_wait_select_single(
234 self.app, objectName='welcomePage', state='loaded')
235@@ -121,19 +122,10 @@
236
237
238 class SessionResumeTests(checkbox_touch.ClickAppTestCase):
239- def select_two_tests_and_quit(self):
240- self.start_and_select_tests(
241- '2015.com.canonical.certification::normal', [
242- '2015.com.canonical.certification::autopilot/user-verify-1',
243- '2015.com.canonical.certification::autopilot/user-verify-2'])
244- # make sure that test is shown (therefore session has been started)
245- self.app.wait_select_single(
246- objectName='userInteractVerifyIntroPage', visible=True)
247- self.app.process.terminate()
248-
249 def test_rerun_after_resume(self):
250 self.select_two_tests_and_quit()
251 self.launch_application()
252+ self.skipSessionRemoval()
253 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
254 # not doing long-wait, as the app was recently launched and it
255 # *shouldn't* take long to relaunch it
256@@ -152,6 +144,7 @@
257 def test_continue_after_resume(self):
258 self.select_two_tests_and_quit()
259 self.launch_application()
260+ self.skipSessionRemoval()
261 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
262 resume_page = self.app.wait_select_single(
263 objectName='resumeSessionPage', visible=True)
264@@ -168,6 +161,7 @@
265 def test_restart_after_resume(self):
266 self.select_two_tests_and_quit()
267 self.launch_application()
268+ self.skipSessionRemoval()
269 self.assertThat(self.main_view.visible, Eventually(Equals(True)))
270 resume_page = self.app.wait_select_single(
271 objectName='resumeSessionPage', visible=True)
272@@ -177,3 +171,46 @@
273 welcome_page = self.app.wait_select_single(
274 objectName='welcomePage')
275 self.assertThat(welcome_page.visible, Eventually(Equals(True)))
276+
277+
278+class GarbageCollectionTests(checkbox_touch.ClickAppTestCase):
279+ def test_garbage_collection_popup_shown(self):
280+ """Ensure popup is shown when there are candidates for removal."""
281+ # start a session, then quit the app, to create dangling incomplete
282+ # session
283+ self.select_two_tests_and_quit()
284+ self.launch_application()
285+ # check if the button from session garbage collect popup is visible
286+ self.app.wait_select_single(
287+ objectName="removeAllOldSessionsButton", visible=True)
288+
289+ def test_do_nor_remove_doesnt_remove(self):
290+ self.select_two_tests_and_quit()
291+ self.launch_application()
292+ do_not_remove_btn = self.app.wait_select_single(
293+ objectName="doNotRemoveButton", visible=True)
294+ self.pointing_device.click_object(do_not_remove_btn)
295+ # make sure the app behaves normally afterwards
296+ self.skipResumeIfShown()
297+ # quit the app
298+ self.app.process.terminate()
299+ self.launch_application()
300+ # make sure the app still wants to garbage collect some sessions
301+ # check if the button from session garbage collect popup is visible
302+ self.app.wait_select_single(
303+ objectName="removeAllOldSessionsButton", visible=True)
304+
305+ def test_remove_all_removes(self):
306+ self.select_two_tests_and_quit()
307+ self.launch_application()
308+ remove_all_btn = self.app.wait_select_single(
309+ objectName="removeAllOldSessionsButton", visible=True)
310+ self.pointing_device.click_object(remove_all_btn)
311+ # make sure the app behaves normally afterwards
312+ self.skipResumeIfShown()
313+ # quit the app
314+ self.app.process.terminate()
315+ self.launch_application()
316+ # make sure that app doens't show garbage collection dialog
317+ self.app.wait_select_single(
318+ objectName='startTestButton', enabled=True)

Subscribers

People subscribed via source and target branches