Merge lp:~barry/ubuntu-ota-tests/check-for-update-2 into lp:ubuntu-ota-tests

Proposed by Barry Warsaw on 2015-03-09
Status: Merged
Approved by: Christopher Lee on 2015-03-17
Approved revision: 13
Merged at revision: 8
Proposed branch: lp:~barry/ubuntu-ota-tests/check-for-update-2
Merge into: lp:ubuntu-ota-tests
Diff against target: 219 lines (+138/-4)
6 files modified
debian/tests/check_for_update (+45/-0)
debian/tests/control (+8/-1)
debian/tests/ota_selftests (+1/-1)
debian/tests/ubuntu_ota_tests/reactors.py (+54/-0)
debian/tests/ubuntu_ota_tests/selftests/test_services.py (+6/-0)
debian/tests/ubuntu_ota_tests/services.py (+24/-2)
To merge this branch: bzr merge lp:~barry/ubuntu-ota-tests/check-for-update-2
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve on 2015-03-17
Christopher Lee (community) Approve on 2015-03-17
Leo Arias (community) Approve on 2015-03-16
Federico Gimenez (community) 2015-03-09 Approve on 2015-03-12
Review via email: mp+252324@code.launchpad.net

This proposal supersedes a proposal from 2015-03-06.

Commit Message

Add check for update test and related services.

Description of the Change

Take 2

To post a comment you must log in.
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal

(as mentioned in inline comments) I think we should have a better method for having support scripts and functionality, I've suggested something in my branch[1] (as well as posed the question there re: packaging the scripts).

[1] https://code.launchpad.net/~canonical-platform-qa/ubuntu-ota-tests/check-for-running-service/+merge/252057

review: Needs Information
Barry Warsaw (barry) wrote : Posted in a previous version of this proposal

On Mar 06, 2015, at 04:44 AM, Christopher Lee wrote:

>(as mentioned in inline comments) I think we should have a better method for
>having support scripts and functionality, I've suggested something in my
>branch[1] (as well as posed the question there re: packaging the scripts).

Yep, commented there. TL;DR: I like create a package for shared
functionality, and debian/tests/ubuntu_ota_tests is fine for now, but later
they should be refactored out into a normal Python 3 package.

>> === added directory 'debian'
>> === renamed directory 'debian' => 'debian.moved'
>> === added file 'debian/changelog'
>> --- debian/changelog 1970-01-01 00:00:00 +0000
>> +++ debian/changelog 2015-03-06 00:20:09 +0000
>> @@ -0,0 +1,5 @@
>> +ubuntu-ota-tests (1.0-1) UNRELEASED; urgency=medium
>> +
>> + * Initial release.
>> +
>> + -- Christopher Lee <email address hidden> Tue, 03 Mar 2015 14:32:30 +1300
>
>This seems odd to be in the diff, it should be in trunk. Either something has
>gone wrong here or trunk needs to be fixed.

Nope, the branch was submitted against a prerequisite branch because at the
time, trunk didn't have the necessary infrastructure. It does now, so I'll
resubmit this branch against trunk, after taking into consideration all the
comments here.

>> +class CheckingReactor(Reactor):
>> + def __init__(self, bus):
>> + super().__init__(bus)
>> + self.signals = []
>> +
>> + def _do_UpdateAvailableStatus(self, signal, path, *args):
>> + self.signals.append(UASRecord(*args))
>> + self.quit()
>> +
>> + def run(self, timeout=None):
>> + super().run(timeout)
>> + if self.timed_out:
>> + raise TimeoutError
>
>These are cool :-) Will they be useful in other tests/scripts? If so it will
>be worth moving them into a support module (as per other comment).

Yep! Will do.

>> +
>> +
>> +def check_for_update():
>> + # Since we'll be receiving signals, we have to first define the default
>> + # D-Bus main loop.
>> + DBusGMainLoop(set_as_default=True)
>> + # Boilerplate for making calls to the system service.
>> + system_bus = dbus.SystemBus()
>> + service = system_bus.get_object('com.canonical.SystemImage', '/Service')
>> + iface = dbus.Interface(service, 'com.canonical.SystemImage')
>
>I imagine the boilerplate code will be used a lot so should live somewhere
>communal, I've comment in the big comment box re: how I've copied this code
>of yours into support modules :-)
>(https://code.launchpad.net/~canonical-platform-qa/ubuntu-ota-tests/check-for-running-service/+merge/252057)

Yep, definitely should be shared. See my comments in your mp. :)

4. By Barry Warsaw on 2015-03-09

Whitespace normalization.

5. By Barry Warsaw on 2015-03-09

Whitespace normalization.

6. By Barry Warsaw on 2015-03-12

Trunk merge.

7. By Barry Warsaw on 2015-03-12

Add the reactors back.

8. By Barry Warsaw on 2015-03-12

Add the check_for_update test.

Barry Warsaw (barry) wrote :

Trunk merged, ready for follow up reviews.

Federico Gimenez (fgimenez) wrote :

The reactor module is really great, I'm already using it the apply-update branch and works flawlessly :)

Perhaps you could add a check_for_update method in the services module and move the debian/tests/check_for_update code to the test_services selftest, although I don't know how you plan to use this check. Anyway, not a blocker.

Thanks!

review: Approve
9. By Barry Warsaw on 2015-03-12

Add test dependencies.

10. By Barry Warsaw on 2015-03-12

* Move the guts of the check for update into a service function.
* Add a selftest for check_for_update.
* Refactor test main.

Barry Warsaw (barry) wrote :

On Mar 12, 2015, at 03:28 PM, Federico Gimenez wrote:

>The reactor module is really great, I'm already using it the apply-update
>branch and works flawlessly :)

\o/

>Perhaps you could add a check_for_update method in the services module and
>move the debian/tests/check_for_update code to the test_services selftest,
>although I don't know how you plan to use this check. Anyway, not a blocker.

Great ideas. I've now done this.

11. By Barry Warsaw on 2015-03-12

Trunk merge, resolve conflicts.

Christopher Lee (veebers) wrote :

Nice, I do have one inline query re: utc time but otherwise this is an approve :-)

review: Needs Information
12. By Barry Warsaw on 2015-03-16

Trunk merge

13. By Barry Warsaw on 2015-03-16

* Actually use UTC.
* Run the selftests in verbose mode so they're easier to verify.

Barry Warsaw (barry) wrote :

On Mar 13, 2015, at 08:02 AM, Christopher Lee wrote:

>> + def test_check_for_update(self):
>> + status = services.check_for_update()
>> + utcnow = datetime.now().replace(microsecond=0).isoformat(' ')
>
>This wouldn't actually be utc would it? datetime.now uses localtime if no tz
>is passed in. For utc there is datetime.utcnow

Good catch! Yes, this should have been utcnow(). I pushed a fix.

I also changed the test suite to run the selftests in verbose mode so that
it's easier to verify that all the tests are running.

Leo Arias (elopio) wrote :

This is nice. When merged with federico's branch, we can remove debian/tests/check_for_update and instead fail the upgrade test when there is no newer version available.

review: Approve
Christopher Lee (veebers) wrote :

Awesome, looks good to me.

review: Approve
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'debian/tests/check_for_update'
2--- debian/tests/check_for_update 1970-01-01 00:00:00 +0000
3+++ debian/tests/check_for_update 2015-03-16 14:25:21 +0000
4@@ -0,0 +1,45 @@
5+#!/usr/bin/python3
6+
7+# Ubuntu OTA Tests
8+# Copyright (C) 2015 Canonical
9+#
10+# This program is free software: you can redistribute it and/or modify
11+# it under the terms of the GNU General Public License as published by
12+# the Free Software Foundation, either version 3 of the License, or
13+# (at your option) any later version.
14+#
15+# This program is distributed in the hope that it will be useful,
16+# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+# GNU General Public License for more details.
19+#
20+# You should have received a copy of the GNU General Public License
21+# along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
23+"""Check for whether an update is available or not."""
24+
25+import sys
26+
27+from ubuntu_ota_tests.services import check_for_update, ensure_loop_ready
28+
29+
30+def main():
31+ ensure_loop_ready()
32+ status = check_for_update()
33+ # This bit may or may not survive. For now, proof that the test works is
34+ # by printing the results to stdout.
35+ available = bool(int(status.is_available))
36+ print('update available?', available)
37+ print('downloading?', bool(int(status.downloading)))
38+ print('available_version:', status.available_version or 'n/a')
39+ print('update size:', status.update_size)
40+ print('last update date:', status.last_update_date)
41+ if status.error_reason:
42+ print('An error occurred:', status.error_reason)
43+ # We want to return to the shell exit code 0 (success) when there is an
44+ # update available, and 1 (failure) when there isn't.
45+ return int(not available)
46+
47+
48+if __name__ == '__main__':
49+ sys.exit(main())
50
51=== modified file 'debian/tests/control'
52--- debian/tests/control 2015-03-12 15:37:04 +0000
53+++ debian/tests/control 2015-03-16 14:25:21 +0000
54@@ -9,4 +9,11 @@
55 Restrictions: needs-root allow-stderr
56 Depends: python3-autopilot,
57 python3-psutil,
58- system-image-cli
59\ No newline at end of file
60+ system-image-cli
61+
62+Tests: check_for_update
63+Depends: system-image-common,
64+ system-image-dbus,
65+ ubuntu-download-manager,
66+ dbus, dbus-x11,
67+ python3-psutil, python3-xdg, python3-setuptools, python3-nose2
68
69=== modified file 'debian/tests/ota_selftests'
70--- debian/tests/ota_selftests 2015-03-11 17:56:51 +0000
71+++ debian/tests/ota_selftests 2015-03-16 14:25:21 +0000
72@@ -22,7 +22,7 @@
73
74 cd debian/tests
75
76-python3-coverage run --include=./ubuntu_ota_tests/* --omit=./ubuntu_ota_tests/selftests/*,./ubuntu_ota_tests/__init__.py -m unittest discover ubuntu_ota_tests.selftests
77+python3-coverage run --include=./ubuntu_ota_tests/* --omit=./ubuntu_ota_tests/selftests/*,./ubuntu_ota_tests/__init__.py -m unittest discover -v ubuntu_ota_tests.selftests
78
79 mkdir -p $ADT_ARTIFACTS/coverage && python3-coverage report -m > $ADT_ARTIFACTS/coverage/report.txt
80 mv .coverage $ADT_ARTIFACTS/coverage
81
82=== added file 'debian/tests/ubuntu_ota_tests/reactors.py'
83--- debian/tests/ubuntu_ota_tests/reactors.py 1970-01-01 00:00:00 +0000
84+++ debian/tests/ubuntu_ota_tests/reactors.py 2015-03-16 14:25:21 +0000
85@@ -0,0 +1,54 @@
86+# Ubuntu OTA Tests
87+# Copyright (C) 2015 Canonical
88+#
89+# This program is free software: you can redistribute it and/or modify
90+# it under the terms of the GNU General Public License as published by
91+# the Free Software Foundation, either version 3 of the License, or
92+# (at your option) any later version.
93+#
94+# This program is distributed in the hope that it will be useful,
95+# but WITHOUT ANY WARRANTY; without even the implied warranty of
96+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
97+# GNU General Public License for more details.
98+#
99+# You should have received a copy of the GNU General Public License
100+# along with this program. If not, see <http://www.gnu.org/licenses/>.
101+
102+"""Convenient reactors used for asynchronous method calls."""
103+
104+__all__ = [
105+ 'CheckForUpdateReactor',
106+ ]
107+
108+
109+import dbus
110+
111+from collections import namedtuple
112+from systemimage.reactor import Reactor
113+
114+
115+# Use a namedtuple for more convenient argument unpacking.
116+UASRecord = namedtuple('UASRecord',
117+ 'is_available downloading available_version update_size '
118+ 'last_update_date error_reason')
119+
120+
121+class CheckForUpdateReactor(Reactor):
122+ def __init__(self, iface):
123+ super().__init__(dbus.SystemBus())
124+ self.iface = iface
125+ self.signals = []
126+ self.react_to('UpdateAvailableStatus')
127+
128+ def _do_UpdateAvailableStatus(self, signal, path, *args):
129+ self.signals.append(UASRecord(*args))
130+ self.quit()
131+
132+ def run(self, timeout=None):
133+ self.schedule(self.iface.CheckForUpdate)
134+ super().run(timeout)
135+ if self.timed_out:
136+ raise TimeoutError
137+ assert len(self.signals) == 1, (
138+ 'Unexpected signal count: {}'.format(len(self.signals)))
139+ return self.signals[0]
140
141=== modified file 'debian/tests/ubuntu_ota_tests/selftests/test_services.py'
142--- debian/tests/ubuntu_ota_tests/selftests/test_services.py 2015-03-12 14:57:16 +0000
143+++ debian/tests/ubuntu_ota_tests/selftests/test_services.py 2015-03-16 14:25:21 +0000
144@@ -18,6 +18,7 @@
145
146 import unittest
147
148+from datetime import datetime
149 from ubuntu_ota_tests import services
150
151
152@@ -46,3 +47,8 @@
153 def test_set_auto_download_on(self):
154 services.set_auto_download(True)
155 self.assertEqual(True, services.get_auto_download())
156+
157+ def test_check_for_update(self):
158+ status = services.check_for_update()
159+ utcnow = datetime.utcnow().replace(microsecond=0).isoformat(' ')
160+ self.assertLessEqual(status.last_update_date, utcnow)
161
162=== modified file 'debian/tests/ubuntu_ota_tests/services.py'
163--- debian/tests/ubuntu_ota_tests/services.py 2015-03-12 14:57:16 +0000
164+++ debian/tests/ubuntu_ota_tests/services.py 2015-03-16 14:25:21 +0000
165@@ -24,6 +24,7 @@
166 from testtools import matchers
167
168 from ubuntu_ota_tests import system
169+from ubuntu_ota_tests.reactors import CheckForUpdateReactor
170
171
172 logger = logging.getLogger(__name__)
173@@ -92,23 +93,44 @@
174 info = sys_image_iface.Information()
175 return int(info['current_build_number'])
176
177+
178 def set_auto_download(on):
179 """Sets the value of auto_download in the system image client.
180
181 :param on: Whether auto download should be on or off
182 """
183 # The system image client only accepts strings for SetSetting,
184- # with '1' representing On and '0' Off
185+ # with '1' representing On and '0' Off
186 auto_download = '1' if on else '0'
187 sys_image_iface = get_system_image_interface()
188 sys_image_iface.SetSetting('auto_download', auto_download)
189
190+
191 def get_auto_download():
192 """Gets the current value of auto_download in the system image client
193-
194+
195 :returns: True if on, False if off
196 """
197 sys_image_iface = get_system_image_interface()
198 # We get back either '1' or '0' (strings) so have to convert first to
199 # Integer then Boolean
200 return bool(int(sys_image_iface.GetSetting('auto_download')))
201+
202+
203+def check_for_update():
204+ """Check for whether updates are available.
205+
206+ :return: namedtuple containing the following attributes:
207+ - is_available: flag indicating whether an update is available
208+ - downloading: flag indicating whether a download is in progress
209+ - available_version: string specifying update target candidate
210+ version, or 'n/a' if no update is available
211+ - update_size: integer providing total size in bytes for the update
212+ - last_update_date: ISO 8601 format UTC date string that the last
213+ update was applied to this device
214+ - error_reason: a string containing the reason the check or download
215+ failed, or the empty string on success
216+ """
217+ iface = get_system_image_interface()
218+ reactor = CheckForUpdateReactor(iface)
219+ return reactor.run()

Subscribers

People subscribed via source and target branches