Merge lp:~rhuddie/ubuntu-push/push-autopilot-tests into lp:ubuntu-push/automatic
- push-autopilot-tests
- Merge into automatic
Status: | Superseded |
---|---|
Proposed branch: | lp:~rhuddie/ubuntu-push/push-autopilot-tests |
Merge into: | lp:ubuntu-push/automatic |
Diff against target: |
844 lines (+789/-0) 10 files modified
tests/autopilot/push_notifications/README (+57/-0) tests/autopilot/push_notifications/__init__.py (+8/-0) tests/autopilot/push_notifications/config/__init__.py (+27/-0) tests/autopilot/push_notifications/config/push.conf (+5/-0) tests/autopilot/push_notifications/config/testing.cert (+10/-0) tests/autopilot/push_notifications/data.py (+85/-0) tests/autopilot/push_notifications/helpers/__init__.py (+6/-0) tests/autopilot/push_notifications/helpers/push_notifications_helper.py (+259/-0) tests/autopilot/push_notifications/tests/__init__.py (+219/-0) tests/autopilot/push_notifications/tests/test_broadcast_notifications.py (+113/-0) |
To merge this branch: | bzr merge lp:~rhuddie/ubuntu-push/push-autopilot-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Leo Arias | Pending | ||
Allan LeSage | Pending | ||
VĂctor R. Ruiz | Pending | ||
Review via email:
|
This proposal supersedes a proposal from 2014-04-15.
Commit message
Description of the change
A new autopilot test framework for testing push notifications.
The tests will configure the client to run against a specified push server and then send through a push notification message which is displayed on device's screen.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Leo Arias (elopio) wrote : Posted in a previous version of this proposal | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Leo Arias (elopio) wrote : Posted in a previous version of this proposal | # |
53 +5) autopilot3 list push-notifications
54 +6) autopilot3 run push-notifications
You have a typo there ^. It should be push_notifications.
166 +class PushNotificatio
187 +class NotificationData:
I would put these classes in a push_notificati
210 + if dbus_info != None:
215 + elif copy_obj != None:
You should make checks like these with 'is not' instead of !=.
That's from pep8: "Comparisons to singletons like None should always be done with is or is not, never the equality operators."
203 + def __init__(self, dbus_info=None, copy_obj=None):
I find this to be a confusing constructor. I think it would be clearer to make two @classmethods, like
def from_dbus_
def copy(original_
Actually, I'm not sure you would need that copy. You could use the copy module.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Richard Huddie (rhuddie) wrote : | # |
Thanks Leo for your comments.
I have re-submitted the proposal against lp:ubuntu-push/automatic
I have also addressed all the other points you mentioned and fixed all the pep8 warnings.
There is still lots of work to do on this branch (e.g. add the dialog validation and add more tests etc.), but it's very good to have your feedback.
Thanks,
Richard.
- 112. By Richard Huddie
-
remove dbus dependency so it can run without root
- 113. By Richard Huddie
-
unity8 dialog validation
- 114. By Richard Huddie
-
added some new tests including locked greeter
- 115. By Richard Huddie
-
re-structure tests so that client is not re-started before each test
- 116. By Richard Huddie
-
restructure to use helper classes
- 117. By Richard Huddie
-
tidy and add test for device screen off
- 118. By Richard Huddie
-
fix flake8
- 119. By Richard Huddie
-
clear notifications before running test and move stuff into base class
- 120. By Richard Huddie
-
added cert_pem_file configuration to client
- 121. By Richard Huddie
-
certificate file path update
- 122. By Richard Huddie
-
rename tests for broadcast
- 123. By Richard Huddie
-
minor change
- 124. By Richard Huddie
-
tidy up
- 125. By Richard Huddie
-
update dialog assertion
- 126. By Richard Huddie
-
tidy up and add documentation to methods
- 127. By Richard Huddie
-
remove comment
- 128. By Richard Huddie
-
Readme updates
- 129. By Richard Huddie
-
disable mocking and minor updates
- 130. By Richard Huddie
-
move display message from base class
- 131. By Richard Huddie
-
fix review comments
- 132. By Richard Huddie
-
fix review comment
- 133. By Richard Huddie
-
Copyright header updates
- 134. By Richard Huddie
-
split greeter changes
- 135. By Richard Huddie
-
tidied up and added extra test
- 136. By Richard Huddie
-
merge with trunk
- 137. By Richard Huddie
-
update comment
- 138. By Richard Huddie
-
revert split greeter changes
- 139. By Richard Huddie
-
use /sbin/initctl
- 140. By Richard Huddie
-
update readme including emulator info
- 141. By Richard Huddie
-
wait for notification to dismiss automatically
Unmerged revisions
Preview Diff
1 | === added directory 'tests' |
2 | === added directory 'tests/autopilot' |
3 | === added directory 'tests/autopilot/push_notifications' |
4 | === added file 'tests/autopilot/push_notifications/README' |
5 | --- tests/autopilot/push_notifications/README 1970-01-01 00:00:00 +0000 |
6 | +++ tests/autopilot/push_notifications/README 2014-05-29 11:31:16 +0000 |
7 | @@ -0,0 +1,57 @@ |
8 | +================== |
9 | +README |
10 | +================== |
11 | + |
12 | +To run ubuntu-push autopilot tests you need to have a push server available. This can be running locally using loopback (127.0.0.1) or remotely on the same network. |
13 | + |
14 | +---------------------------------- |
15 | +To configure and build the server: |
16 | +---------------------------------- |
17 | + |
18 | +1) export GOPATH=${PWD}/push |
19 | +2) mkdir -p push/src/launchpad.net |
20 | +3) cd push/src/launchpad.net |
21 | +4) bzr branch lp:ubuntu-push |
22 | +5) Edit ubuntu-push/sampleconfigs/dev.json: |
23 | + "addr": "192.168.1.2:9090", |
24 | +5) cd ubuntu-push |
25 | +6) make bootstrap |
26 | +7) make run-server-dev |
27 | + Following output should be observed: |
28 | + INFO listening for http on 192.168.1.2:8080 |
29 | + INFO listening for devices on 192.168.1.2:9090 |
30 | + |
31 | +------------------------ |
32 | +To configure the client: |
33 | +------------------------ |
34 | + |
35 | +1) bzr branch lp:ubuntu-push |
36 | +2) Edit ip address and ports to match environment: ubuntu-push/tests/autopilot/push_notifications/config/push.conf: |
37 | + [config] |
38 | + addr = 192.168.1.2 |
39 | + listener_port = 8080 |
40 | + device_port = 9090 |
41 | + cert_pem_file = testing.cert |
42 | +3) cd ubuntu-push/tests/autopilot |
43 | +4) autopilot3 list push_notifications |
44 | +5) autopilot3 run push_notifications |
45 | + |
46 | +---------------- |
47 | +Troubleshooting: |
48 | +---------------- |
49 | + |
50 | +1) Ping from client to server to ensure connectivity is correct |
51 | +2) Delete ~/.local/share/ubuntu-push-client/levels.db if no notifications are being displayed: |
52 | + rm ~/.local/share-ubuntu-push-client/levels.db |
53 | +3) Check client log file at ~/.cache/upstart/ubuntu-push-client.log: |
54 | + tail -f --line=30 ~/.cache/upstart/ubuntu-push-client.log |
55 | +4) To send a notification manually: |
56 | + echo '{"channel":"system", "data": {"ubuntu-touch/trusty-proposed/mako": [297, ""]}, "expire_on": "2015-12-19T16:39:57-08:00"}' | POST -c application/json http://192.168.1.2:8080/broadcast |
57 | + Response should be: |
58 | + {"ok":true} |
59 | + Note that: |
60 | + - The channel and device names must match the client. |
61 | + - The build number must be greater than current installed version in order to trigger an update message. |
62 | + - The expiration time must be in the future. |
63 | +5) Make sure unity8-autopilot is installed |
64 | + |
65 | |
66 | === added file 'tests/autopilot/push_notifications/__init__.py' |
67 | --- tests/autopilot/push_notifications/__init__.py 1970-01-01 00:00:00 +0000 |
68 | +++ tests/autopilot/push_notifications/__init__.py 2014-05-29 11:31:16 +0000 |
69 | @@ -0,0 +1,8 @@ |
70 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
71 | +# Copyright 2014 Canonical |
72 | +# |
73 | +# This program is free software: you can redistribute it and/or modify it |
74 | +# under the terms of the GNU General Public License version 3, as published |
75 | +# by the Free Software Foundation. |
76 | + |
77 | +"""push-notifications autopilot tests.""" |
78 | |
79 | === added directory 'tests/autopilot/push_notifications/config' |
80 | === added file 'tests/autopilot/push_notifications/config/__init__.py' |
81 | --- tests/autopilot/push_notifications/config/__init__.py 1970-01-01 00:00:00 +0000 |
82 | +++ tests/autopilot/push_notifications/config/__init__.py 2014-05-29 11:31:16 +0000 |
83 | @@ -0,0 +1,27 @@ |
84 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
85 | +# Copyright 2014 Canonical |
86 | +# |
87 | +# This program is free software: you can redistribute it and/or modify it |
88 | +# under the terms of the GNU General Public License version 3, as published |
89 | +# by the Free Software Foundation. |
90 | + |
91 | + |
92 | +import os |
93 | + |
94 | +CONFIG_FILE = 'push.conf' |
95 | + |
96 | + |
97 | +def get_config_file(): |
98 | + """ |
99 | + Return the path for the config file |
100 | + """ |
101 | + config_dir = os.path.dirname(__file__) |
102 | + return os.path.join(config_dir, CONFIG_FILE) |
103 | + |
104 | + |
105 | +def get_cert_file(cert_file_name): |
106 | + """ |
107 | + Return the path for the testing certificate file |
108 | + """ |
109 | + config_dir = os.path.dirname(__file__) |
110 | + return os.path.join(config_dir, cert_file_name) |
111 | |
112 | === added file 'tests/autopilot/push_notifications/config/push.conf' |
113 | --- tests/autopilot/push_notifications/config/push.conf 1970-01-01 00:00:00 +0000 |
114 | +++ tests/autopilot/push_notifications/config/push.conf 2014-05-29 11:31:16 +0000 |
115 | @@ -0,0 +1,5 @@ |
116 | +[config] |
117 | +addr = 192.168.1.3 |
118 | +listener_port = 8080 |
119 | +device_port = 9090 |
120 | +cert_pem_file = testing.cert |
121 | |
122 | === added file 'tests/autopilot/push_notifications/config/testing.cert' |
123 | --- tests/autopilot/push_notifications/config/testing.cert 1970-01-01 00:00:00 +0000 |
124 | +++ tests/autopilot/push_notifications/config/testing.cert 2014-05-29 11:31:16 +0000 |
125 | @@ -0,0 +1,10 @@ |
126 | +-----BEGIN CERTIFICATE----- |
127 | +MIIBYzCCAQ+gAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD |
128 | +bzAeFw0xMzEyMTkyMDU1NDNaFw0yMzEyMTcyMDU1NDNaMBIxEDAOBgNVBAoTB0Fj |
129 | +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAPw+niki17X2qALE2A2AzE1q5dvK |
130 | +9CI4OduRtT9IgbFLC6psqAT21NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEA |
131 | +AaNUMFIwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud |
132 | +EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGCSqGSIb3 |
133 | +DQEBBQNBAFqiVI+Km2XPSO+pxITaPvhmuzg+XG3l1+2di3gL+HlDobocjBqRctRU |
134 | +YySO32W07acjGJmCHUKpCJuq9X8hpmk= |
135 | +-----END CERTIFICATE----- |
136 | |
137 | === added file 'tests/autopilot/push_notifications/data.py' |
138 | --- tests/autopilot/push_notifications/data.py 1970-01-01 00:00:00 +0000 |
139 | +++ tests/autopilot/push_notifications/data.py 2014-05-29 11:31:16 +0000 |
140 | @@ -0,0 +1,85 @@ |
141 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
142 | +# Copyright 2014 Canonical |
143 | +# |
144 | +# This program is free software: you can redistribute it and/or modify it |
145 | +# under the terms of the GNU General Public License version 3, as published |
146 | +# by the Free Software Foundation. |
147 | + |
148 | +"""Push-Notifications autopilot data structure classes""" |
149 | + |
150 | + |
151 | +class PushNotificationMessage: |
152 | + """ |
153 | + Class to hold all the details required for a |
154 | + push notification message |
155 | + """ |
156 | + |
157 | + def __init__(self, channel='system', data=None, expire_after=None): |
158 | + """ |
159 | + Constructor |
160 | + :param channel: Name of channel |
161 | + :param data: Data value |
162 | + :param expire_after: expiration time |
163 | + """ |
164 | + self.channel = channel |
165 | + self.data = data |
166 | + self.expire_after = expire_after |
167 | + |
168 | + def json(self): |
169 | + """ |
170 | + Return JSON representation of message |
171 | + :return: JSON representation of message |
172 | + """ |
173 | + json_str = '{{"channel":"{0}", "data":{{{1}}}, "expire_on":"{2}"}}' |
174 | + return json_str.format(self.channel, self.data, self.expire_after) |
175 | + |
176 | + |
177 | +class DeviceNotificationData: |
178 | + """ |
179 | + Class to represent device's data used for sending notification, including: |
180 | + - Device software channel |
181 | + - Device build number |
182 | + - Device model |
183 | + - Device last update |
184 | + - Data for the notification |
185 | + """ |
186 | + |
187 | + def __init__(self, channel=None, device=None, build_number=None, |
188 | + last_update=None, version=None, data=None): |
189 | + """ |
190 | + Constructor |
191 | + :param channel: Name of channel |
192 | + :param device: Name of device |
193 | + :param build_number: Build number |
194 | + :param last_update: Last update time |
195 | + :param version: Build version |
196 | + :param data: Device specific data |
197 | + """ |
198 | + self.channel = channel |
199 | + self.build_number = build_number |
200 | + self.device = device |
201 | + self.last_update = last_update |
202 | + self.version = version |
203 | + self.data = data |
204 | + |
205 | + def inc_build_number(self): |
206 | + """ |
207 | + Increment build number |
208 | + """ |
209 | + self.build_number = str(int(self.build_number) + 1) |
210 | + |
211 | + def dec_build_number(self): |
212 | + """ |
213 | + Decrement build number |
214 | + """ |
215 | + self.build_number = str(int(self.build_number) - 1) |
216 | + |
217 | + def json(self): |
218 | + """ |
219 | + Return json representation of info based: |
220 | + "IMAGE-CHANNEL/DEVICE-MODEL": [BUILD-NUMBER, CHANNEL-ALIAS]" |
221 | + :return: JSON representation of device data |
222 | + """ |
223 | + json_str = '"{0}/{1}": [{2}, "{3}"]' |
224 | + return json_str.format(self.channel, self.device, self.build_number, |
225 | + self.data) |
226 | |
227 | === added directory 'tests/autopilot/push_notifications/helpers' |
228 | === added file 'tests/autopilot/push_notifications/helpers/__init__.py' |
229 | --- tests/autopilot/push_notifications/helpers/__init__.py 1970-01-01 00:00:00 +0000 |
230 | +++ tests/autopilot/push_notifications/helpers/__init__.py 2014-05-29 11:31:16 +0000 |
231 | @@ -0,0 +1,6 @@ |
232 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
233 | +# Copyright 2014 Canonical |
234 | +# |
235 | +# This program is free software: you can redistribute it and/or modify it |
236 | +# under the terms of the GNU General Public License version 3, as published |
237 | +# by the Free Software Foundation. |
238 | |
239 | === added file 'tests/autopilot/push_notifications/helpers/push_notifications_helper.py' |
240 | --- tests/autopilot/push_notifications/helpers/push_notifications_helper.py 1970-01-01 00:00:00 +0000 |
241 | +++ tests/autopilot/push_notifications/helpers/push_notifications_helper.py 2014-05-29 11:31:16 +0000 |
242 | @@ -0,0 +1,259 @@ |
243 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
244 | +# Copyright 2014 Canonical |
245 | +# |
246 | +# This program is free software: you can redistribute it and/or modify it |
247 | +# under the terms of the GNU General Public License version 3, as published |
248 | +# by the Free Software Foundation. |
249 | + |
250 | +import configparser |
251 | +import http.client as http |
252 | +import json |
253 | +import os |
254 | +import datetime |
255 | +import subprocess |
256 | +import systemimage.config as sys_info |
257 | + |
258 | +from push_notifications.data import PushNotificationMessage |
259 | +from push_notifications.data import DeviceNotificationData |
260 | +from push_notifications import config as push_config |
261 | + |
262 | + |
263 | +class PushClientConfig: |
264 | + """ |
265 | + Container class to read and hold all required server config |
266 | + - Server listener address |
267 | + - Server device address |
268 | + - Certificate PEM file path |
269 | + """ |
270 | + KEY_ADDR = 'addr' |
271 | + KEY_LISTENER_PORT = 'listener_port' |
272 | + KEY_DEVICE_PORT = 'device_port' |
273 | + KEY_CONFIG = 'config' |
274 | + KEY_CERT_PEM_FILE = 'cert_pem_file' |
275 | + |
276 | + def read_config(self, config_file_path): |
277 | + """ |
278 | + Open the specified file and read and save required config values |
279 | + :param config_file_path: path to required config file |
280 | + """ |
281 | + parser = configparser.ConfigParser() |
282 | + parser.read(config_file_path) |
283 | + server_addr = parser[self.KEY_CONFIG][self.KEY_ADDR] |
284 | + device_port = parser[self.KEY_CONFIG][self.KEY_DEVICE_PORT] |
285 | + listener_port = parser[self.KEY_CONFIG][self.KEY_LISTENER_PORT] |
286 | + addr_fmt = '{0}:{1}' |
287 | + self.server_listener_addr = addr_fmt.format(server_addr, listener_port) |
288 | + self.server_device_addr = addr_fmt.format(server_addr, device_port) |
289 | + self.cert_pem_file = push_config.get_cert_file( |
290 | + parser[self.KEY_CONFIG][self.KEY_CERT_PEM_FILE]) |
291 | + |
292 | + |
293 | +class PushClientController: |
294 | + """ |
295 | + Class used to reconfigure and re-start ubuntu-push-client for testing |
296 | + """ |
297 | + |
298 | + PUSH_CLIENT_DEFAULT_CONFIG_FILE = '/etc/xdg/ubuntu-push-client/config.json' |
299 | + PUSH_CLIENT_CONFIG_FILE = '~/.config/ubuntu-push-client/config.json' |
300 | + |
301 | + def restart_push_client_using_config(self, client_config=None): |
302 | + """ |
303 | + Restart the push client using the config provided |
304 | + If the config is none then revert to default client behaviour |
305 | + :param client_config: PushClientConfig object containing |
306 | + required config |
307 | + """ |
308 | + if client_config is None: |
309 | + # just delete the local custom config file |
310 | + # client will then just use the original config |
311 | + abs_config_file = self._get_abs_local_config_file_path() |
312 | + if os.path.exists(abs_config_file): |
313 | + os.remove(abs_config_file) |
314 | + else: |
315 | + # write the config to local config file |
316 | + self._write_client_test_config(client_config) |
317 | + |
318 | + # Now re-start the client |
319 | + self._restart_push_client() |
320 | + |
321 | + def _write_client_test_config(self, client_config): |
322 | + """ |
323 | + Write the test server address and certificate path |
324 | + to the client config file |
325 | + :param client_config: PushClientConfig object containing |
326 | + required config |
327 | + """ |
328 | + # read the original push client config file |
329 | + with open(self.PUSH_CLIENT_DEFAULT_CONFIG_FILE) as config_file: |
330 | + config = json.load(config_file) |
331 | + # change server address |
332 | + config['addr'] = client_config.server_device_addr |
333 | + # add certificate file path |
334 | + config['cert_pem_file'] = client_config.cert_pem_file |
335 | + # write the config json out to the ~.local address |
336 | + # creating the directory if it doesn't already exist |
337 | + abs_config_file = self._get_abs_local_config_file_path() |
338 | + config_dir = os.path.dirname(abs_config_file) |
339 | + if not os.path.exists(config_dir): |
340 | + os.makedirs(config_dir) |
341 | + with open(abs_config_file, 'w+') as outfile: |
342 | + json.dump(config, outfile, indent=4) |
343 | + outfile.close() |
344 | + |
345 | + def _get_abs_local_config_file_path(self): |
346 | + """ |
347 | + Return absolute path of ~.local config file |
348 | + """ |
349 | + return os.path.expanduser(self.PUSH_CLIENT_CONFIG_FILE) |
350 | + |
351 | + def _control_client(self, command): |
352 | + """ |
353 | + start/stop/restart the ubuntu-push-client using initctl |
354 | + """ |
355 | + subprocess.call( |
356 | + ['initctl', command, 'ubuntu-push-client'], |
357 | + stdout=subprocess.DEVNULL) |
358 | + |
359 | + def _stop_push_client(self): |
360 | + """ |
361 | + Stop the push client |
362 | + """ |
363 | + self._control_client('stop') |
364 | + |
365 | + def _start_push_client(self): |
366 | + """ |
367 | + Start the push client |
368 | + """ |
369 | + self._control_client('start') |
370 | + |
371 | + def _restart_push_client(self): |
372 | + """ |
373 | + Restart the push client |
374 | + """ |
375 | + self._stop_push_client() |
376 | + self._start_push_client() |
377 | + |
378 | + |
379 | +class PushNotificationHelper: |
380 | + """ |
381 | + Utility class to create and send push notification messages |
382 | + """ |
383 | + |
384 | + DEFAULT_BROADCAST_URL = '/broadcast' |
385 | + |
386 | + def get_device_info(self): |
387 | + """ |
388 | + Discover the device's model and build info |
389 | + - device name e.g. mako |
390 | + - channel name e.g. ubuntu-touch/utopic-proposed |
391 | + - build_number e.g. 101 |
392 | + :return: DeviceNotificationData object containing device info |
393 | + """ |
394 | + # channel info needs to be read from file |
395 | + parser = configparser.ConfigParser() |
396 | + channel_config_file = '/etc/system-image/channel.ini' |
397 | + parser.read(channel_config_file) |
398 | + channel = parser['service']['channel'] |
399 | + return DeviceNotificationData( |
400 | + device=sys_info.config.device, |
401 | + channel=channel, |
402 | + build_number=sys_info.config.build_number) |
403 | + |
404 | + def send_push_broadcast_notification(self, msg_json, server_addr, |
405 | + url=DEFAULT_BROADCAST_URL): |
406 | + """ |
407 | + Send the specified push message to the server broadcast url |
408 | + using an HTTP POST command |
409 | + :param msg_json: JSON representation of message to send |
410 | + :param url: destination server url to send message to |
411 | + """ |
412 | + headers = {'Content-type': 'application/json'} |
413 | + conn = http.HTTPConnection(server_addr) |
414 | + conn.request( |
415 | + 'POST', |
416 | + url, |
417 | + headers=headers, |
418 | + body=msg_json) |
419 | + return conn.getresponse() |
420 | + |
421 | + def create_push_message(self, channel='system', data=None, |
422 | + expire_after=None): |
423 | + """ |
424 | + Return a new push message |
425 | + If no expiry time is given, a future date will be assigned |
426 | + :param channel: name of the channel |
427 | + :param data: data value of the message |
428 | + :param expire_after: expiry time for message |
429 | + :return: PushNotificationMessage object containing specified parameters |
430 | + """ |
431 | + if expire_after is None: |
432 | + expire_after = self.get_future_iso_time() |
433 | + return PushNotificationMessage( |
434 | + channel=channel, |
435 | + data=data, |
436 | + expire_after=expire_after) |
437 | + |
438 | + def get_past_iso_time(self): |
439 | + """ |
440 | + Return time 1 year in past in ISO format |
441 | + """ |
442 | + return self.get_iso_time(year_offset=-1) |
443 | + |
444 | + def get_near_past_iso_time(self): |
445 | + """ |
446 | + Return time 1 minute in past in ISO format |
447 | + """ |
448 | + return self.get_iso_time(min_offset=-1) |
449 | + |
450 | + def get_near_future_iso_time(self): |
451 | + """ |
452 | + Return time 1 minute in future in ISO format |
453 | + """ |
454 | + return self.get_iso_time(min_offset=1) |
455 | + |
456 | + def get_future_iso_time(self): |
457 | + """ |
458 | + Return time 1 year in future in ISO format |
459 | + """ |
460 | + return self.get_iso_time(year_offset=1) |
461 | + |
462 | + def get_current_iso_time(self): |
463 | + """ |
464 | + Return current time in ISO format |
465 | + """ |
466 | + return self.get_iso_time() |
467 | + |
468 | + def get_iso_time(self, year_offset=0, month_offset=0, day_offset=0, |
469 | + hour_offset=0, min_offset=0, sec_offset=0, |
470 | + tz_hour_offset=0, tz_min_offset=0): |
471 | + """ |
472 | + Return an ISO8601 format date-time string, including time-zone |
473 | + offset: YYYY-MM-DDTHH:MM:SS-HH:MM |
474 | + :param year_offset: number of years to offset |
475 | + :param month_offset: number of months to offset |
476 | + :param day_offset: number of days to offset |
477 | + :param hour_offset: number of hours to offset |
478 | + :param min_offset: number of minutes to offset |
479 | + :param sec_offset: number of seconds to offset |
480 | + :param tz_hour_offset: number of hours to offset time zone |
481 | + :param tz_min_offset: number of minutes to offset time zone |
482 | + :return: string representation of required time in ISO8601 format |
483 | + """ |
484 | + # calulate target time based on current time and format it |
485 | + now = datetime.datetime.now() |
486 | + target_time = datetime.datetime( |
487 | + year=now.year + year_offset, |
488 | + month=now.month + month_offset, |
489 | + day=now.day + day_offset, |
490 | + hour=now.hour + hour_offset, |
491 | + minute=now.minute + min_offset, |
492 | + second=now.second + sec_offset) |
493 | + target_time_fmt = target_time.strftime('%Y-%m-%dT%H:%M:%S') |
494 | + # format time zone offset |
495 | + tz = datetime.time( |
496 | + hour=tz_hour_offset, |
497 | + minute=tz_min_offset) |
498 | + tz_fmt = tz.strftime('%H:%M') |
499 | + # combine target time and time zone offset |
500 | + iso_time = '{0}-{1}'.format(target_time_fmt, tz_fmt) |
501 | + return iso_time |
502 | |
503 | === added directory 'tests/autopilot/push_notifications/tests' |
504 | === added file 'tests/autopilot/push_notifications/tests/__init__.py' |
505 | --- tests/autopilot/push_notifications/tests/__init__.py 1970-01-01 00:00:00 +0000 |
506 | +++ tests/autopilot/push_notifications/tests/__init__.py 2014-05-29 11:31:16 +0000 |
507 | @@ -0,0 +1,219 @@ |
508 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
509 | +# Copyright 2014 Canonical |
510 | +# |
511 | +# This program is free software: you can redistribute it and/or modify it |
512 | +# under the terms of the GNU General Public License version 3, as published |
513 | +# by the Free Software Foundation. |
514 | + |
515 | +"""push-notifications autopilot tests.""" |
516 | + |
517 | + |
518 | +import copy |
519 | +import evdev |
520 | + |
521 | +from testtools.matchers import Equals, NotEquals |
522 | +from autopilot.matchers import Eventually |
523 | +from autopilot.introspection import dbus |
524 | +from unity8.shell.tests import UnityTestCase |
525 | +import unity8.process_helpers as unity8_helpers |
526 | +from push_notifications import config as push_config |
527 | +import push_notifications.helpers.push_notifications_helper as push_helper |
528 | + |
529 | + |
530 | +class PushNotificationTestBase(UnityTestCase): |
531 | + """ |
532 | + Base class for push notification test cases |
533 | + """ |
534 | + DEFAULT_DISPLAY_MESSAGE = 'There\'s an updated system image.' |
535 | + |
536 | + @classmethod |
537 | + def setUpClass(cls): |
538 | + """ |
539 | + Executed once before all the tests run |
540 | + Restart the push client using the test config |
541 | + """ |
542 | + test_config = push_helper.PushClientConfig() |
543 | + test_config.read_config(push_config.get_config_file()) |
544 | + push_client_controller = push_helper.PushClientController() |
545 | + push_client_controller.restart_push_client_using_config(test_config) |
546 | + |
547 | + @classmethod |
548 | + def tearDownClass(cls): |
549 | + """ |
550 | + Executed once after all tests have completed |
551 | + Reset the push client to use the device's original config |
552 | + """ |
553 | + push_client_controller = push_helper.PushClientController() |
554 | + push_client_controller.restart_push_client_using_config(None) |
555 | + |
556 | + def setUp(self): |
557 | + """ |
558 | + Setup phase executed before each test |
559 | + """ |
560 | + # setup |
561 | + super(PushNotificationTestBase, self).setUp() |
562 | + |
563 | + # read and store the test config data |
564 | + self.test_config = push_helper.PushClientConfig() |
565 | + self.test_config.read_config(push_config.get_config_file()) |
566 | + # create a push helper object which will do all the message sending |
567 | + self.push_helper = push_helper.PushNotificationHelper() |
568 | + # get and store device and build info |
569 | + self.device_data = self.push_helper.get_device_info() |
570 | + # start unity8 |
571 | + self.unity = self.launch_unity() |
572 | + # dismiss any outstanding dialog |
573 | + self.dismiss_outstanding_dialog() |
574 | + |
575 | + def create_device_info_copy(self): |
576 | + """ |
577 | + Return a copy of the device's model and build data |
578 | + :return: DeviceNotificationData object containging device's model |
579 | + and build data |
580 | + """ |
581 | + return copy.deepcopy(self.device_data) |
582 | + |
583 | + def press_power_button(self): |
584 | + """ |
585 | + Simulate a power key press event |
586 | + """ |
587 | + uinput = evdev.UInput(name='push-autopilot-power-button', |
588 | + devnode='/dev/autopilot-uinput') |
589 | + # One press and release to turn screen off (locking unity) |
590 | + uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 1) |
591 | + uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 0) |
592 | + uinput.syn() |
593 | + |
594 | + def unlock_greeter(self): |
595 | + """ |
596 | + Unlock the greeter to display home screen |
597 | + """ |
598 | + unity8_helpers.unlock_unity(self.unity) |
599 | + |
600 | + def validate_response(self, response, expected_status_code=200): |
601 | + """ |
602 | + Validate the received response status code against expected code |
603 | + :param response: response to validate |
604 | + :param expected_status_code: value of expected http status code |
605 | + """ |
606 | + self.assertThat(response.status, Equals(expected_status_code)) |
607 | + |
608 | + def _assert_notification_dialog(self, notification, summary=None, |
609 | + body=None, icon=True, secondary_icon=False, |
610 | + opacity=None): |
611 | + """ |
612 | + Assert that the properties of the notification are as |
613 | + expected |
614 | + :param notification: notification object to validate |
615 | + :param summary: expected notification summary value |
616 | + :param body: expected notification body value |
617 | + :param icon: expected icon status |
618 | + :param secondary_icon: expected secondary icon status |
619 | + :param opacity: expected opacity value |
620 | + """ |
621 | + if summary is not None: |
622 | + self.assertThat(notification.summary, Eventually(Equals(summary))) |
623 | + if body is not None: |
624 | + self.assertThat(notification.body, Eventually(Equals(body))) |
625 | + if opacity is not None: |
626 | + self.assertThat(notification.opacity, Eventually(Equals(opacity))) |
627 | + |
628 | + if icon: |
629 | + self.assertThat( |
630 | + notification.iconSource, Eventually(NotEquals(''))) |
631 | + else: |
632 | + self.assertThat( |
633 | + notification.iconSource, Eventually(Equals(''))) |
634 | + |
635 | + if secondary_icon: |
636 | + self.assertThat( |
637 | + notification.secondaryIconSource, Eventually(NotEquals(''))) |
638 | + else: |
639 | + self.assertThat( |
640 | + notification.secondaryIconSource, Eventually(Equals(''))) |
641 | + |
642 | + def validate_notification_not_displayed(self, wait=True): |
643 | + """ |
644 | + Validate that the notification is not displayed |
645 | + If wait is True then wait for default timeout period |
646 | + If wait is False then do not wait at all |
647 | + :param wait: wait status |
648 | + """ |
649 | + found = True |
650 | + try: |
651 | + if wait is True: |
652 | + self.main_window.wait_select_single( |
653 | + 'Notification', objectName='notification1') |
654 | + else: |
655 | + self.main_window.select_single( |
656 | + 'Notification', objectName='notification1') |
657 | + except dbus.StateNotFoundError: |
658 | + found = False |
659 | + self.assertFalse(found) |
660 | + |
661 | + def send_push_broadcast_message(self): |
662 | + """ |
663 | + Send a push broadcast message which should trigger a notification |
664 | + to be displayed on the client |
665 | + """ |
666 | + # create a copy of the device's build info |
667 | + device_info = self.create_device_info_copy() |
668 | + # increment the build number to trigger an update |
669 | + device_info.inc_build_number() |
670 | + # create push message based on the device data |
671 | + push_msg = self.push_helper.create_push_message( |
672 | + data=device_info.json()) |
673 | + # send the notification message to the server and check response |
674 | + response = self.push_helper.send_push_broadcast_notification( |
675 | + push_msg.json(), self.test_config.server_listener_addr) |
676 | + self.validate_response(response) |
677 | + |
678 | + def get_notification_dialog(self, wait=True): |
679 | + """ |
680 | + Get the notification dialog being displaye on screen |
681 | + If wait is True then wait for default timeout period |
682 | + If wait is False then do not wait at all |
683 | + :param wait: wait status |
684 | + :return: dialog introspection object |
685 | + """ |
686 | + if wait is True: |
687 | + dialog = self.main_window.wait_select_single( |
688 | + 'Notification', objectName='notification1') |
689 | + else: |
690 | + dialog = self.main_window.select_single( |
691 | + 'Notification', objectName='notification1') |
692 | + return dialog |
693 | + |
694 | + def validate_and_dismiss_notification_dialog(self, message): |
695 | + """ |
696 | + Validate a notification dialog is displayed and dismiss it |
697 | + :param message: expected message displayed in summary |
698 | + """ |
699 | + # get the dialog |
700 | + dialog = self.get_notification_dialog() |
701 | + # validate dialog |
702 | + self._assert_notification_dialog( |
703 | + dialog, summary=message) |
704 | + # press dialog to dismiss |
705 | + self.press_notification_dialog(dialog) |
706 | + # check the dialog is no longer displayed |
707 | + self.validate_notification_not_displayed(wait=False) |
708 | + |
709 | + def press_notification_dialog(self, dialog): |
710 | + """ |
711 | + Press the dialog to dismiss it |
712 | + """ |
713 | + self.touch.tap_object(dialog) |
714 | + |
715 | + def dismiss_outstanding_dialog(self): |
716 | + """ |
717 | + Dismiss outstanding notification dialog that may be displayed |
718 | + from an aborted previous test |
719 | + """ |
720 | + try: |
721 | + dialog = self.main_window.select_single( |
722 | + 'Notification', objectName='notification1') |
723 | + except dbus.StateNotFoundError: |
724 | + dialog = None |
725 | + if dialog is not None: |
726 | + self.press_notification_dialog(dialog) |
727 | |
728 | === added file 'tests/autopilot/push_notifications/tests/test_broadcast_notifications.py' |
729 | --- tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 1970-01-01 00:00:00 +0000 |
730 | +++ tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 2014-05-29 11:31:16 +0000 |
731 | @@ -0,0 +1,113 @@ |
732 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
733 | +# Copyright 2014 Canonical |
734 | +# |
735 | +# This program is free software: you can redistribute it and/or modify it |
736 | +# under the terms of the GNU General Public License version 3, as published |
737 | +# by the Free Software Foundation. |
738 | + |
739 | +"""Tests broadcast push notifications sent to the client""" |
740 | + |
741 | +import time |
742 | +from push_notifications.tests import PushNotificationTestBase |
743 | + |
744 | + |
745 | +class TestPushClientBroadcast(PushNotificationTestBase): |
746 | + """ |
747 | + Test cases for broadcast push notifications |
748 | + """ |
749 | + |
750 | + def test_broadcast_push_notification_screen_off(self): |
751 | + """ |
752 | + Send a push message whilst the device's screen is turned off |
753 | + Notification should still be displayed when it is turned on |
754 | + """ |
755 | + # Assumes greeter starts in locked state |
756 | + # Turn display off |
757 | + self.press_power_button() |
758 | + # send message |
759 | + self.send_push_broadcast_message() |
760 | + # wait before turning screen on |
761 | + time.sleep(2) |
762 | + # Turn display on |
763 | + self.press_power_button() |
764 | + self.validate_and_dismiss_notification_dialog( |
765 | + self.DEFAULT_DISPLAY_MESSAGE) |
766 | + |
767 | + def test_broadcast_push_notification_locked_greeter(self): |
768 | + """ |
769 | + Positive test case to send a valid broadcast push notification |
770 | + to the client and validate that a notification message is displayed |
771 | + whist the greeter screen is displayed |
772 | + """ |
773 | + # Assumes greeter starts in locked state |
774 | + self.send_push_broadcast_message() |
775 | + self.validate_and_dismiss_notification_dialog( |
776 | + self.DEFAULT_DISPLAY_MESSAGE) |
777 | + |
778 | + def test_broadcast_push_notification(self): |
779 | + """ |
780 | + Positive test case to send a valid broadcast push notification |
781 | + to the client and validate that a notification message is displayed |
782 | + """ |
783 | + # Assumes greeter starts in locked state |
784 | + self.unlock_greeter() |
785 | + # send message |
786 | + self.send_push_broadcast_message() |
787 | + self.validate_and_dismiss_notification_dialog( |
788 | + self.DEFAULT_DISPLAY_MESSAGE) |
789 | + |
790 | + def test_expired_broadcast_push_notification(self): |
791 | + """ |
792 | + Send an expired broadcast notification message to server |
793 | + """ |
794 | + # Assumes greeter starts in locked state |
795 | + self.unlock_greeter() |
796 | + # create notification message using past expiry time |
797 | + device_info = self.create_device_info_copy() |
798 | + device_info.inc_build_number() |
799 | + push_msg = self.push_helper.create_push_message( |
800 | + data=device_info.json(), |
801 | + expire_after=self.push_helper.get_past_iso_time()) |
802 | + # send message |
803 | + response = self.push_helper.send_push_broadcast_notification( |
804 | + push_msg.json(), |
805 | + self.test_config.server_listener_addr) |
806 | + # 400 status is received for an expired message |
807 | + self.validate_response(response, expected_status_code=400) |
808 | + # validate no notification is displayed |
809 | + self.validate_notification_not_displayed() |
810 | + |
811 | + def test_older_version_broadcast_push_notification(self): |
812 | + """ |
813 | + Send an old version broadcast notification message to server |
814 | + """ |
815 | + # Assumes greeter starts in locked state |
816 | + self.unlock_greeter() |
817 | + # create notification message using previous build number |
818 | + device_info = self.create_device_info_copy() |
819 | + device_info.dec_build_number() |
820 | + push_msg = self.push_helper.create_push_message( |
821 | + data=device_info.json()) |
822 | + response = self.push_helper.send_push_broadcast_notification( |
823 | + push_msg.json(), |
824 | + self.test_config.server_listener_addr) |
825 | + self.validate_response(response) |
826 | + # validate no notification is displayed |
827 | + self.validate_notification_not_displayed() |
828 | + |
829 | + def test_equal_version_broadcast_push_notification(self): |
830 | + """ |
831 | + Send an equal version broadcast notification message to server |
832 | + """ |
833 | + # Assumes greeter starts in locked state |
834 | + self.unlock_greeter() |
835 | + # create notification message using equal build number |
836 | + device_info = self.create_device_info_copy() |
837 | + push_msg = self.push_helper.create_push_message( |
838 | + data=device_info.json()) |
839 | + response = self.push_helper.send_push_broadcast_notification( |
840 | + push_msg.json(), |
841 | + self.test_config.server_listener_addr) |
842 | + self.validate_response(response) |
843 | + # validate no notification is displayed |
844 | + self.validate_notification_not_displayed() |
Hey Richard. You need to resubmit your MP against lp:ubuntu-push/automatic instead of trunk. I'll forward you the mail pedronis sent about this.