summaryrefslogtreecommitdiffstats
path: root/plugins/eos-updater/tests/manual-test.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/eos-updater/tests/manual-test.py')
-rwxr-xr-xplugins/eos-updater/tests/manual-test.py434
1 files changed, 434 insertions, 0 deletions
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()