summaryrefslogtreecommitdiffstats
path: root/plugins/eos-updater/tests
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/eos-updater/tests')
-rw-r--r--plugins/eos-updater/tests/eos_updater.py414
-rwxr-xr-xplugins/eos-updater/tests/manual-test.py434
2 files changed, 848 insertions, 0 deletions
diff --git a/plugins/eos-updater/tests/eos_updater.py b/plugins/eos-updater/tests/eos_updater.py
new file mode 100644
index 0000000..5e4aa8d
--- /dev/null
+++ b/plugins/eos-updater/tests/eos_updater.py
@@ -0,0 +1,414 @@
+'''eos-updater mock template
+
+This creates a mock eos-updater interface (com.endlessm.Updater), with several
+methods on the Mock sidecar interface which allow its internal state flow to be
+controlled.
+
+A typical call chain for this would be:
+ - Test harness calls SetPollAction('update', {}, '', '')
+ - SUT calls Poll()
+ - Test harness calls FinishPoll()
+ - SUT calls Fetch()
+ - Test harness calls FinishFetch()
+ - SUT calls Apply()
+ - Test harness calls FinishApply()
+
+Errors can be simulated by specifying an `early-error` or `late-error` as the
+action in a Set*Action() call. `early-error` will result in the associated
+Poll() call (for example) transitioning to the error state. `late-error` will
+result in a transition to the error state only once (for example) FinishPoll()
+is called.
+
+See the implementation of each Set*Action() method for the set of actions it
+supports.
+
+Usage:
+ python3 -m dbusmock \
+ --template ./plugins/eos-updater/tests/mock-eos-updater.py
+'''
+
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version. See http://www.gnu.org/copyleft/lgpl.html for the full
+# text of the license.
+#
+# The LGPL 2.1+ has been chosen as that’s the license eos-updater is under.
+
+from enum import IntEnum
+from gi.repository import GLib
+import time
+
+import dbus
+import dbus.mainloop.glib
+from dbusmock import MOCK_IFACE
+
+
+__author__ = 'Philip Withnall'
+__email__ = 'withnall@endlessm.com'
+__copyright__ = '© 2019 Endless Mobile Inc.'
+__license__ = 'LGPL 2.1+'
+
+
+class UpdaterState(IntEnum):
+ NONE = 0
+ READY = 1
+ ERROR = 2
+ POLLING = 3
+ UPDATE_AVAILABLE = 4
+ FETCHING = 5
+ UPDATE_READY = 6
+ APPLYING_UPDATE = 7
+ UPDATE_APPLIED = 8
+
+
+BUS_NAME = 'com.endlessm.Updater'
+MAIN_OBJ = '/com/endlessm/Updater'
+MAIN_IFACE = 'com.endlessm.Updater'
+SYSTEM_BUS = True
+
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+
+def load(mock, parameters):
+ mock.AddProperties(
+ MAIN_IFACE,
+ dbus.Dictionary({
+ 'State': dbus.UInt32(parameters.get('State', 1)),
+ 'UpdateID': dbus.String(parameters.get('UpdateID', '')),
+ 'UpdateRefspec': dbus.String(parameters.get('UpdateRefspec', '')),
+ 'OriginalRefspec':
+ dbus.String(parameters.get('OriginalRefspec', '')),
+ 'CurrentID': dbus.String(parameters.get('CurrentID', '')),
+ 'UpdateLabel': dbus.String(parameters.get('UpdateLabel', '')),
+ 'UpdateMessage': dbus.String(parameters.get('UpdateMessage', '')),
+ 'Version': dbus.String(parameters.get('Version', '')),
+ 'DownloadSize': dbus.Int64(parameters.get('DownloadSize', 0)),
+ 'DownloadedBytes':
+ dbus.Int64(parameters.get('DownloadedBytes', 0)),
+ 'UnpackedSize': dbus.Int64(parameters.get('UnpackedSize', 0)),
+ 'FullDownloadSize':
+ dbus.Int64(parameters.get('FullDownloadSize', 0)),
+ 'FullUnpackedSize':
+ dbus.Int64(parameters.get('FullUnpackedSize', 0)),
+ 'ErrorCode': dbus.UInt32(parameters.get('ErrorCode', 0)),
+ 'ErrorName': dbus.String(parameters.get('ErrorName', '')),
+ 'ErrorMessage': dbus.String(parameters.get('ErrorMessage', '')),
+ }, signature='sv'))
+
+ # Set up initial state
+ mock.__poll_action = 'no-update'
+ mock.__fetch_action = 'success'
+ mock.__apply_action = 'success'
+
+ # Set up private methods
+ mock.__set_properties = __set_properties
+ mock.__change_state = __change_state
+ mock.__set_error = __set_error
+ mock.__check_state = __check_state
+
+
+#
+# Internal utility methods
+#
+
+# Values in @properties must have variant_level≥1
+def __set_properties(self, iface, properties):
+ for key, value in properties.items():
+ self.props[iface][key] = value
+ self.EmitSignal(dbus.PROPERTIES_IFACE, 'PropertiesChanged', 'sa{sv}as', [
+ iface,
+ properties,
+ [],
+ ])
+
+
+def __change_state(self, new_state):
+ props = {
+ 'State': dbus.UInt32(new_state, variant_level=1)
+ }
+
+ # Reset error state if necessary.
+ if new_state != UpdaterState.ERROR and \
+ self.props[MAIN_IFACE]['ErrorName'] != '':
+ props['ErrorCode'] = dbus.UInt32(0, variant_level=1)
+ props['ErrorName'] = dbus.String('', variant_level=1)
+ props['ErrorMessage'] = dbus.String('', variant_level=1)
+
+ self.__set_properties(self, MAIN_IFACE, props)
+ self.EmitSignal(MAIN_IFACE, 'StateChanged', 'u', [dbus.UInt32(new_state)])
+
+
+def __set_error(self, error_name, error_message):
+ assert(error_name != '')
+
+ self.__set_properties(self, MAIN_IFACE, {
+ 'ErrorName': dbus.String(error_name, variant_level=1),
+ 'ErrorMessage': dbus.String(error_message, variant_level=1),
+ 'ErrorCode': dbus.UInt32(1, variant_level=1),
+ })
+ self.__change_state(self, UpdaterState.ERROR)
+
+
+def __check_state(self, allowed_states):
+ if self.props[MAIN_IFACE]['State'] not in allowed_states:
+ raise dbus.exceptions.DBusException(
+ 'Call not allowed in this state',
+ name='com.endlessm.Updater.Error.WrongState')
+
+
+#
+# Updater methods which are too big for squeezing into AddMethod()
+#
+
+@dbus.service.method(MAIN_IFACE, in_signature='', out_signature='')
+def Poll(self):
+ self.__check_state(self, set([
+ UpdaterState.READY,
+ UpdaterState.UPDATE_AVAILABLE,
+ UpdaterState.UPDATE_READY,
+ UpdaterState.ERROR,
+ ]))
+
+ self.__change_state(self, UpdaterState.POLLING)
+
+ if self.__poll_action == 'early-error':
+ time.sleep(0.5)
+ self.__set_error(self, self.__poll_error_name,
+ self.__poll_error_message)
+ else:
+ # we now expect the test harness to call FinishPoll() on the mock
+ # interface
+ pass
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='s', out_signature='')
+def PollVolume(self, path):
+ # FIXME: Currently unsupported
+ return self.Poll()
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='', out_signature='')
+def Fetch(self):
+ return self.FetchFull()
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='a{sv}', out_signature='')
+def FetchFull(self, options=None):
+ self.__check_state(self, set([UpdaterState.UPDATE_AVAILABLE]))
+
+ self.__change_state(self, UpdaterState.FETCHING)
+
+ if self.__fetch_action == 'early-error':
+ time.sleep(0.5)
+ self.__set_error(self, self.__fetch_error_name,
+ self.__fetch_error_message)
+ else:
+ # we now expect the test harness to call FinishFetch() on the mock
+ # interface
+ pass
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='', out_signature='')
+def Apply(self):
+ self.__check_state(self, set([UpdaterState.UPDATE_READY]))
+
+ self.__change_state(self, UpdaterState.APPLYING_UPDATE)
+
+ if self.__apply_action == 'early-error':
+ time.sleep(0.5)
+ self.__set_error(self, self.__apply_error_name,
+ self.__apply_error_message)
+ else:
+ # we now expect the test harness to call FinishApply() on the mock
+ # interface
+ pass
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='', out_signature='')
+def Cancel(self):
+ self.__check_state(self, set([
+ UpdaterState.POLLING,
+ UpdaterState.FETCHING,
+ UpdaterState.APPLYING_UPDATE,
+ ]))
+
+ time.sleep(1)
+ self.__set_error(self, 'com.endlessm.Updater.Error.Cancelled',
+ 'Update was cancelled')
+
+
+#
+# Convenience methods on the mock
+#
+
+@dbus.service.method(MOCK_IFACE, in_signature='sa{sv}ss', out_signature='')
+def SetPollAction(self, action, update_properties, error_name, error_message):
+ '''Set the action to happen when the SUT calls Poll().
+
+ This sets the action which will happen when Poll() (and subsequently
+ FinishPoll()) are called, including the details of the error which will be
+ returned or the new update which will be advertised.
+ '''
+ # Provide a default update.
+ if not update_properties:
+ update_properties = {
+ 'UpdateID': dbus.String('f' * 64, variant_level=1),
+ 'UpdateRefspec':
+ dbus.String('remote:new-refspec', variant_level=1),
+ 'OriginalRefspec':
+ dbus.String('remote:old-refspec', variant_level=1),
+ 'CurrentID': dbus.String('1' * 64, variant_level=1),
+ 'UpdateLabel': dbus.String('New OS Update', variant_level=1),
+ 'UpdateMessage':
+ dbus.String('Some release notes.', variant_level=1),
+ 'Version': dbus.String('3.7.0', variant_level=1),
+ 'DownloadSize': dbus.Int64(1000000000, variant_level=1),
+ 'UnpackedSize': dbus.Int64(1500000000, variant_level=1),
+ 'FullDownloadSize': dbus.Int64(1000000000 * 0.8, variant_level=1),
+ 'FullUnpackedSize': dbus.Int64(1500000000 * 0.8, variant_level=1),
+ }
+
+ self.__poll_action = action
+ self.__poll_update_properties = update_properties
+ self.__poll_error_name = error_name
+ self.__poll_error_message = error_message
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='', out_signature='')
+def FinishPoll(self):
+ self.__check_state(self, set([UpdaterState.POLLING]))
+
+ if self.__poll_action == 'no-update':
+ self.__change_state(self, UpdaterState.READY)
+ elif self.__poll_action == 'update':
+ assert(set([
+ 'UpdateID',
+ 'UpdateRefspec',
+ 'OriginalRefspec',
+ 'CurrentID',
+ 'UpdateLabel',
+ 'UpdateMessage',
+ 'Version',
+ 'FullDownloadSize',
+ 'FullUnpackedSize',
+ 'DownloadSize',
+ 'UnpackedSize',
+ ]) <= set(self.__poll_update_properties.keys()))
+
+ # Set the initial DownloadedBytes based on whether we know the full
+ # download size.
+ props = self.__poll_update_properties
+ if props['DownloadSize'] < 0:
+ props['DownloadedBytes'] = dbus.Int64(-1, variant_level=1)
+ else:
+ props['DownloadedBytes'] = dbus.Int64(0, variant_level=1)
+
+ self.__set_properties(self, MAIN_IFACE, props)
+ self.__change_state(self, UpdaterState.UPDATE_AVAILABLE)
+ elif self.__poll_action == 'early-error':
+ # Handled in Poll() itself.
+ pass
+ elif self.__poll_action == 'late-error':
+ self.__set_error(self, self.__poll_error_name,
+ self.__poll_error_message)
+ else:
+ assert(False)
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='sss', out_signature='')
+def SetFetchAction(self, action, error_name, error_message):
+ '''Set the action to happen when the SUT calls Fetch().
+
+ This sets the action which will happen when Fetch() (and subsequently
+ FinishFetch()) are called, including the details of the error which will be
+ returned, if applicable.
+ '''
+ self.__fetch_action = action
+ self.__fetch_error_name = error_name
+ self.__fetch_error_message = error_message
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='', out_signature='',
+ async_callbacks=('success_cb', 'error_cb'))
+def FinishFetch(self, success_cb, error_cb):
+ '''Finish a pending client call to Fetch().
+
+ This is implemented using async_callbacks since if the fetch action is
+ ‘success’ it will block until the simulated download is complete, emitting
+ download progress signals throughout. As it’s implemented asynchronously,
+ this allows any calls to Cancel() to be handled by the mock service
+ part-way through the fetch.
+ '''
+ self.__check_state(self, set([UpdaterState.FETCHING]))
+
+ if self.__fetch_action == 'success':
+ # Simulate the download.
+ i = 0
+ download_size = self.props[MAIN_IFACE]['DownloadSize']
+
+ def _download_progress_cb():
+ nonlocal i
+
+ # Allow cancellation.
+ if self.props[MAIN_IFACE]['State'] != UpdaterState.FETCHING:
+ return False
+
+ downloaded_bytes = (i / 100.0) * download_size
+ self.__set_properties(self, MAIN_IFACE, {
+ 'DownloadedBytes':
+ dbus.Int64(downloaded_bytes, variant_level=1),
+ })
+
+ i += 1
+
+ # Keep looping until the download is complete.
+ if i <= 100:
+ return True
+
+ # When the download is complete, change the service state and
+ # finish the asynchronous FinishFetch() call.
+ self.__change_state(self, UpdaterState.UPDATE_READY)
+ success_cb()
+ return False
+
+ GLib.timeout_add(100, _download_progress_cb)
+ elif self.__fetch_action == 'early-error':
+ # Handled in Fetch() itself.
+ success_cb()
+ elif self.__fetch_action == 'late-error':
+ self.__set_error(self, self.__fetch_error_name,
+ self.__fetch_error_message)
+ success_cb()
+ else:
+ assert(False)
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='sss', out_signature='')
+def SetApplyAction(self, action, error_name, error_message):
+ '''Set the action to happen when the SUT calls Apply().
+
+ This sets the action which will happen when Apply() (and subsequently
+ FinishApply()) are called, including the details of the error which will be
+ returned, if applicable.
+ '''
+ self.__apply_action = action
+ self.__apply_error_name = error_name
+ self.__apply_error_message = error_message
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='', out_signature='')
+def FinishApply(self):
+ self.__check_state(self, set([UpdaterState.APPLYING_UPDATE]))
+
+ if self.__apply_action == 'success':
+ self.__change_state(self, UpdaterState.UPDATE_APPLIED)
+ elif self.__apply_action == 'early-error':
+ # Handled in Apply() itself.
+ pass
+ elif self.__apply_action == 'late-error':
+ self.__set_error(self, self.__apply_error_name,
+ self.__apply_error_message)
+ else:
+ assert(False)
diff --git a/plugins/eos-updater/tests/manual-test.py b/plugins/eos-updater/tests/manual-test.py
new file mode 100755
index 0000000..b6413d9
--- /dev/null
+++ b/plugins/eos-updater/tests/manual-test.py
@@ -0,0 +1,434 @@
+#!/usr/bin/python3
+
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1+ of the License, or (at your option)
+# any later version. See http://www.gnu.org/copyleft/lgpl.html for the full
+# text of the license.
+#
+# The LGPL 2.1+ has been chosen as that’s the license eos-updater is under.
+
+
+from enum import IntEnum
+import os
+import time
+import unittest
+import dbus
+import dbusmock
+import ddt
+
+
+__author__ = 'Philip Withnall'
+__email__ = 'withnall@endlessm.com'
+__copyright__ = '© 2019 Endless Mobile Inc.'
+__license__ = 'LGPL 2.1+'
+
+
+class UpdaterState(IntEnum):
+ '''eos-updater states; see its State property'''
+ NONE = 0
+ READY = 1
+ ERROR = 2
+ POLLING = 3
+ UPDATE_AVAILABLE = 4
+ FETCHING = 5
+ UPDATE_READY = 6
+ APPLYING_UPDATE = 7
+ UPDATE_APPLIED = 8
+
+
+@ddt.ddt
+class ManualTest(dbusmock.DBusTestCase):
+ '''A manual test of the eos-updater plugin in gnome-software.
+
+ It creates a mock eos-updater D-Bus daemon, on the real system bus (because
+ otherwise gnome-software’s other plugins can’t communicate with their
+ system daemons; to fix this, we’d need to mock those up too). The test
+ harness provides the user with instructions about how to run gnome-software
+ and what to do in it, waiting for them to press enter between steps.
+
+ FIXME: This test could potentially eventually be automated by doing the UI
+ steps using Dogtail or OpenQA.
+
+ It tests various classes of interaction between the plugin and the daemon:
+ normal update process (with and without an update available); error returns
+ from the daemon; cancellation of the daemon by another process;
+ cancellation of the daemon from gnome-software; and the daemon unexpectedly
+ going away (i.e. crashing).
+ '''
+
+ @classmethod
+ def setUpClass(cls):
+ # FIXME: See the comment below about why we currently run on the actual
+ # system bus.
+ # cls.start_system_bus()
+ cls.dbus_con = cls.get_dbus(True)
+
+ def setUp(self):
+ # Work out the path to the dbusmock template in the same directory as
+ # this file.
+ self_path = os.path.dirname(os.path.realpath(__file__))
+ template_path = os.path.join(self_path, 'eos_updater.py')
+
+ # Spawn a python-dbusmock server. Use the actual system bus, since
+ # gnome-software needs to access various other services (such as
+ # packagekit) which we don’t currently mock (FIXME).
+ (self.p_mock, self.obj_eos_updater) = self.spawn_server_template(
+ template_path, {}, stdout=None)
+ self.dbusmock = dbus.Interface(self.obj_eos_updater,
+ dbusmock.MOCK_IFACE)
+
+ def tearDown(self):
+ self.kill_gnome_software()
+ self.p_mock.terminate()
+ self.p_mock.wait()
+
+ def launch_gnome_software(self):
+ '''Instruct the user to launch gnome-software'''
+ print('Launch gnome-software with:')
+ print('gnome-software --verbose')
+ self.manual_check('Press enter to continue')
+
+ def kill_gnome_software(self):
+ '''Instruct the user to kill gnome-software'''
+ print('Kill gnome-software with:')
+ print('pkill gnome-software')
+ self.manual_check('Press enter to continue')
+
+ def await_state(self, state):
+ '''Block until eos-updater reaches the given `state`'''
+ print('Awaiting state %u' % state)
+ props_iface = dbus.Interface(self.obj_eos_updater,
+ dbus.PROPERTIES_IFACE)
+ while props_iface.Get('com.endlessm.Updater', 'State') != state:
+ time.sleep(0.2)
+
+ def manual_check(self, prompt):
+ '''Instruct the user to do a manual check and block until done'''
+ input('\033[92;1m' + prompt + '\033[0m\n')
+
+ def test_poll_no_update(self):
+ '''Test that no updates are shown if eos-updater successfully says
+ there are none.'''
+ self.dbusmock.SetPollAction(
+ 'no-update', dbus.Dictionary({}, signature='sv'), '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check there are no EOS updates listed')
+ self.await_state(UpdaterState.READY)
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.LiveBoot',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.NotOstreeSystem',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_poll_early_error(self, error_name):
+ '''Test that a D-Bus error return from Poll() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'early-error', dbus.Dictionary({}, signature='sv'),
+ error_name, 'Some error message.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.ERROR)
+
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check there are no EOS updates listed, and a '
+ 'GsPluginEosUpdater error is printed on the '
+ 'terminal')
+ else:
+ self.manual_check('Check there are no EOS updates listed, and no '
+ 'GsPluginEosUpdater cancellation error is '
+ 'printed on the terminal')
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.LiveBoot',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.NotOstreeSystem',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_poll_late_error(self, error_name):
+ '''Test that a transition to the Error state after successfully calling
+ Poll() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'late-error', dbus.Dictionary({}, signature='sv'),
+ error_name, 'Some error message.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check there are no EOS updates listed, and a '
+ 'GsPluginEosUpdater error is printed on the '
+ 'terminal')
+ else:
+ self.manual_check('Check there are no EOS updates listed, and no '
+ 'GsPluginEosUpdater cancellation error is '
+ 'printed on the terminal')
+ self.await_state(UpdaterState.ERROR)
+
+ @ddt.data(True, False)
+ def test_update_available(self, manually_refresh):
+ '''Test that the entire update process works if an update is
+ available.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+ self.dbusmock.SetApplyAction('success', '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ if manually_refresh:
+ self.manual_check('Check an EOS update is listed; press the '
+ 'Refresh button')
+
+ # TODO: if you proceed through the test slowly, this sometimes doesn’t
+ # work
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.manual_check('Check the download has paused at ~75% complete '
+ '(waiting to apply)')
+ self.await_state(UpdaterState.APPLYING_UPDATE)
+ self.dbusmock.FinishApply()
+
+ self.manual_check('Check the banner says to ‘Restart Now’ (don’t '
+ 'click it)')
+ self.await_state(UpdaterState.UPDATE_APPLIED)
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.Fetching',
+ 'com.endlessm.Updater.Error.MalformedAutoinstallSpec',
+ 'com.endlessm.Updater.Error.UnknownEntryInAutoinstallSpec',
+ 'com.endlessm.Updater.Error.FlatpakRemoteConflict',
+ 'com.endlessm.Updater.Error.MeteredConnection',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_fetch_early_error(self, error_name):
+ '''Test that a D-Bus error return from Fetch() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('early-error', error_name,
+ 'Some error or other.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check a fetch error is displayed')
+ else:
+ self.manual_check('Check no cancellation error is displayed')
+
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+ self.manual_check('Check an EOS update is listed again')
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.Fetching',
+ 'com.endlessm.Updater.Error.MalformedAutoinstallSpec',
+ 'com.endlessm.Updater.Error.UnknownEntryInAutoinstallSpec',
+ 'com.endlessm.Updater.Error.FlatpakRemoteConflict',
+ 'com.endlessm.Updater.Error.MeteredConnection',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_fetch_late_error(self, error_name):
+ '''Test that a transition to the Error state after successfully calling
+ Fetch() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('late-error', error_name,
+ 'Some error or other.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.await_state(UpdaterState.ERROR)
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check a fetch error is displayed')
+ else:
+ self.manual_check('Check no cancellation error is displayed')
+
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+ self.manual_check('Check an EOS update is listed again')
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_apply_early_error(self, error_name):
+ '''Test that a D-Bus error return from Apply() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+ self.dbusmock.SetApplyAction('early-error', error_name,
+ 'Some error or other.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.await_state(UpdaterState.ERROR)
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check an apply error is displayed after the '
+ 'update reached ~75% completion')
+ else:
+ self.manual_check('Check no cancellation error is displayed after '
+ 'the update reached ~75% completion')
+
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+ self.manual_check('Check an EOS update is listed again')
+
+ @ddt.data('com.endlessm.Updater.Error.WrongState',
+ 'com.endlessm.Updater.Error.WrongConfiguration',
+ 'com.endlessm.Updater.Error.Cancelled')
+ def test_apply_late_error(self, error_name):
+ '''Test that a transition to the Error state after successfully calling
+ Apply() is handled correctly.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+ self.dbusmock.SetApplyAction('late-error', error_name,
+ 'Some error or other.')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.manual_check('Check the download has paused at ~75% complete '
+ '(waiting to apply)')
+ self.await_state(UpdaterState.APPLYING_UPDATE)
+ self.dbusmock.FinishApply()
+
+ self.await_state(UpdaterState.ERROR)
+ if error_name != 'com.endlessm.Updater.Error.Cancelled':
+ self.manual_check('Check an apply error is displayed')
+ else:
+ self.manual_check('Check no cancellation error is displayed')
+
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+ self.manual_check('Check an EOS update is listed again')
+
+ def test_no_eos_updater_running(self):
+ '''Test that the plugin doesn’t make a fuss if eos-updater is
+ unavailable.'''
+ self.p_mock.kill()
+
+ self.launch_gnome_software()
+
+ self.manual_check('Check there are no EOS updates listed, and no '
+ 'errors shown')
+
+ def test_fetch_ui_cancellation(self):
+ '''Test that cancelling a download from the UI works correctly.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button, then shortly afterwards press the Cancel '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.await_state(UpdaterState.ERROR)
+ self.manual_check('Check a fetch cancellation error is displayed')
+
+ def test_poll_eos_updater_dies(self):
+ '''Test that gnome-software recovers if eos-updater dies while
+ polling for updates.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.p_mock.kill()
+
+ self.manual_check('Check no error is shown for the poll failure')
+ self.setUp()
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+
+ self.manual_check('Press the Refresh button and check an update is '
+ 'shown')
+ # TODO: It may take a few minutes for the update to appear on the
+ # updates page
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ def test_fetch_eos_updater_dies(self):
+ '''Test that gnome-software recovers if eos-updater dies while
+ fetching an update.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.p_mock.kill()
+
+ self.manual_check('Check an error is shown for the fetch failure')
+
+ def test_apply_eos_updater_dies(self):
+ '''Test that gnome-software recovers if eos-updater dies while
+ applying an update.'''
+ self.dbusmock.SetPollAction(
+ 'update', dbus.Dictionary({}, signature='sv'), '', '')
+ self.dbusmock.SetFetchAction('success', '', '')
+ self.dbusmock.SetApplyAction('success', '', '')
+
+ self.launch_gnome_software()
+ self.await_state(UpdaterState.POLLING)
+ self.dbusmock.FinishPoll()
+
+ self.manual_check('Check an EOS update is listed; press the Download '
+ 'button')
+ self.await_state(UpdaterState.FETCHING)
+ self.dbusmock.FinishFetch()
+
+ self.manual_check('Check the download has paused at ~75% complete '
+ '(waiting to apply)')
+ self.await_state(UpdaterState.APPLYING_UPDATE)
+ self.p_mock.kill()
+
+ self.manual_check('Check an error is shown for the apply failure')
+
+
+if __name__ == '__main__':
+ unittest.main()