Merge lp:~om26er/ubuntu-system-image/add_upgrade_test into lp:~registry/ubuntu-system-image/client

Proposed by Omer Akram
Status: Needs review
Proposed branch: lp:~om26er/ubuntu-system-image/add_upgrade_test
Merge into: lp:~registry/ubuntu-system-image/client
Diff against target: 475 lines (+436/-0)
7 files modified
systemimage/tests/autopilot/system_upgrade/__init__.py (+6/-0)
systemimage/tests/autopilot/system_upgrade/helpers/__init__.py (+6/-0)
systemimage/tests/autopilot/system_upgrade/helpers/system_image_setup.py (+167/-0)
systemimage/tests/autopilot/system_upgrade/helpers/unlock_screen.py (+55/-0)
systemimage/tests/autopilot/system_upgrade/tests/test_system_updates.py (+46/-0)
systemimage/tests/autopilot/system_upgrade/variables.py (+14/-0)
systemimage/tests/autopilot/upgrade_test_runner (+142/-0)
To merge this branch: bzr merge lp:~om26er/ubuntu-system-image/add_upgrade_test
Reviewer Review Type Date Requested Status
Registry Administrators Pending
Review via email: mp+215028@code.launchpad.net

Commit message

Add a test runner that flashes the touch device and then runs the upgrade test on the Device under test.

Description of the change

Add a test runner that flashes the touch device and then runs the upgrade test on the Device under test.

Needs packaging work so that we have a separate package system-image-autopilot ?

depends on: lp:~om26er/ubuntu-system-settings/upgrade_testing_prerequisite

For Barry regarding packaging, the newly created test package that you create out of these tests should depend on.
  * ubuntu-system-settings-autopilot
  * unity8-autopilot

To post a comment you must log in.
259. By Omer Akram

remove hard-coded package list

Unmerged revisions

259. By Omer Akram

remove hard-coded package list

258. By Omer Akram

add docstrings to elaborate helpers

257. By Omer Akram

update header, arrange import

256. By Omer Akram

minor cleaning, make some methods private, remove unsued variables

255. By Omer Akram

don't try to push tests to the device if the test runner is installed system-wide

254. By Omer Akram

fix pep8

253. By Omer Akram

adapt to helper class changes

252. By Omer Akram

import update helpers from system-settings

251. By Omer Akram

clean code a bit

250. By Omer Akram

bring in upgrade tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'systemimage/tests/autopilot'
2=== added directory 'systemimage/tests/autopilot/system_upgrade'
3=== added file 'systemimage/tests/autopilot/system_upgrade/__init__.py'
4--- systemimage/tests/autopilot/system_upgrade/__init__.py 1970-01-01 00:00:00 +0000
5+++ systemimage/tests/autopilot/system_upgrade/__init__.py 2014-04-09 20:44:26 +0000
6@@ -0,0 +1,6 @@
7+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
8+# Copyright 2014 Canonical
9+#
10+# This program is free software: you can redistribute it and/or modify it
11+# under the terms of the GNU General Public License version 3, as published
12+# by the Free Software Foundation.
13
14=== added directory 'systemimage/tests/autopilot/system_upgrade/helpers'
15=== added file 'systemimage/tests/autopilot/system_upgrade/helpers/__init__.py'
16--- systemimage/tests/autopilot/system_upgrade/helpers/__init__.py 1970-01-01 00:00:00 +0000
17+++ systemimage/tests/autopilot/system_upgrade/helpers/__init__.py 2014-04-09 20:44:26 +0000
18@@ -0,0 +1,6 @@
19+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
20+# Copyright 2014 Canonical
21+#
22+# This program is free software: you can redistribute it and/or modify it
23+# under the terms of the GNU General Public License version 3, as published
24+# by the Free Software Foundation.
25
26=== added file 'systemimage/tests/autopilot/system_upgrade/helpers/system_image_setup.py'
27--- systemimage/tests/autopilot/system_upgrade/helpers/system_image_setup.py 1970-01-01 00:00:00 +0000
28+++ systemimage/tests/autopilot/system_upgrade/helpers/system_image_setup.py 2014-04-09 20:44:26 +0000
29@@ -0,0 +1,167 @@
30+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
31+# Copyright 2014 Canonical
32+#
33+# This program is free software: you can redistribute it and/or modify it
34+# under the terms of the GNU General Public License version 3, as published
35+# by the Free Software Foundation.
36+
37+import logging
38+import subprocess
39+import time
40+
41+logger = logging.getLogger(__name__)
42+
43+
44+class DeviceImageFlash(object):
45+
46+ def setup_device(self, ppa=None, package_list=None, **kwargs):
47+ """Sets up the device for upgrade testing
48+
49+ :param ppa: A launchpad ppa to add to the DUT before
50+ apt-get update.
51+
52+ :param package_list: Names of the packages to be installed
53+ on the DUT before running tests.
54+
55+ :param channel: Ubuntu system image update channel like
56+ ubuntu-touch/devel.
57+
58+ :param revision_number: System image revision number.
59+
60+ :param bootstrap: clear everything on DUT before flashing
61+ the image.
62+
63+ """
64+ self.flash_device(**kwargs)
65+ self.wait_for_device()
66+ self.set_device_read_write()
67+ self.reboot_device()
68+ self.disable_intro_screen()
69+ self.reboot_device()
70+
71+ self.setup_network()
72+
73+ if ppa is not None:
74+ self.add_apt_repository(ppa)
75+
76+ self.apt_get_update()
77+
78+ if package_list is not None:
79+ self.apt_get_install(package_list)
80+
81+ def str_to_lst(self, string):
82+ result = []
83+ for char in string.split():
84+ result.append(char)
85+
86+ return result
87+
88+ def run_command_on_host(self, command, str_to_list=True, **kwargs):
89+ logger.info('Running command: {}'.format(command))
90+ if str_to_list is True:
91+ command = self.str_to_lst(command)
92+
93+ subprocess.call(command, **kwargs)
94+
95+ def flash_device(
96+ self,
97+ channel='ubuntu-touch/trusty-proposed',
98+ revision_number=None,
99+ bootstrap=False
100+ ):
101+
102+ command = []
103+ command.append('phablet-flash')
104+ command.append('ubuntu-system')
105+ command.append('--serial')
106+ command.append(self.dut_serial)
107+ command.append('--channel')
108+ command.append(channel)
109+ if revision_number is not None:
110+ command.append('--revision')
111+ command.append(revision_number)
112+ if bootstrap is True:
113+ command.append('--bootstrap')
114+
115+ self.run_command_on_host(command, str_to_list=False)
116+
117+ def run_command_on_target(self, command, user=None, shell=False):
118+ if user == 'phablet':
119+ command = 'adb -s {} shell sudo -iu {} bash -ic "{}"'.format(
120+ self.dut_serial, user, command
121+ )
122+ self.run_command_on_host(command)
123+ elif user is None:
124+ command = 'adb -s {} shell {}'.format(self.dut_serial, command)
125+ self.run_command_on_host(command)
126+
127+ def wait_for_device(self, timeout=60):
128+ """wait for the DUT to restart
129+
130+ :param timeout: number of seconds to wait for the system
131+ to settle.
132+ """
133+ command = 'adb -s {} wait-for-device'.format(self.dut_serial)
134+ self.run_command_on_host(command)
135+ logger.info(
136+ 'Waiting {} seconds for the device to settle before '
137+ 'proceeding further.'.format(timeout)
138+ )
139+ time.sleep(timeout)
140+ logger.info('Just to be sure')
141+ self.run_command_on_host(command)
142+
143+ def set_device_read_write(self):
144+ command = 'touch /userdata/.writable_image'
145+ logger.info('Changing system to read/write.')
146+ self.run_command_on_target(command)
147+
148+ def reboot_device(self):
149+ wait = 10
150+ self.run_command_on_target('reboot')
151+ logger.info(
152+ 'Wating {} seconds to ensure target goes away from adb before '
153+ 'waiting for it to come back'.format(wait)
154+ )
155+ time.sleep(wait)
156+ self.wait_for_device()
157+
158+ def disable_intro_screen(self):
159+ self.run_command_on_target(
160+ 'dbus-send --system --print-reply '
161+ '--dest=org.freedesktop.Accounts '
162+ '/org/freedesktop/Accounts/User32011 '
163+ 'org.freedesktop.DBus.Properties.Set '
164+ 'string:com.canonical.unity.AccountsService '
165+ 'string:demo-edges variant:boolean:false')
166+ self.run_command_on_target('touch /.intro_off')
167+
168+ def setup_network(self):
169+ if self.running_in_ci:
170+ command = ('adb -s {} shell nmcli dev wifi '
171+ 'connect ubuntu-qa-g-wpa-d password '
172+ 'qalabwireless'.format(self.dut_serial))
173+ logger.info('Setting up network on target.')
174+ self.run_command_on_host(command)
175+ else:
176+ command = 'phablet-network -s {}'.format(self.dut_serial)
177+ logger.warning(
178+ 'Setting up network on target. you may need to '
179+ 'enter your password for "{}" to complete.'.format(command)
180+ )
181+ self.run_command_on_host(command)
182+
183+ def add_apt_repository(self, ppa):
184+ command = 'add-apt-repository -y {}'.format(ppa)
185+ logger.info('Adding ppa {} on target.'.format(ppa))
186+ self.run_command_on_target(command)
187+
188+ def apt_get_update(self):
189+ command = 'apt-get update'
190+ logger.info('Updating apt database.')
191+ self.run_command_on_target(command)
192+
193+ def apt_get_install(self, packages):
194+ command = 'apt-get -y -f --force-yes install {}'.format(packages)
195+ logger.info('Installing required packages on target.')
196+ self.run_command_on_target(command)
197
198=== added file 'systemimage/tests/autopilot/system_upgrade/helpers/unlock_screen.py'
199--- systemimage/tests/autopilot/system_upgrade/helpers/unlock_screen.py 1970-01-01 00:00:00 +0000
200+++ systemimage/tests/autopilot/system_upgrade/helpers/unlock_screen.py 2014-04-09 20:44:26 +0000
201@@ -0,0 +1,55 @@
202+#!/usr/bin/env python
203+
204+import logging
205+import sys
206+
207+from unity8 import process_helpers as helpers
208+
209+import dbus
210+
211+logging.basicConfig(level=logging.INFO)
212+
213+
214+class UnlockScreen(object):
215+
216+ def __init__(self):
217+ self.powerd = self._get_powerd_interface()
218+
219+ def _get_powerd_interface(self):
220+ bus = dbus.SystemBus()
221+ return bus.get_object(
222+ 'com.canonical.powerd', '/com/canonical/powerd'
223+ )
224+
225+ def _powerd_request_state_active(self):
226+ logging.info('Setting system state "Active"')
227+ return self.powerd.requestSysState(
228+ 'autopilot-lock', 1, dbus_interface='com.canonical.powerd'
229+ )
230+
231+ def _powerd_release_state_active(self):
232+ logging.info('Releasing system state "Active"')
233+ self.powerd.clearSysState(
234+ self.active_cookie, dbus_interface='com.canonical.powerd'
235+ )
236+
237+ def restart_with_testability(self):
238+ self.active_cookie = self._powerd_request_state_active()
239+ helpers.restart_unity_with_testability()
240+ self._powerd_release_state_active()
241+
242+ def unlock_screen(self):
243+ self.restart_with_testability()
244+ helpers.unlock_unity()
245+
246+
247+def help():
248+ print("Usage:")
249+ print("Run the script without any argument to unlock with assertion.")
250+
251+
252+if len(sys.argv) >= 2 and sys.argv[1] == '-h':
253+ help()
254+else:
255+ unlocker = UnlockScreen()
256+ unlocker.unlock_screen()
257
258=== added directory 'systemimage/tests/autopilot/system_upgrade/tests'
259=== added file 'systemimage/tests/autopilot/system_upgrade/tests/__init__.py'
260=== added file 'systemimage/tests/autopilot/system_upgrade/tests/test_system_updates.py'
261--- systemimage/tests/autopilot/system_upgrade/tests/test_system_updates.py 1970-01-01 00:00:00 +0000
262+++ systemimage/tests/autopilot/system_upgrade/tests/test_system_updates.py 2014-04-09 20:44:26 +0000
263@@ -0,0 +1,46 @@
264+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
265+# Copyright 2014 Canonical
266+#
267+# This program is free software: you can redistribute it and/or modify it
268+# under the terms of the GNU General Public License version 3, as published
269+# by the Free Software Foundation.
270+
271+import logging
272+
273+from autopilot.matchers import Eventually
274+from testtools.matchers import Equals
275+
276+from ubuntu_system_settings.emulators import UpdatesPage
277+from ubuntu_system_settings.tests import SystemUpdatesBaseTestCase
278+
279+logging.basicConfig(level=logging.INFO)
280+
281+
282+class SystemUpdatesTestCases(SystemUpdatesBaseTestCase):
283+
284+ """Tests for System Updates."""
285+
286+ def setUp(self):
287+ self.patch_environment('IGNORE_CREDENTIALS', 'True')
288+ super(SystemUpdatesTestCases, self).setUp()
289+
290+ @property
291+ def updates_page(self):
292+ return UpdatesPage(self.app, self.pointer)
293+
294+ def test_image_update_from_ui_works(self):
295+ """Install latest system update available."""
296+ self.updates_page.wait_for_state('UPDATE')
297+ logging.info('Waiting for update to download.')
298+ self.updates_page.wait_for_updates_to_download()
299+ logging.info('finished waiting for update')
300+ self.updates_page.click_install_and_restart_button()
301+ installing_screen = self.updates_page.get_installing_update_screen()
302+ self.assertThat(
303+ installing_screen.visible, Eventually(Equals(True))
304+ )
305+
306+ def test_state_noupdates(self):
307+ """Check if system is fully updated."""
308+ state = self.updates_page.wait_for_state('NOUPDATES')
309+ self.assertThat(state, Equals('NOUPDATES'))
310
311=== added file 'systemimage/tests/autopilot/system_upgrade/variables.py'
312--- systemimage/tests/autopilot/system_upgrade/variables.py 1970-01-01 00:00:00 +0000
313+++ systemimage/tests/autopilot/system_upgrade/variables.py 2014-04-09 20:44:26 +0000
314@@ -0,0 +1,14 @@
315+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
316+# Copyright 2014 Canonical
317+#
318+# This program is free software: you can redistribute it and/or modify it
319+# under the terms of the GNU General Public License version 3, as published
320+# by the Free Software Foundation.
321+
322+
323+TEST1_PART1 = 'system_upgrade.tests.test_system_updates.SystemUpdatesTestCases\
324+.test_image_update_from_ui_works'
325+
326+
327+TEST1_PART2 = 'system_upgrade.tests.test_system_updates.SystemUpdatesTestCases\
328+.test_state_noupdates'
329
330=== added file 'systemimage/tests/autopilot/upgrade_test_runner'
331--- systemimage/tests/autopilot/upgrade_test_runner 1970-01-01 00:00:00 +0000
332+++ systemimage/tests/autopilot/upgrade_test_runner 2014-04-09 20:44:26 +0000
333@@ -0,0 +1,142 @@
334+#!/usr/bin/python3
335+
336+import argparse
337+import logging
338+import os
339+import subprocess
340+import sys
341+import tempfile
342+import time
343+import unittest
344+
345+from system_upgrade.helpers.system_image_setup import DeviceImageFlash
346+from system_upgrade.variables import TEST1_PART1, TEST1_PART2
347+
348+
349+class SystemImageUpgrader(unittest.TestCase, DeviceImageFlash):
350+
351+ source = 'system_upgrade'
352+ test_suite = 'system_upgrade'
353+ device_test_path = '/home/phablet/{}'.format(test_suite)
354+ unlocker = 'unlock_screen.py'
355+ unlocker_location = 'system_upgrade/helpers/' + unlocker
356+
357+ def setUp(self):
358+ self.dut_serial = arguments.serial
359+ self.running_in_ci = arguments.ci
360+ self.setup_device(
361+ ppa=arguments.ppa,
362+ package_list=arguments.packages,
363+ bootstrap=True,
364+ revision_number='-1'
365+ )
366+
367+ def test_system_update(self):
368+ if self.installed_system_wide():
369+ self.run_tests(test_suite=TEST1_PART1)
370+ else:
371+ self.push_and_run_tests(test_suite=TEST1_PART1)
372+ log_file = self.open_log_file()
373+ self.assertTrue(log_file.endswith('OK\n'))
374+
375+ self.wait_for_device(timeout=150)
376+
377+ self.run_tests(test_suite=TEST1_PART2)
378+ log_file = self.open_log_file()
379+ self.assertTrue(log_file.endswith('OK\n'))
380+
381+ def open_log_file(self, retries=10):
382+ for retry in range(retries):
383+ if os.path.exists(self.result_log):
384+ break
385+ time.sleep(1)
386+ retry = retry - 1
387+ else:
388+ raise IOError('file {} not found'.format(self.result_log))
389+
390+ log_file = open(self.result_log, 'r')
391+ self.addCleanup(log_file.close)
392+ return log_file.read()
393+
394+ def push_and_run_tests(self, test_suite):
395+ self._push_files_to_device(self.source, self.device_test_path)
396+ self.run_tests(test_suite)
397+
398+ def run_tests(self, test_suite):
399+ self._unlock_device_screen()
400+ self._run_autopilot_test_on_target(test_suite)
401+
402+ def _push_files_to_device(self, source, location):
403+ self.addCleanup(self._delete_autopilot_test)
404+ command = 'adb -s {} push {} {}'.format(
405+ self.dut_serial, source, location
406+ )
407+
408+ logger.info('Pushing files to target with command:')
409+ logger.info(command)
410+ self.run_command_on_host(command)
411+
412+ def _delete_autopilot_test(self):
413+ command = 'rm -rf {}'.format(self.device_test_path)
414+ self.run_command_on_target(command)
415+
416+ def _unlock_device_screen(self):
417+ command = 'python3 -m system_upgrade.helpers.unlock_screen'
418+ self.run_command_on_target(command, user='phablet')
419+
420+ def _run_autopilot_test_on_target(self, test_suite, user='phablet'):
421+ self.result_log = tempfile.mkstemp()[1]
422+ autopilot_log_target = '/tmp/upgrade.log'
423+ test_command = \
424+ "autopilot run -f text -o {} -v {}".format(
425+ autopilot_log_target, test_suite)
426+ command = 'adb -s {} shell sudo -iu {} bash -ic "{}"'.format(
427+ self.dut_serial, user, test_command)
428+
429+ subprocess.call(command, shell=True)
430+
431+ self.run_command_on_host('adb -s {} pull {} {}'.format(
432+ self.dut_serial, autopilot_log_target, self.result_log)
433+ )
434+ self.addCleanup(os.remove, self.result_log)
435+
436+ def installed_system_wide(self):
437+ """Returns true if the test runner is installed in /usr
438+ or any of its subdirectories.
439+ """
440+ return os.path.abspath('.').startswith('/usr')
441+
442+
443+def _parse_command_line_arguments():
444+ parser = argparse.ArgumentParser(
445+ description='Ubuntu system update test runner'
446+ )
447+ parser.add_argument(
448+ 'serial', metavar='SERIAL_NUMBER', type=str,
449+ help='Serial numer of the device to run tests on.'
450+ )
451+ parser.add_argument(
452+ '--ppa', type=str,
453+ help='Launchpad ppa to add to the device before installing '
454+ 'test packages. example: ppa:test/ppa'
455+ )
456+ parser.add_argument(
457+ '--packages', type=str,
458+ help='Names of extra packages to be installed on the test device '
459+ 'before running the tests.'
460+ )
461+ parser.add_argument(
462+ '--ci', metavar='BOOLEAN', default=False,
463+ help='Specify whether the test suite is running in the Canonical '
464+ 'CI lab so that the network connection could be established '
465+ 'with the appropriate Wi-Fi network.'
466+ )
467+
468+ return parser.parse_args()
469+
470+
471+if __name__ == '__main__':
472+ arguments = _parse_command_line_arguments()
473+ logging.basicConfig(level=logging.INFO)
474+ logger = logging.getLogger(__name__)
475+ unittest.main(argv=[sys.argv[0]])

Subscribers

People subscribed via source and target branches