Merge lp:~chipaca/ubuntu-system-settings/push-helper-to-spec into lp:ubuntu-system-settings
- push-helper-to-spec
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Sebastien Bacher |
Approved revision: | 1034 |
Merged at revision: | 1047 |
Proposed branch: | lp:~chipaca/ubuntu-system-settings/push-helper-to-spec |
Merge into: | lp:ubuntu-system-settings |
Diff against target: |
417 lines (+264/-75) 3 files modified
CMakeLists.txt (+1/-1) push-helper/software_updates_helper.py (+149/-64) tests/test_push_helper.py.in (+114/-10) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-system-settings/push-helper-to-spec |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Sebastien Bacher (community) | Approve | ||
Ken VanDine | Needs Fixing | ||
Roberto Alsina (community) | Approve | ||
Review via email: mp+234311@code.launchpad.net |
Commit message
Take the system settings push helper closer to implementing the spec at https:/
Description of the change
This brings the push helper for system settings closer to implementing https:/
Missing still is the bit about click package updates counter. Also the emblem doesn't appear everywhere the icon for system settings is, so the fallback to a notification in the software centre exists instead.
Roberto Alsina (ralsina) : | # |
Roberto Alsina (ralsina) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1031
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
None: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1033
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Sebastien Bacher (seb128) wrote : | # |
Thanks, does that address bug #1363972 (notification should replace previous ones)? if not could that be done in the same changeset?
could you also summarize what should change compared to the previous version, from an user perspective, so we can confirm that the update works as it should?
Sebastien Bacher (seb128) wrote : | # |
oh, also the title is confusing since it suggest you can tap anywhere on the entry where it's not the case, see bug #1366288 (not a new issue but I want to point it out while we refactor/update to match design)
John Lenton (chipaca) wrote : | # |
> Thanks, does that address bug #1363972 (notification should replace previous
> ones)? if not could that be done in the same changeset?
yes, it fixes that bug. I'll tie the bug to the branch now.
> could you also summarize what should change compared to the previous version,
> from an user perspective, so we can confirm that the update works as it
> should?
yes. Before, when a broadcast notification came in (or if you faked it with gdbus as per roberto's suggestion) you'd get a notification immediately every time, and they'd stack up.
Now, you only get a notification immediately if you have turned off auto downloads; otherwise, you'll get a notification after it's been downloaded.
John Lenton (chipaca) wrote : | # |
> oh, also the title is confusing since it suggest you can tap anywhere on the
> entry where it's not the case, see bug #1366288 (not a new issue but I want to
> point it out while we refactor/update to match design)
as far as i know i'm per design on this, if not please point me at it?
the tap-to-
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1034
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : | # |
Looks good, it does properly supersede the notification instead of stacking with normal use. You can trigger a race if you make several dbus calls to simulate the notification, it'll still give you multiple notifications. But that's a corner case, the postal service shouldn't do that.
Ted Gould (ted) wrote : | # |
This relies on a bug in lifecycle confinement that will be fixed shortly:
https:/
At that point all children will also have the same lifecycle as their parents.
Ken VanDine (ken-vandine) wrote : | # |
I dropped the top approval based on ted's comment, I'd rather not just end up with this broken next week and have to scramble to get a fix.
John Lenton (chipaca) wrote : | # |
The system settings push helper is not an untrusted helper.
John Lenton (chipaca) wrote : | # |
That is: the system settings push helper is not an untrusted helper, so it does not run confined. It is not launched by ubuntu-app-launch. That bug and its fix does not apply; you can't have untrusted helpers for "legacy" applications, so for legacy applications (which are trusted) we just do an exec "old style".
Ted Gould (ted) wrote : | # |
Legacy applications can have untrusted helpers you just need to have an AppArmor profile or set them explicitly to unconfined. I'd recommend that they have an AppArmor profile as there are dpkg tools that make that relatively easy to maintain.
I'm really surprised that there's two separate mechanisms to run helpers, but I guess that does make this MR safe, so I retract my objection.
John Lenton (chipaca) wrote : | # |
> Legacy applications can have untrusted helpers you just need to have an
> AppArmor profile or set them explicitly to unconfined. I'd recommend that they
> have an AppArmor profile as there are dpkg tools that make that relatively
> easy to maintain.
Hm! I'm pretty sure this failed in interesting ways when I tested it. I've just tested it again and it fails in a sane way that looks suspiciously like a missing apparmor profile. I guess we can revisit this split then (not now though).
I'll catch you on IRC to go over whether it'll possible to implement the design for update notifications without escaping the lifecycle nor special-casing system settings.
> I'm really surprised that there's two separate mechanisms to run helpers,
yeah, i wasn't too happy having to write that code either. Not having to maintain it long term would be good.
> but
> I guess that does make this MR safe, so I retract my objection.
ok
Sebastien Bacher (seb128) wrote : | # |
since the issue got resolved setting back to approved, we can land it ;-)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1034
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-08-05 10:12:14 +0000 |
3 | +++ CMakeLists.txt 2014-09-11 20:53:25 +0000 |
4 | @@ -76,7 +76,7 @@ |
5 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ubuntu-system-settings.desktop DESTINATION share/applications) |
6 | install(FILES ubuntu-system-settings.url-dispatcher DESTINATION share/url-dispatcher/urls) |
7 | install(FILES screenshot.png DESTINATION ${SETTINGS_SHARE_DIR}) |
8 | -install(PROGRAMS push-helper/software-updates-helper.py DESTINATION ${PUSH_HELPER_DIR} RENAME ubuntu-system-settings) |
9 | +install(PROGRAMS push-helper/software_updates_helper.py DESTINATION ${PUSH_HELPER_DIR} RENAME ubuntu-system-settings) |
10 | |
11 | if(cmake_build_type_lower MATCHES coverage) |
12 | ENABLE_COVERAGE_REPORT(TARGETS system-settings FILTER /usr/include ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/*) |
13 | |
14 | === renamed file 'push-helper/software-updates-helper.py' => 'push-helper/software_updates_helper.py' |
15 | --- push-helper/software-updates-helper.py 2014-08-28 00:45:29 +0000 |
16 | +++ push-helper/software_updates_helper.py 2014-09-11 20:53:25 +0000 |
17 | @@ -16,74 +16,159 @@ |
18 | # 2. yes, this is rather convoluted. Most push helpers don't have to deal with |
19 | # this stuff. |
20 | |
21 | +import os |
22 | import json |
23 | import sys |
24 | import time |
25 | import gettext |
26 | |
27 | -if len(sys.argv) != 3: |
28 | - print("File in and out expected via argv", file=sys.stderr) |
29 | - sys.exit(1) |
30 | - |
31 | -f1, f2 = sys.argv[1:3] |
32 | _ = gettext.translation('ubuntu-system-settings', fallback=True).gettext |
33 | |
34 | -# here you should look at the input (the contents of the file whose |
35 | -# name is in f1, which are guaranteed to be json). If it's a broadcast |
36 | -# it will be the most recent we've received, and will have passed a |
37 | -# minimum amount of sanity checking, but you can probably do more. As |
38 | -# per the design on https://wiki.ubuntu.com/SoftwareUpdates#Prompting |
39 | -# if things are set to auto-download you should go download them (in a |
40 | -# child process -- this helper process itself has 4 more seconds to |
41 | -# live). |
42 | -# |
43 | -# the broadcast payload will be a single json object looking like |
44 | -# { image-channel/device-model": [build-number, channel-alias]} |
45 | -# |
46 | -# e.g., |
47 | -# |
48 | -# {"ubuntu-touch/utopic-proposed/hammerhead":[265,""]} |
49 | -# |
50 | -# |
51 | -# When the click server starts sending notifications of packages a user can |
52 | -# update, you should probably describe that payload here. What to do with it |
53 | -# is described in some detail in the wiki page above. |
54 | -# |
55 | -# |
56 | -# Once you've downloaded things and need to actually notify the user, you'd |
57 | -# send a notification as below, over dbus to Post. That's the third payload |
58 | -# this script will be called with; for that case you'd just pass the payload |
59 | -# through. |
60 | -# |
61 | -# For cases when you don't want to notify the user (yet), the correct |
62 | -# output (to be written to a file whose name is f2) is “{}”, i.e. an |
63 | -# empty json object. |
64 | -# |
65 | -# For now, this script assumes everything that comes in is a valid |
66 | -# broadcast notification, and notifies the user directly: |
67 | - |
68 | - |
69 | -icon = "/usr/share/ubuntu/settings/system/icons/settings-system-update.svg" |
70 | -obj = { |
71 | - "notification": { |
72 | - "emblem-counter": { |
73 | - "count": 1, |
74 | - "visible": True, |
75 | - }, |
76 | - "vibrate": { |
77 | - "pattern": [50, 150], |
78 | - "repeat": 3, |
79 | - }, |
80 | - "card": { |
81 | - "summary": _("There's an updated system image."), |
82 | - "body": _("Tap to open the system updater."), |
83 | - "actions": ["settings:///system/system-update"], |
84 | - "icon": icon, |
85 | - "timestamp": int(time.time()), |
86 | - "persist": True, |
87 | - "popup": True, |
88 | - }, |
89 | - }, |
90 | -} |
91 | - |
92 | -json.dump(obj, open(f2, "w")) |
93 | +SYS_UPDATE = "system-image-update" |
94 | + |
95 | + |
96 | +class SystemImage: |
97 | + def __init__(self): |
98 | + self.loop = None |
99 | + self.sysimg = None |
100 | + self.postal = None |
101 | + self.notify = False |
102 | + |
103 | + def setup(self): |
104 | + import dbus |
105 | + from dbus.mainloop.glib import DBusGMainLoop |
106 | + from gi.repository import GLib |
107 | + |
108 | + gloop = DBusGMainLoop() |
109 | + sys_bus = dbus.SystemBus(mainloop=gloop) |
110 | + ses_bus = dbus.SessionBus(mainloop=gloop) |
111 | + self.loop = GLib.MainLoop() |
112 | + self.sysimg = dbus.Interface( |
113 | + sys_bus.get_object("com.canonical.SystemImage", "/Service"), |
114 | + dbus_interface="com.canonical.SystemImage") |
115 | + self.postal = dbus.Interface( |
116 | + ses_bus.get_object("com.ubuntu.Postal", "/com/ubuntu/Postal/_"), |
117 | + dbus_interface="com.ubuntu.Postal") |
118 | + self.notify = False |
119 | + |
120 | + def quit(self): |
121 | + if self.notify: |
122 | + # remove any older notifications about this |
123 | + self.postal.ClearPersistent("_ubuntu-system-settings", SYS_UPDATE) |
124 | + # send ours. This will of course come back to this same script. |
125 | + self.postal.Post("_ubuntu-system-settings", json.dumps(SYS_UPDATE)) |
126 | + self.loop.quit() |
127 | + |
128 | + def available_cb(self, available, downloading, *ignored): |
129 | + if available: |
130 | + if downloading: |
131 | + # handled in the UpdateDownloaded or UpdateFailed handlers |
132 | + return |
133 | + # available and not downloading: auto downloads are turned |
134 | + # off; notify the user right now. |
135 | + self.notify = True |
136 | + else: |
137 | + # if not available, we were called spuriously. No notification. |
138 | + self.notify = False |
139 | + self.quit() |
140 | + |
141 | + def downloaded_cb(self): |
142 | + self.notify = True |
143 | + self.quit() |
144 | + |
145 | + def failed_cb(self, *ignored): |
146 | + # give up |
147 | + self.notify = False |
148 | + self.quit() |
149 | + |
150 | + def run(self): |
151 | + self.sysimg.connect_to_signal("UpdateAvailableStatus", |
152 | + self.available_cb) |
153 | + self.sysimg.connect_to_signal("UpdateDownloaded", self.downloaded_cb) |
154 | + self.sysimg.connect_to_signal("UpdateFailed", self.failed_cb) |
155 | + self.sysimg.CheckForUpdate() |
156 | + self.loop.run() |
157 | + |
158 | + |
159 | +def main(): |
160 | + if len(sys.argv) != 3: |
161 | + print("File in and out expected via argv", file=sys.stderr) |
162 | + sys.exit(1) |
163 | + |
164 | + f1, f2 = sys.argv[1:3] |
165 | + |
166 | + # here you should look at the input (the contents of the file whose |
167 | + # name is in f1, which are guaranteed to be json). If it's a broadcast |
168 | + # it will be the most recent we've received, and will have passed a |
169 | + # minimum amount of sanity checking, but you can probably do more. As |
170 | + # per the design on https://wiki.ubuntu.com/SoftwareUpdates#Prompting |
171 | + # if things are set to auto-download you should go download them (in a |
172 | + # child process -- this helper process itself has 4 more seconds to |
173 | + # live). |
174 | + # |
175 | + # the broadcast payload will be a single json object looking like |
176 | + # { image-channel/device-model": [build-number, channel-alias]} |
177 | + # |
178 | + # e.g., |
179 | + # |
180 | + # {"ubuntu-touch/utopic-proposed/hammerhead":[265,""]} |
181 | + # |
182 | + # |
183 | + # When the click server starts sending notifications of packages a user |
184 | + # can update, you should probably describe that payload here. What to |
185 | + # do with it is described in some detail in the wiki page above. |
186 | + # |
187 | + # |
188 | + # Once you've downloaded things and need to actually notify the user, |
189 | + # you'd send a notification as below, over dbus to Post. That's the |
190 | + # third payload this script will be called with; for that case you'd |
191 | + # just pass the payload through. |
192 | + # |
193 | + # For cases when you don't want to notify the user (yet), the correct |
194 | + # output (to be written to a file whose name is f2) is “{}”, i.e. an |
195 | + # empty json object. |
196 | + |
197 | + with open(f1) as f: |
198 | + arg = json.load(f) |
199 | + |
200 | + obj = {} |
201 | + if arg == "system-image-update": |
202 | + icon = "/usr/share/ubuntu/settings/system/icons/" + \ |
203 | + "settings-system-update.svg" |
204 | + obj = { |
205 | + "notification": { |
206 | + "tag": SYS_UPDATE, |
207 | + "emblem-counter": { |
208 | + "count": 1, |
209 | + "visible": True, |
210 | + }, |
211 | + "vibrate": { |
212 | + "pattern": [50, 150], |
213 | + "repeat": 3, |
214 | + }, |
215 | + "card": { |
216 | + "summary": _("There's an updated system image."), |
217 | + "body": _("Tap to open the system updater."), |
218 | + "actions": ["settings:///system/system-update"], |
219 | + "icon": icon, |
220 | + "timestamp": int(time.time()), |
221 | + "persist": True, |
222 | + }, |
223 | + }, |
224 | + } |
225 | + elif arg == "testing": |
226 | + # for tests |
227 | + obj = {"testing": True} |
228 | + else: |
229 | + # assume it's a broadcast |
230 | + if os.fork() == 0: |
231 | + os.setsid() |
232 | + s = SystemImage() |
233 | + s.setup() |
234 | + s.run() |
235 | + return |
236 | + |
237 | + json.dump(obj, open(f2, "w")) |
238 | + |
239 | +if __name__ == '__main__': |
240 | + main() |
241 | |
242 | === modified file 'tests/test_push_helper.py.in' |
243 | --- tests/test_push_helper.py.in 2014-09-03 14:12:52 +0000 |
244 | +++ tests/test_push_helper.py.in 2014-09-11 20:53:25 +0000 |
245 | @@ -1,5 +1,4 @@ |
246 | -#!/usr/bin/python |
247 | - |
248 | +#!/usr/bin/python3 |
249 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
250 | # Copyright 2014 Canonical |
251 | # |
252 | @@ -14,6 +13,16 @@ |
253 | import sys |
254 | import tempfile |
255 | import unittest |
256 | +from unittest import mock |
257 | + |
258 | +HELPER_DIR = '@CMAKE_CURRENT_SOURCE_DIR@/../push-helper/' |
259 | +sys.path.append(HELPER_DIR) |
260 | +import software_updates_helper |
261 | + |
262 | + |
263 | +class TestingSystemImage(software_updates_helper.SystemImage): |
264 | + def setup(self): |
265 | + pass |
266 | |
267 | |
268 | class PushHelperTests(unittest.TestCase): |
269 | @@ -22,15 +31,14 @@ |
270 | def setUp(self): |
271 | super(PushHelperTests, self).setUp() |
272 | self.tmp_dir = tempfile.mkdtemp(suffix='push-helper', prefix='tests') |
273 | - self.helper_path = '@CMAKE_CURRENT_SOURCE_DIR@/../push-helper/' + \ |
274 | - 'software-updates-helper.py' |
275 | + self.helper_path = HELPER_DIR + 'software_updates_helper.py' |
276 | |
277 | def tearDown(self): |
278 | super(PushHelperTests, self).tearDown() |
279 | shutil.rmtree(self.tmp_dir) |
280 | |
281 | def run_push_helper(self, input_fname, output_fname): |
282 | - subprocess.call([self.helper_path, input_fname, output_fname], |
283 | + subprocess.call(["python3", self.helper_path, input_fname, output_fname], |
284 | stdout=subprocess.PIPE) |
285 | |
286 | def create_input_file(self, filename, content): |
287 | @@ -49,7 +57,7 @@ |
288 | self.assertEqual(card['actions'], ['settings:///system/system-update']) |
289 | self.assertEqual(card['persist'], True) |
290 | self.assertEqual(card['body'], 'Tap to open the system updater.') |
291 | - self.assertEqual(card['popup'], True) |
292 | + self.assertEqual(card.get('popup', False), False) |
293 | emblem_counter = notif['notification']['emblem-counter'] |
294 | self.assertEqual(emblem_counter, {'visible': True, 'count': 1}) |
295 | vibrate = notif['notification']['vibrate'] |
296 | @@ -58,7 +66,7 @@ |
297 | def test_update_broadcast(self): |
298 | """Default system-update broadcast.""" |
299 | input_f = self.create_input_file('bcast_in', |
300 | - '{"daily/mako": [200, ""]}') |
301 | + '"system-image-update"') |
302 | output_f = os.path.join(self.tmp_dir, 'bcast_out') |
303 | self.run_push_helper(input_f, output_f) |
304 | with open(output_f, 'r') as fd: |
305 | @@ -67,13 +75,109 @@ |
306 | |
307 | def test_valid_json(self): |
308 | """Handle a valid json input.""" |
309 | - input_f = self.create_input_file('valid_json_in', '"null"') |
310 | + input_f = self.create_input_file('valid_json_in', '"testing"') |
311 | output_f = os.path.join(self.tmp_dir, 'valid_json_out') |
312 | self.run_push_helper(input_f, output_f) |
313 | with open(output_f, 'r') as fd: |
314 | output = json.load(fd) |
315 | - self.assertSystemUpdateNotification(output) |
316 | - |
317 | + self.assertEqual(output, {"testing": True}) |
318 | + |
319 | + def test_system_image_run(self): |
320 | + """Check that run looks sane""" |
321 | + s = TestingSystemImage() |
322 | + s.sysimg = mock.Mock(name="sysimg") |
323 | + s.loop = mock.Mock(name="loop") |
324 | + s.run() |
325 | + # check the main loop was run |
326 | + s.loop.run.assert_called_once_with() |
327 | + # check CheckForUpdate was called |
328 | + s.sysimg.CheckForUpdate.assert_called_once_with() |
329 | + # and connect_to_signal |
330 | + s.sysimg.connect_to_signal.assert_any_call("UpdateDownloaded", |
331 | + s.downloaded_cb) |
332 | + s.sysimg.connect_to_signal.assert_any_call("UpdateFailed", |
333 | + s.failed_cb) |
334 | + s.sysimg.connect_to_signal.assert_any_call("UpdateAvailableStatus", |
335 | + s.available_cb) |
336 | + self.assertEqual(s.notify, False) |
337 | + |
338 | + def test_available_and_downloading(self): |
339 | + """check that available_cb when available and d'loading just returns""" |
340 | + s = TestingSystemImage() |
341 | + s.quit = mock.Mock(name="quit") |
342 | + |
343 | + self.assertEqual(s.notify, False) |
344 | + # available and downloading; returns without calling quit |
345 | + s.available_cb(True, True) |
346 | + self.assertEqual(s.notify, False) |
347 | + self.assertEqual(s.quit.called, False) |
348 | + |
349 | + def test_available_not_downloading(self): |
350 | + """check that available_cb when available and not downloading |
351 | + sets notify and quits""" |
352 | + s = TestingSystemImage() |
353 | + s.quit = mock.Mock(name="quit") |
354 | + |
355 | + self.assertEqual(s.notify, False) |
356 | + # available and not downloading; quits with notification |
357 | + s.available_cb(True, False) |
358 | + self.assertEqual(s.notify, True) |
359 | + s.quit.assert_called_once_with() |
360 | + |
361 | + def test_not_available(self): |
362 | + """check that available_cb quits when not available""" |
363 | + s = TestingSystemImage() |
364 | + s.quit = mock.Mock(name="quit") |
365 | + |
366 | + self.assertEqual(s.notify, False) |
367 | + # not available; quits without notifying |
368 | + s.available_cb(False, False) |
369 | + self.assertEqual(s.notify, False) |
370 | + s.quit.assert_called_once_with() |
371 | + |
372 | + def test_downloaded_cb(self): |
373 | + """check that on download, notify is set to True and quit is called""" |
374 | + s = TestingSystemImage() |
375 | + s.quit = mock.Mock(name="quit") |
376 | + |
377 | + self.assertEqual(s.notify, False) |
378 | + s.downloaded_cb() |
379 | + self.assertEqual(s.notify, True) |
380 | + s.quit.assert_called_once_with() |
381 | + |
382 | + def test_failed_cb(self): |
383 | + """check that on failure, notify is set to False and quit is called""" |
384 | + s = TestingSystemImage() |
385 | + s.quit = mock.Mock(name="quit") |
386 | + |
387 | + self.assertEqual(s.notify, False) |
388 | + s.failed_cb() |
389 | + self.assertEqual(s.notify, False) |
390 | + s.quit.assert_called_once_with() |
391 | + |
392 | + def test_quit_no_notify(self): |
393 | + """Check that quit withlooks sane""" |
394 | + s = TestingSystemImage() |
395 | + s.postal = mock.Mock(name="sysimg") |
396 | + s.loop = mock.Mock(name="loop") |
397 | + s.notify = False |
398 | + s.quit() |
399 | + self.assertEqual(s.postal.Post.called, False) |
400 | + self.assertEqual(s.postal.ClearPersistent.called, False) |
401 | + s.loop.quit.assert_called_once_with() |
402 | + |
403 | + def test_quit_with_notify(self): |
404 | + """Check that quit withlooks sane""" |
405 | + s = TestingSystemImage() |
406 | + s.postal = mock.Mock(name="sysimg") |
407 | + s.loop = mock.Mock(name="loop") |
408 | + s.notify = True |
409 | + s.quit() |
410 | + s.postal.Post.assert_called_once_with("_ubuntu-system-settings", |
411 | + '"system-image-update"') |
412 | + s.postal.ClearPersistent.assert_called_once_with( |
413 | + "_ubuntu-system-settings", "system-image-update") |
414 | + s.loop.quit.assert_called_once_with() |
415 | |
416 | if __name__ == '__main__': |
417 | unittest.main( |
To test:
* Downgrade to an older image Postal/ _ -m com.ubuntu. Postal. Post _ubuntu- system- settings '"null"'
* Configure for automatic update
* gdbus call -e -d com.ubuntu.Postal -o /com/ubuntu/
* Image will download automatically
* Notification on notification centre about update will appear