diff options
Diffstat (limited to 'plugins/eos-updater/tests/manual-test.py')
-rwxr-xr-x | plugins/eos-updater/tests/manual-test.py | 434 |
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() |