systray: add a standalone window impl for uitests

This gives us as much coverage as possible without relying on too much
host desktop config

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-09-01 14:51:30 -04:00
parent 4348d4b128
commit 9b5d9eb9eb
4 changed files with 193 additions and 15 deletions

View File

@ -0,0 +1,15 @@
[org/virt-manager/virt-manager]
system-tray=true
manager-window-width=550
manager-window-height=550
[org/virt-manager/virt-manager/connections]
uris=['test:///default', 'foo:///']
[org/virt-manager/virt-manager/stats]
update-interval=1
[org/virt-manager/virt-manager/vmlist-fields]
disk-usage=false
network-traffic=false

View File

@ -0,0 +1,124 @@
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import tests.utils
from tests.uitests import utils as uiutils
class Systray(uiutils.UITestCase):
"""
UI tests for virt-manager's systray using a fake testing backend
"""
##############
# Test cases #
##############
def testSystrayFake(self):
self.app.open(
keyfile="systray.ini",
extra_opts=["--test-options=fake-systray"],
window_name="Virtual Machine Manager")
manager = self.app.topwin
systray = self.app.root.find("vmm-fake-systray", check_active=False)
uiutils.drag(manager, 1000, 1000)
# Add a connection to trigger systray update
uri = tests.utils.URIs.kvm
manager.find("File", "menu").click()
manager.find("Add Connection...", "menu item").click()
win = self.app.root.find_fuzzy("Add Connection", "dialog")
win.combo_select("Hypervisor", "Custom URI")
win.find("uri-entry", "text").set_text(uri)
win.find("Connect", "push button").click()
# Hide the manager
systray.click_title()
systray.click()
uiutils.check(lambda: not manager.showing)
uiutils.check(lambda: self.app.is_running())
systray.click(button=3)
menu = self.app.root.find("vmm-systray-menu")
def _get_conn_action(connstr, actionstr):
if not menu.showing:
systray.click(button=3)
uiutils.check(lambda: menu.showing)
connmenu = menu.find(connstr, "menu")
connmenu.point()
return connmenu.find(actionstr, "menu")
def _check_conn_action(connstr, actionstr):
item = _get_conn_action(connstr, actionstr)
uiutils.check(lambda: item.showing)
systray.click(button=3)
uiutils.check(lambda: not menu.showing)
def _do_conn_action(connstr, actionstr):
item = _get_conn_action(connstr, actionstr)
item.click()
uiutils.check(lambda: not menu.showing)
def _get_vm_action(connstr, vmname, action):
vmenu = _get_conn_action(connstr, vmname)
vmenu.point()
return vmenu.find(action, "menu")
def _check_vm_action(connstr, vmname, action):
item = _get_vm_action(connstr, vmname, action)
uiutils.check(lambda: item.showing)
systray.click(button=3)
uiutils.check(lambda: not menu.showing)
def _do_vm_action(connstr, vmname, action):
item = _get_vm_action(connstr, vmname, action)
item.click()
uiutils.check(lambda: not menu.showing)
# Right click start a connection
_check_conn_action("QEMU/KVM", "Disconnect")
_do_conn_action("test default", "Connect")
_check_conn_action("test default", "Disconnect")
_do_conn_action("test testdriver", "Disconnect")
_check_conn_action("test testdriver", "Connect")
# Trigger VM change
_do_vm_action("QEMU/KVM", "test-arm-kernel", "Pause")
_check_vm_action("QEMU/KVM", "test-arm-kernel", "Resume")
# Reshow the manager
systray.click()
uiutils.check(lambda: manager.showing)
uiutils.check(lambda: self.app.is_running())
# Close from the menu
systray.click_title()
systray.click(button=3)
menu = self.app.root.find("vmm-systray-menu")
menu.find("Quit", "menu item").click()
uiutils.check(lambda: not self.app.is_running())
def testSystrayToggle(self):
self.app.open(
keyfile="systray.ini",
extra_opts=["--test-options=fake-systray"],
window_name="Virtual Machine Manager")
manager = self.app.topwin
systray = self.app.root.find("vmm-fake-systray", check_active=False)
manager.find("Edit", "menu").click()
manager.find("Preferences", "menu item").click()
prefs = self.app.root.find_fuzzy("Preferences", "frame")
# Close the system tray
prefs.click_title()
prefs.find_fuzzy("Enable system tray", "check").click()
uiutils.check(lambda: not systray.showing)
# Close the manager
manager.click_title()
manager.keyCombo("<alt>F4")
uiutils.check(lambda: not self.app.is_running())

View File

@ -112,6 +112,7 @@ class CLITestOptionsClass:
* fake-console-resolution: Fake viewer console resolution response.
Spice doesn't return values here when we are just testing
against seabios in uitests, this fakes it to hit more code paths
* fake-systray: Enable the fake systray window
"""
def __init__(self, test_options_str):
optset = set()
@ -149,6 +150,7 @@ class CLITestOptionsClass:
self.fake_systemd_success = _get("fake-systemd-success")
self.fake_vnc_username = _get("fake-vnc-username")
self.fake_console_resolution = _get("fake-console-resolution")
self.fake_systray = _get("fake-systray")
if optset: # pragma: no cover
raise RuntimeError("Unknown --test-options keys: %s" % optset)

View File

@ -19,7 +19,7 @@ from .connmanager import vmmConnectionManager
try:
# pylint: disable=ungrouped-imports
from gi.repository import AppIndicator3
except Exception:
except Exception: # pragma: no cover
AppIndicator3 = None
@ -48,7 +48,7 @@ def _conn_disconnect_cb(src, uri):
conn.close()
def _has_appindicator_dbus():
def _has_appindicator_dbus(): # pragma: no cover
try:
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
dbus = Gio.DBusProxy.new_sync(bus, 0, None,
@ -66,7 +66,7 @@ def _has_appindicator_dbus():
_USING_APPINDICATOR = False
if AppIndicator3:
if AppIndicator3: # pragma: no cover
if not _has_appindicator_dbus():
log.debug("AppIndicator3 is available, but didn't "
"find any dbus watcher.")
@ -90,7 +90,7 @@ class _Systray(object):
raise NotImplementedError()
class _SystrayIndicator(_Systray):
class _SystrayIndicator(_Systray): # pragma: no cover
"""
UI backend for appindicator
"""
@ -121,7 +121,7 @@ class _SystrayIndicator(_Systray):
self._icon.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
class _SystrayStatusIcon(_Systray):
class _SystrayStatusIcon(_Systray): # pragma: no cover
"""
UI backend for Gtk StatusIcon
"""
@ -153,9 +153,45 @@ class _SystrayStatusIcon(_Systray):
self._icon.set_visible(False)
class _SystrayWindow(_Systray):
"""
A mock systray implementation that shows its own top level window,
so we can test more of the infrastructure in our ui tests
"""
def __init__(self):
self._window = None
self._menu = None
self._init_ui()
def _cmp(a, b):
return ((a > b) - (a < b))
def _init_ui(self):
image = Gtk.Image()
image.set_from_stock(Gtk.STOCK_ADD, Gtk.IconSize.DIALOG)
box = Gtk.EventBox()
box.add(image)
box.connect("button-press-event", self._popup_cb)
self._window = Gtk.Window()
self._window.set_size_request(100, 100)
self._window.get_accessible().set_name("vmm-fake-systray")
self._window.add(box)
def is_embedded(self):
return self._window.is_visible()
def set_menu(self, menu):
self._menu = menu
def _popup_cb(self, src, event):
if event.button == 1:
_toggle_manager()
else:
self._menu.popup_at_pointer(event)
def show(self):
self._window.show_all()
def hide(self):
self._window.hide()
class _TrayMainMenu(vmmGObject):
@ -183,6 +219,7 @@ class _TrayMainMenu(vmmGObject):
Build the top level conn list menu when clicking the icon
"""
menu = Gtk.Menu()
menu.get_accessible().set_name("vmm-systray-menu")
menu.add(Gtk.SeparatorMenuItem())
exit_item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None)
@ -362,7 +399,7 @@ class vmmSystray(vmmGObject):
return cls._instance
@staticmethod
def systray_disabled_message():
def systray_disabled_message(): # pragma: no cover
if "WAYLAND_DISPLAY" not in os.environ:
return
if _USING_APPINDICATOR:
@ -389,8 +426,9 @@ class vmmSystray(vmmGObject):
def _cleanup(self):
self._hide_systray()
self._systray = None
self._mainmenu.cleanup()
self._mainmenu = None
if self._mainmenu:
self._mainmenu.cleanup()
self._mainmenu = None
###########################
@ -398,9 +436,6 @@ class vmmSystray(vmmGObject):
###########################
def _init_mainmenu(self):
if self._mainmenu:
return
self._mainmenu = _TrayMainMenu()
connmanager = vmmConnectionManager.get_instance()
connmanager.connect("conn-added", self._conn_added_cb)
@ -410,9 +445,11 @@ class vmmSystray(vmmGObject):
def _show_systray(self):
if not self._systray:
if _USING_APPINDICATOR:
if self.config.CLITestOptions.fake_systray:
self._systray = _SystrayWindow()
elif _USING_APPINDICATOR: # pragma: no cover
self._systray = _SystrayIndicator()
else:
else: # pragma: no cover
self._systray = _SystrayStatusIcon()
self._init_mainmenu()
self._systray.set_menu(self._mainmenu.get_menu())