Merge lp:~canonical-platform-qa/ubuntu-system-tests/snap-channel-support into lp:ubuntu-system-tests

Proposed by Richard Huddie
Status: Merged
Approved by: Santiago Baldassin
Approved revision: 524
Merged at revision: 521
Proposed branch: lp:~canonical-platform-qa/ubuntu-system-tests/snap-channel-support
Merge into: lp:ubuntu-system-tests
Diff against target: 398 lines (+213/-22)
6 files modified
ubuntu_system_tests/helpers/terminal/app.py (+36/-4)
ubuntu_system_tests/host/commands.py (+55/-9)
ubuntu_system_tests/host/target_setup.py (+44/-2)
ubuntu_system_tests/host/targets.py (+11/-0)
ubuntu_system_tests/selftests/test_commands.py (+60/-4)
ubuntu_system_tests/tests/test_launch_apps.py (+7/-3)
To merge this branch: bzr merge lp:~canonical-platform-qa/ubuntu-system-tests/snap-channel-support
Reviewer Review Type Date Requested Status
Santiago Baldassin (community) Approve
platform-qa-bot continuous-integration Approve
Review via email: mp+320393@code.launchpad.net

Commit message

Add support for installing snaps from beta and edge channels.

Description of the change

Changes include:
- Check the beta channel first and use this version, or use edge channel if beta is not found.
- Update target_setup.py to do this during snap install
- Update Target class to do this during test using --upgrade option
- Get the core series and architecture from target rather than using parameters
- Update command helpers to check for snap channel revisions when creating --setup-commands for upgrade
- Update the Terminal app classes to work with both deb and snap versions as snap version would be required when testing on snap based image.
- Update self tests and add new ones for checking snap channel

This should be landed in parallel with: https://code.launchpad.net/~canonical-platform-qa/qa-jenkins-jobs/snap-channel-support/+merge/320420

To post a comment you must log in.
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
524. By Richard Huddie

Merge from trunk.

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Looks good in general. I just have a couple of questions:

1. Why is beta the default channel? Once a "snap" silo is ready for QA, will the snap in question be automatically available in the beta channel?

2. I'm not sure yet about the workflow with Bileto but if the app is not available in the beta channel, I would just make the setup to fail. Installing the snap from --edge could lead us to test the wrong version of the app. right?

For example, let's say that there's a new silo for the calc snap v1.2, stable has 1.0 and edge has 1.1, if 1.2 is not uploaded to --beta, then we would end up executing the test cases against 1.1 and approving the 1.2 silo based on the 1.1 results

Thoughts?

review: Needs Information
Revision history for this message
Richard Huddie (rhuddie) wrote :

> 1. Why is beta the default channel? Once a "snap" silo is ready for QA, will
> the snap in question be automatically available in the beta channel?

This change was requested due to the poor state of snap testing, so we could use more stable versions, rather than always taking the latest development version from edge which might not be fit for testing.

This doesn't take into account silos, I'm not sure how that process works for snaps. It would be possible to add a command option to force it to take a specific channel/revision for specific cases.

>
> 2. I'm not sure yet about the workflow with Bileto but if the app is not
> available in the beta channel, I would just make the setup to fail. Installing
> the snap from --edge could lead us to test the wrong version of the app.
> right?
>
> For example, let's say that there's a new silo for the calc snap v1.2, stable
> has 1.0 and edge has 1.1, if 1.2 is not uploaded to --beta, then we would end
> up executing the test cases against 1.1 and approving the 1.2 silo based on
> the 1.1 results
>
> Thoughts?

I'm also unsure how that process would work. But we could easily add some command options to force a specific channel and revision and fail if it can't find it.

These changes would also be related to the other app changes we're discussing about only having 1 version installed, so I think it would be better to look at adding these options again when we have that sorted.

Revision history for this message
Santiago Baldassin (sbaldassin) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ubuntu_system_tests/helpers/terminal/app.py'
--- ubuntu_system_tests/helpers/terminal/app.py 2017-03-13 14:28:26 +0000
+++ ubuntu_system_tests/helpers/terminal/app.py 2017-03-30 18:35:16 +0000
@@ -18,18 +18,50 @@
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
2020
21from ubuntu_system_tests.helpers.application import Deb21from ubuntu_system_tests.helpers import autopilot
22from ubuntu_system_tests.helpers.application import (
23 Deb,
24 Snap,
25)
26from ubuntu_system_tests.helpers.processes import get_process_id
2227
28APP = 'terminal'
23APP_NAME = 'Terminal'29APP_NAME = 'Terminal'
24APP_ID = 'com.ubuntu.terminal'30APP_ID = 'com.ubuntu.terminal'
25APP_PACKAGE_ID = 'ubuntu-terminal-app'31APP_PACKAGE_ID = 'ubuntu-terminal-app'
2632
2733
28class Terminal(Deb):34class TerminalDeb(Deb):
2935
30 def __init__(self):36 def __init__(self):
31 super().__init__(APP_NAME, APP_ID, APP_PACKAGE_ID)37 super().__init__(APP_NAME, APP_ID, APP_PACKAGE_ID)
3238
33 def get_main_view(self):39 def get_main_view(self):
34 from ubuntu_system_tests.helpers.terminal import cpo # NOQA40 return get_main_view()
35 return self.get_proxy_object().main_view41
42
43class TerminalSnap(Snap):
44
45 def __init__(self):
46 super().__init__(APP_NAME, APP_ID)
47
48 def get_main_view(self):
49 return get_main_view()
50
51
52def get_main_view():
53 from ubuntu_system_tests.helpers.terminal import cpo # NOQA
54 pid = get_process_id(APP)
55 return autopilot.get_proxy_object(pid=pid).main_view
56
57
58def get_terminal_app(mode):
59 """Return terminal application class of required type.
60 :param mode: Either 'snap' or 'deb'.
61 :return: Either TerminalDeb or TerminalSnap class reference.
62 """
63 app_modes = {
64 'deb': TerminalDeb,
65 'snap': TerminalSnap
66 }
67 return app_modes[mode]
3668
=== modified file 'ubuntu_system_tests/host/commands.py'
--- ubuntu_system_tests/host/commands.py 2017-03-09 17:21:07 +0000
+++ ubuntu_system_tests/host/commands.py 2017-03-30 18:35:16 +0000
@@ -19,6 +19,7 @@
19# along with this program. If not, see <http://www.gnu.org/licenses/>.19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#20#
2121
22import requests
22import subprocess23import subprocess
2324
24from ubuntu_system_tests.common import (25from ubuntu_system_tests.common import (
@@ -44,6 +45,23 @@
44legacy_cmds = None45legacy_cmds = None
4546
46XENIAL = '16.04'47XENIAL = '16.04'
48SNAP_STORE_QUERY_URL = (
49 'https://search.apps.ubuntu.com/api/v1/snaps/details/{n}?channel={c}'
50)
51# List of apps to be installed as snaps on a deb based session.
52# Anything else will be installed as a deb.
53# This is used for upgrade command where a list of packages is
54# specified which must be upgraded before running a test.
55SNAP_APPS = [
56 'address-book-app',
57 'camera-app',
58 'gallery-app',
59 'ubuntu-calculator-app',
60 'ubuntu-calendar-app',
61 'ubuntu-clock-app',
62 'ubuntu-filemanager-app',
63 'webbrowser-app',
64]
4765
4866
49def use_legacy_commands():67def use_legacy_commands():
@@ -158,20 +176,48 @@
158def _get_upgrade_setup_command(target, upgrade):176def _get_upgrade_setup_command(target, upgrade):
159 """Return setup command to upgrade csv list of components."""177 """Return setup command to upgrade csv list of components."""
160 if upgrade:178 if upgrade:
179 upgrade_list = upgrade.split(',')
180 cmd = ''
181 series = target.core_series()
182 arch = target.architecture()
161 if target.snap_session():183 if target.snap_session():
162 cmd = ''184 # For a snap session all apps must be snaps
163 for name in upgrade.split(','):185 for name in upgrade_list:
164 cmd += 'snap refresh --devmode --edge {}; '.format(name)186 cmd += _get_snap_channel_upgrade_command(series, arch, name)
165 cmd = cmd.strip()
166 else:187 else:
167 upgrade = upgrade.replace(',', ' ')188 # For a deb session, the apps could be either deb or snap
168 cmd = (189 for name in upgrade_list:
169 'apt-get -y --no-install-recommends install '190 if name in SNAP_APPS:
170 '{}'.format(upgrade))191 cmd += _get_snap_channel_upgrade_command(
171 return ['--setup-commands', _get_fs_rw_command(target) + cmd]192 series, arch, name)
193 else:
194 cmd += _get_deb_upgrade_command(name)
195 return ['--setup-commands', _get_fs_rw_command(target) + cmd.strip()]
172 return []196 return []
173197
174198
199def _get_snap_channel_upgrade_command(series, arch, snap_name):
200 for channel in ['beta', 'edge']:
201 if _is_snap_published_for_channel(series, arch, snap_name, channel):
202 return _get_snap_upgrade_command(snap_name, channel)
203 raise RuntimeError('No channel found for {}.'.format(snap_name))
204
205
206def _get_snap_upgrade_command(app, channel):
207 return 'snap refresh --devmode --{c} {a}; '.format(c=channel, a=app)
208
209
210def _get_deb_upgrade_command(app):
211 return 'apt-get -y --no-install-recommends install {}; '.format(app)
212
213
214def _is_snap_published_for_channel(series, arch, snap, channel):
215 headers = {'X-Ubuntu-Series': series, 'X-Ubuntu-Architecture': arch}
216 url = SNAP_STORE_QUERY_URL.format(n=snap, c=channel)
217 response = requests.get(url, headers=headers)
218 return response.ok
219
220
175def _get_fs_ro_setup_command(target):221def _get_fs_ro_setup_command(target):
176 """Return setup command to set file system read-only."""222 """Return setup command to set file system read-only."""
177 if target.config.get('mount_fs_ro', False):223 if target.config.get('mount_fs_ro', False):
178224
=== modified file 'ubuntu_system_tests/host/target_setup.py'
--- ubuntu_system_tests/host/target_setup.py 2017-03-13 14:28:26 +0000
+++ ubuntu_system_tests/host/target_setup.py 2017-03-30 18:35:16 +0000
@@ -24,7 +24,9 @@
24import dbus24import dbus
25import json25import json
26import os26import os
27import platform
27import pwd28import pwd
29import requests
28import shutil30import shutil
29import subprocess31import subprocess
30import sys32import sys
@@ -48,6 +50,10 @@
48XENIAL = 16.0450XENIAL = 16.04
49YAKKETY = 16.1051YAKKETY = 16.10
5052
53SNAP_STORE_QUERY_URL = (
54 'https://search.apps.ubuntu.com/api/v1/snaps/details/{n}?channel={c}'
55)
56
5157
52class SetupRunner:58class SetupRunner:
5359
@@ -57,6 +63,8 @@
57 self.config = config63 self.config = config
58 self.series = None64 self.series = None
59 self.release = None65 self.release = None
66 self.core_series = None
67 self.arch = None
60 self._set_cache_dir()68 self._set_cache_dir()
6169
62 def _set_cache_dir(self):70 def _set_cache_dir(self):
@@ -95,6 +103,23 @@
95 ['lsb_release', '-cs']).decode().strip()103 ['lsb_release', '-cs']).decode().strip()
96 return self.series104 return self.series
97105
106 def get_core_series(self):
107 """Return the Ubuntu Core series."""
108 if self.core_series is None:
109 self.core_series = subprocess.check_output(
110 'snap version | grep series | tr -s " " | cut -d" " -f2',
111 shell=True).decode().strip()
112 return self.core_series
113
114 def get_architecture(self):
115 """Return the current architecture."""
116 if self.arch is None:
117 if platform.architecture()[0] == '64bit':
118 self.arch = 'amd64'
119 else:
120 self.arch = 'i386'
121 return self.arch
122
98 def update_apt(self):123 def update_apt(self):
99 """Return command to apt-get update."""124 """Return command to apt-get update."""
100 if self.config['update_apt']:125 if self.config['update_apt']:
@@ -182,9 +207,26 @@
182 if snaps:207 if snaps:
183 self.mount_fs_rw()208 self.mount_fs_rw()
184 for snap in snaps:209 for snap in snaps:
210 installed = False
185 cmd = 'refresh' if self.is_snap_installed(snap) else 'install'211 cmd = 'refresh' if self.is_snap_installed(snap) else 'install'
186 subprocess.check_call(212 for channel in ['beta', 'edge']:
187 ['snap', cmd, '--devmode', '--edge', snap])213 if self.is_snap_published_for_channel(snap, channel):
214 channel_arg = '--{}'.format(channel)
215 subprocess.check_call(
216 ['snap', cmd, '--devmode', channel_arg, snap])
217 installed = True
218 break
219 if not installed:
220 raise RuntimeError(
221 'Could not find channel for snap {}'.format(snap))
222
223 def is_snap_published_for_channel(self, snap, channel):
224 arch = self.get_architecture()
225 series = self.get_core_series()
226 headers = {'X-Ubuntu-Series': series, 'X-Ubuntu-Architecture': arch}
227 url = SNAP_STORE_QUERY_URL.format(n=snap, c=channel)
228 response = requests.get(url, headers=headers)
229 return response.ok
188230
189 def is_snap_installed(self, snap):231 def is_snap_installed(self, snap):
190 return os.path.isdir(os.path.join('/snap', snap))232 return os.path.isdir(os.path.join('/snap', snap))
191233
=== modified file 'ubuntu_system_tests/host/targets.py'
--- ubuntu_system_tests/host/targets.py 2017-03-07 16:30:47 +0000
+++ ubuntu_system_tests/host/targets.py 2017-03-30 18:35:16 +0000
@@ -131,6 +131,17 @@
131 def release(self):131 def release(self):
132 return self.run('lsb_release -rs', log_stdout=False).output.strip()132 return self.run('lsb_release -rs', log_stdout=False).output.strip()
133133
134 def core_series(self):
135 return self.run(
136 'snap version | grep series | tr -s " " | cut -d" " -f2 ',
137 log_stdout=False).output.strip()
138
139 def architecture(self):
140 arch = self.run('uname --machine', log_stdout=False).output.strip()
141 if arch == 'x86_64':
142 return 'amd64'
143 return 'i386'
144
134 def user_id(self, user):145 def user_id(self, user):
135 """Return user id for specified user name."""146 """Return user id for specified user name."""
136 return self.run(147 return self.run(
137148
=== modified file 'ubuntu_system_tests/selftests/test_commands.py'
--- ubuntu_system_tests/selftests/test_commands.py 2017-02-24 14:22:54 +0000
+++ ubuntu_system_tests/selftests/test_commands.py 2017-03-30 18:35:16 +0000
@@ -228,12 +228,20 @@
228class MockTarget:228class MockTarget:
229229
230 def __init__(self, **kwargs):230 def __init__(self, **kwargs):
231 self._core_series = '16'
232 self._architecture = 'amd64'
231 for arg in kwargs.keys():233 for arg in kwargs.keys():
232 setattr(self, arg, kwargs[arg])234 setattr(self, arg, kwargs[arg])
233235
234 def snap_session(self):236 def snap_session(self):
235 return self.snap237 return self.snap
236238
239 def core_series(self):
240 return self._core_series
241
242 def architecture(self):
243 return self._architecture
244
237245
238class TestUpgradeSetupCommands(ConfigBaseTestCase):246class TestUpgradeSetupCommands(ConfigBaseTestCase):
239247
@@ -241,14 +249,20 @@
241 super().setUp()249 super().setUp()
242 self.args = DummyArgs(upgrade='package1,package2')250 self.args = DummyArgs(upgrade='package1,package2')
243251
244 def test_get_upgrade_setup_command_snap(self):252 @mock.patch('ubuntu_system_tests.host.commands.'
253 '_is_snap_published_for_channel',
254 return_value=True)
255 def test_get_upgrade_setup_command_snap(self, mock_is_snap_published):
245 target = MockTarget(snap=True, config={'mount_fs_rw': False})256 target = MockTarget(snap=True, config={'mount_fs_rw': False})
246 cmd = commands._get_upgrade_setup_command(target, self.args.upgrade)257 cmd = commands._get_upgrade_setup_command(target, self.args.upgrade)
258 mock_is_snap_published.assert_has_calls([
259 mock.call('16', 'amd64', 'package1', 'beta'),
260 mock.call('16', 'amd64', 'package2', 'beta')])
247 self.assertEqual(261 self.assertEqual(
248 cmd,262 cmd,
249 ['--setup-commands',263 ['--setup-commands',
250 'snap refresh --devmode --edge package1; '264 'snap refresh --devmode --beta package1; '
251 'snap refresh --devmode --edge package2;'])265 'snap refresh --devmode --beta package2;'])
252266
253 def test_get_upgrade_setup_command_deb(self):267 def test_get_upgrade_setup_command_deb(self):
254 target = MockTarget(snap=False, config={'mount_fs_rw': False})268 target = MockTarget(snap=False, config={'mount_fs_rw': False})
@@ -256,9 +270,51 @@
256 self.assertEqual(270 self.assertEqual(
257 cmd,271 cmd,
258 ['--setup-commands',272 ['--setup-commands',
259 'apt-get -y --no-install-recommends install package1 package2'])273 'apt-get -y --no-install-recommends install package1; '
274 'apt-get -y --no-install-recommends install package2;'])
260275
261 def test_get_upgrade_setup_includes_fs_rw(self):276 def test_get_upgrade_setup_includes_fs_rw(self):
262 target = MockTarget(snap=False, config={'mount_fs_rw': True})277 target = MockTarget(snap=False, config={'mount_fs_rw': True})
263 cmd = commands._get_upgrade_setup_command(target, self.args.upgrade)278 cmd = commands._get_upgrade_setup_command(target, self.args.upgrade)
264 self.assertIn('mount -o remount,rw /', cmd[1])279 self.assertIn('mount -o remount,rw /', cmd[1])
280
281
282class TestSnapUpgradeChannelSelection(ConfigBaseTestCase):
283
284 @mock.patch('ubuntu_system_tests.host.commands.'
285 '_is_snap_published_for_channel',
286 return_value=True)
287 def test_beta_channel_first_priority(self, mock_is_snap_published):
288 target = MockTarget(snap=True, config={'mount_fs_rw': False})
289 cmd = commands._get_upgrade_setup_command(target, 'pkg1')
290 self.assertEqual(
291 cmd,
292 ['--setup-commands',
293 'snap refresh --devmode --beta pkg1;'])
294 mock_is_snap_published.assert_has_calls([
295 mock.call('16', 'amd64', 'pkg1', 'beta')])
296
297 @mock.patch('ubuntu_system_tests.host.commands.'
298 '_is_snap_published_for_channel',
299 side_effect=[False, True])
300 def test_edge_channel_second_priority(self, mock_is_snap_published):
301 target = MockTarget(snap=True, config={'mount_fs_rw': False})
302 cmd = commands._get_upgrade_setup_command(target, 'pkg1')
303 self.assertEqual(
304 cmd,
305 ['--setup-commands',
306 'snap refresh --devmode --edge pkg1;'])
307 mock_is_snap_published.assert_has_calls([
308 mock.call('16', 'amd64', 'pkg1', 'beta'),
309 mock.call('16', 'amd64', 'pkg1', 'edge')])
310
311 @mock.patch('ubuntu_system_tests.host.commands.'
312 '_is_snap_published_for_channel',
313 side_effect=[False, False])
314 def test_raises_on_no_channel_found(self, mock_is_snap_published):
315 target = MockTarget(snap=True, config={'mount_fs_rw': False})
316 with self.assertRaises(RuntimeError):
317 commands._get_upgrade_setup_command(target, 'pkg1')
318 mock_is_snap_published.assert_has_calls([
319 mock.call('16', 'amd64', 'pkg1', 'beta'),
320 mock.call('16', 'amd64', 'pkg1', 'edge')])
265321
=== modified file 'ubuntu_system_tests/tests/test_launch_apps.py'
--- ubuntu_system_tests/tests/test_launch_apps.py 2017-03-14 17:05:13 +0000
+++ ubuntu_system_tests/tests/test_launch_apps.py 2017-03-30 18:35:16 +0000
@@ -31,7 +31,9 @@
31from ubuntu_system_tests.helpers.clock.app import Clock31from ubuntu_system_tests.helpers.clock.app import Clock
32from ubuntu_system_tests.helpers.filemanager.app import FileManager32from ubuntu_system_tests.helpers.filemanager.app import FileManager
33from ubuntu_system_tests.helpers.gallery.app import Gallery33from ubuntu_system_tests.helpers.gallery.app import Gallery
34from ubuntu_system_tests.helpers.terminal.app import Terminal34from ubuntu_system_tests.helpers.terminal.app import (
35 get_terminal_app,
36)
35from ubuntu_system_tests.helpers.system_settings.app import (37from ubuntu_system_tests.helpers.system_settings.app import (
36 get_system_settings_app,38 get_system_settings_app,
37)39)
@@ -96,7 +98,8 @@
96 self.check_app_launch(FileManager, 1)98 self.check_app_launch(FileManager, 1)
9799
98 def test_launch_terminal_app(self):100 def test_launch_terminal_app(self):
99 self.check_app_launch(Terminal, 1)101 app = get_terminal_app(self.config_stack.get('unity8_mode'))
102 self.check_app_launch(app, 1)
100103
101 def test_launch_webbrowser_app(self):104 def test_launch_webbrowser_app(self):
102 self.check_app_launch(WebBrowser, 1)105 self.check_app_launch(WebBrowser, 1)
@@ -131,7 +134,8 @@
131 self.check_app_launch(FileManager, 2)134 self.check_app_launch(FileManager, 2)
132135
133 def test_launch_terminal_app_twice(self):136 def test_launch_terminal_app_twice(self):
134 self.check_app_launch(Terminal, 2)137 app = get_terminal_app(self.config_stack.get('unity8_mode'))
138 self.check_app_launch(app, 2)
135139
136 def test_launch_webbrowser_app_twice(self):140 def test_launch_webbrowser_app_twice(self):
137 self.check_app_launch(WebBrowser, 2)141 self.check_app_launch(WebBrowser, 2)

Subscribers

People subscribed via source and target branches

to all changes: