uitests: Finish console.py and viewers.py coverage

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-08-29 13:32:37 -04:00
parent e414fc3bdc
commit c36f232370
7 changed files with 268 additions and 54 deletions

View File

@ -717,3 +717,156 @@ class Details(uiutils.UITestCase):
# Do standard xmleditor tests
self._test_xmleditor_interactions(win, finish)
def testDetailsConsoleChecksSSH(self):
"""
Trigger a bunch of console connection failures to hit
various details/* code paths
"""
fakeuri = "qemu+ssh://foouser@256.256.256.256:1234/system"
uri = tests.utils.URIs.test_full + ",fakeuri=%s" % fakeuri
self.app.uri = uri
self.app.open(xmleditor_enabled=True)
self.app.topwin.find("test\n", "table cell").doubleClick()
win = self.app.root.find("test on", "frame")
conpages = win.find("console-pages")
run = win.find("Run", "push button")
shutdown = win.find("Shut Down", "push button")
conbtn = win.find("Console", "radio button")
detailsbtn = win.find("Details", "radio button")
def _run():
win.click_title()
run.click()
uiutils.check(lambda: not run.sensitive)
def _stop():
shutdown.click()
uiutils.check(lambda: not shutdown.sensitive)
def _checkcon(msg):
conbtn.click()
uiutils.check(lambda: conpages.showing)
conpages.find(msg)
def _check_textconsole_menu(msg):
vmenu = win.find("^View$", "menu")
vmenu.click()
tmenu = win.find("Text Consoles", "menu")
tmenu.point()
tmenu.find(msg, "menu item")
vmenu.click()
# Check initial state
_checkcon("Graphical console not configured")
_stop()
_check_textconsole_menu("No graphical console available")
# Add a SDL graphics device which can't be displayed
detailsbtn.click()
win.find("add-hardware", "push button").click()
addhw = self.app.root.find("Add New Virtual Hardware", "frame")
addhw.find("Graphics", "table cell").click()
addhw.find("XML", "page tab").click()
dev = '<graphics type="sdl" display=":3.4" xauth="/tmp/.Xauthority"/>'
addhw.find("XML editor").text = dev
addhw.find("Finish", "push button").click()
uiutils.check(lambda: not addhw.active)
uiutils.check(lambda: win.active)
_run()
_checkcon("Cannot display graphical console type")
def _change_gfx_xml(_xml):
detailsbtn.click()
win.find("Display ", "table cell").click()
win.find("XML", "page tab").click()
win.find("XML editor").set_text(_xml)
win.find("config-apply").click()
# Listening from some other address
_stop()
xml = '<graphics type="spice" listen="0.0.0.0" port="6000" tlsPort="6001"/>'
_change_gfx_xml(xml)
_run()
_checkcon(".*resolving.*256.256.256.256.*")
# Listening from some other address
_stop()
xml = '<graphics type="spice" listen="257.0.0.1" port="6000"/>'
_change_gfx_xml(xml)
_run()
_checkcon(".*resolving.*257.0.0.1.*")
# Hit a specific error about tls only and ssh
_stop()
xml = '<graphics type="spice" tlsPort="60001" autoport="no"/>'
_change_gfx_xml(xml)
_run()
_checkcon(".*configured for TLS only.*")
# Fake a socket connection
_stop()
xml = '<graphics type="vnc" socket="/tmp/foobar.sock"/>'
_change_gfx_xml(xml)
_run()
_checkcon(".*SSH tunnel error output.*")
# Add a listen type='none' check
_stop()
xml = '<graphics type="spice"><listen type="none"/></graphics>'
_change_gfx_xml(xml)
_run()
_checkcon(".*local file descriptor.*")
# Add a local list + port check
_stop()
xml = '<graphics type="spice" listen="127.0.0.1" port="6000" tlsPort="60001"/>'
_change_gfx_xml(xml)
_run()
_checkcon(".*SSH tunnel error output.*")
def testDetailsConsoleChecksTCP(self):
"""
Hit a specific warning when the connection has
non-SSH transport but the guest config is only listening locally
"""
fakeuri = "qemu+tcp://foouser@256.256.256.256:1234/system"
uri = tests.utils.URIs.test_full + ",fakeuri=%s" % fakeuri
self.app.uri = uri
self.app.open(xmleditor_enabled=True)
self.app.topwin.find("test\n", "table cell").doubleClick()
win = self.app.root.find("test on", "frame")
conpages = win.find("console-pages")
run = win.find("Run", "push button")
shutdown = win.find("Shut Down", "push button")
conbtn = win.find("Console", "radio button")
detailsbtn = win.find("Details", "radio button")
def _run():
win.click_title()
run.click()
uiutils.check(lambda: not run.sensitive)
def _stop():
shutdown.click()
uiutils.check(lambda: not shutdown.sensitive)
def _checkcon(msg):
conbtn.click()
uiutils.check(lambda: conpages.showing)
conpages.find(msg)
# Check initial state
_checkcon("Graphical console not configured")
_stop()
# Add a SDL graphics device which can't be displayed
detailsbtn.click()
win.find("add-hardware", "push button").click()
addhw = self.app.root.find("Add New Virtual Hardware", "frame")
addhw.find("Graphics", "table cell").click()
addhw.find("XML", "page tab").click()
dev = '<graphics type="vnc" port="6000" address="127.0.0.1"/>'
addhw.find("XML editor").text = dev
addhw.find("Finish", "push button").click()
uiutils.check(lambda: not addhw.active)
uiutils.check(lambda: win.active)
_run()
_checkcon(".*configured to listen locally.*")

View File

@ -28,7 +28,7 @@ def _vm_wrapper(vmname, uri="qemu:///system", opts=None):
extra_opts = (opts or [])
extra_opts += ["--show-domain-console", vmname]
self.app.open(extra_opts=extra_opts)
fn(self, *args, **kwargs)
fn(self, dom, *args, **kwargs)
finally:
try:
self.app.stop()
@ -55,7 +55,7 @@ class Console(uiutils.UITestCase):
# Test cases #
##############
def _checkConsoleStandard(self):
def _checkConsoleStandard(self, dom):
"""
Shared logic for general console handling
"""
@ -97,6 +97,11 @@ class Console(uiutils.UITestCase):
uiutils.check(lambda: not fstb.showing, timeout=5)
self.point(win.position[0] + win.size[0] / 2, 0)
uiutils.check(lambda: fstb.showing)
# Move it off and have it hide again
win.point()
uiutils.check(lambda: not fstb.showing, timeout=5)
self.point(win.position[0] + win.size[0] / 2, 0)
uiutils.check(lambda: fstb.showing)
# Click stuff and exit fullscreen
win.find("Fullscreen Send Key").click()
@ -104,6 +109,13 @@ class Console(uiutils.UITestCase):
win.find("Fullscreen Exit").click()
uiutils.check(lambda: win.size == newsize)
# Trigger pointer grab, verify title was updated
win.click()
uiutils.check(lambda: "Control_L" in win.name)
# Ungrab
win.keyCombo("<ctrl><alt>")
uiutils.check(lambda: "Control_L" not in win.name)
# Tweak scaling
win.click_title()
win.click_title()
@ -115,15 +127,20 @@ class Console(uiutils.UITestCase):
scalemenu = win.find("Scale Display", "menu")
scalemenu.point()
scalemenu.find("Never", "radio menu item").click()
self.sleep(.5)
win.find("^View$", "menu").click()
scalemenu = win.find("Scale Display", "menu")
scalemenu.point()
scalemenu.find("Only", "radio menu item").click()
dom.destroy()
win.find("Guest is not running.")
@_vm_wrapper("uitests-vnc-standard")
def testConsoleVNCStandard(self):
return self._checkConsoleStandard()
def testConsoleVNCStandard(self, dom):
return self._checkConsoleStandard(dom)
@_vm_wrapper("uitests-spice-standard")
def testConsoleSpiceStandard(self):
return self._checkConsoleStandard()
def testConsoleSpiceStandard(self, dom):
return self._checkConsoleStandard(dom)
def _checkPassword(self):
"""
@ -177,12 +194,32 @@ class Console(uiutils.UITestCase):
uiutils.check(lambda: not bool(passwd.text))
@_vm_wrapper("uitests-vnc-password")
def testConsoleVNCPassword(self):
def testConsoleVNCPassword(self, dom):
ignore = dom
return self._checkPassword()
@_vm_wrapper("uitests-spice-password")
def testConsoleSpicePassword(self):
def testConsoleSpicePassword(self, dom):
ignore = dom
return self._checkPassword()
@_vm_wrapper("uitests-vnc-password",
opts=["--test-options=fake-vnc-username"])
def testConsoleVNCPasswordUsername(self, dom):
ignore = dom
win = self.app.topwin
con = win.find("console-gfx-viewport")
uiutils.check(lambda: not con.showing)
passwd = win.find("Password:", "password text")
uiutils.check(lambda: passwd.showing)
username = win.find("Username:", "text")
uiutils.check(lambda: username.showing)
# Since we are mocking the username, sending the credentials
# is ignored, so with the correct password this succeeds
username.text = "fakeuser"
passwd.typeText("goodp")
win.find("Login", "push button").click()
uiutils.check(lambda: con.showing)
@_vm_wrapper("uitests-vnc-socket")
def testConsoleVNCSocket(self, dom):
@ -205,10 +242,11 @@ class Console(uiutils.UITestCase):
uiutils.check(lambda: con.showing)
@_vm_wrapper("uitests-lxc-serial", uri="lxc:///")
def testConsoleLXCSerial(self):
def testConsoleLXCSerial(self, dom):
"""
Ensure LXC has serial open, and we can send some data
"""
ignore = dom
win = self.app.topwin
term = win.find("Serial Terminal")
uiutils.check(lambda: term.showing)
@ -246,12 +284,15 @@ class Console(uiutils.UITestCase):
term = win.find("Serial Terminal")
uiutils.check(lambda: term.showing)
@_vm_wrapper("uitests-spice-specific", opts=["--test-options=spice-agent"])
def testConsoleSpiceSpecific(self):
@_vm_wrapper("uitests-spice-specific",
opts=["--test-options=spice-agent",
"--test-options=fake-console-resolution"])
def testConsoleSpiceSpecific(self, dom):
"""
Spice specific behavior. Has lots of devices that will open
channels, spice GL + local config, and usbredir
"""
ignore = dom
win = self.app.topwin
con = win.find("console-gfx-viewport")
uiutils.check(lambda: con.showing)
@ -337,11 +378,12 @@ class Console(uiutils.UITestCase):
@_vm_wrapper("uitests-hotplug")
def testLiveHotplug(self):
def testLiveHotplug(self, dom):
"""
Live test for basic hotplugging and media change, as well as
testing our auto-poolify magic
"""
ignore = dom
import tempfile
tmpdir = tempfile.TemporaryDirectory(prefix="uitests-tmp")
dname = tmpdir.name

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkAccelGroup" id="accelgroup1"/>
@ -18,9 +18,6 @@
</accel-groups>
<signal name="configure-event" handler="on_vmm_details_configure_event" swapped="no"/>
<signal name="delete-event" handler="on_vmm_details_delete_event" swapped="no"/>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
@ -772,6 +769,11 @@
<property name="tab_fill">False</property>
</packing>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="console-pages-atkobject">
<property name="AtkObject::accessible-name">console-pages</property>
</object>
</child>
</object>
<packing>
<property name="position">1</property>
@ -821,5 +823,8 @@
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</object>
</interface>

View File

@ -350,8 +350,12 @@ class vmmConsolePages(vmmGObjectUI):
def _scroll_size_allocate(self, src_ignore, req):
if not self._viewer:
return
if not self._viewer.console_get_desktop_resolution():
return
res = self._viewer.console_get_desktop_resolution()
if res is None:
if not self.config.CLITestOptions.fake_console_resolution:
return
res = (800, 600)
scroll = self.widget("console-gfx-scroll")
is_scale = self._viewer.console_get_scaling()
@ -362,7 +366,7 @@ class vmmConsolePages(vmmGObjectUI):
align_ratio = float(req.width) / float(req.height)
# pylint: disable=unpacking-non-sequence
desktop_w, desktop_h = self._viewer.console_get_desktop_resolution()
desktop_w, desktop_h = res
desktop_ratio = float(desktop_w) / float(desktop_h)
if is_scale:
@ -556,7 +560,7 @@ class vmmConsolePages(vmmGObjectUI):
##########################
def _show_vm_status_unavailable(self):
if self.vm.is_crashed():
if self.vm.is_crashed(): # pragma: no cover
self._activate_unavailable_page(_("Guest has crashed."))
else:
self._activate_unavailable_page(_("Guest is not running."))
@ -778,7 +782,7 @@ class vmmConsolePages(vmmGObjectUI):
force_accel = self.config.get_console_accels()
if force_accel:
self._enable_modifiers()
self._enable_modifiers() # pragma: no cover
elif self._someone_has_focus():
self._disable_modifiers()
else:
@ -800,14 +804,15 @@ class vmmConsolePages(vmmGObjectUI):
self._activate_auth_page(withPassword, withUsername)
def _viewer_agent_connected(self, ignore):
self._refresh_resizeguest_from_settings()
self._refresh_resizeguest_from_settings() # pragma: no cover
def _viewer_usb_redirect_error(self, ignore, errstr):
self.err.show_err(_("USB redirection error"),
text2=str(errstr), modal=True)
self.err.show_err(
_("USB redirection error"),
text2=str(errstr), modal=True) # pragma: no cover
def _viewer_disconnected_set_page(self, errdetails, ssherr):
if self.vm.is_runable():
if self.vm.is_runable(): # pragma: no cover
# Exit was probably for legitimate reasons
self._show_vm_status_unavailable()
return

View File

@ -148,7 +148,7 @@ class _Tunnel(object):
def close(self):
if self._closed:
return
return # pragma: no cover
self._closed = True
log.debug("Close tunnel PID=%s ERRFD=%s",
@ -168,7 +168,7 @@ class _Tunnel(object):
while True:
try:
new = self._errfd.recv(1024)
except Exception:
except Exception: # pragma: no cover
break
if not new:
@ -180,11 +180,11 @@ class _Tunnel(object):
def open(self, argv, sshfd):
if self._closed:
return
return # pragma: no cover
errfds = socket.socketpair()
pid = os.fork()
if pid == 0:
if pid == 0: # pragma: no cover
errfds[0].close()
os.dup2(sshfd.fileno(), 0)

View File

@ -16,7 +16,7 @@ try:
from gi.repository import SpiceClientGtk
from gi.repository import SpiceClientGLib
have_spice_gtk = True
except (ValueError, ImportError):
except (ValueError, ImportError): # pragma: no cover
have_spice_gtk = False
from virtinst import log
@ -131,7 +131,7 @@ class Viewer(vmmGObject):
if self._ginfo.gtlsport and not self._ginfo.gport:
# This makes spice loop requesting an fd. Disable until spice is
# fixed: https://bugzilla.redhat.com/show_bug.cgi?id=1334071
return None
return None # pragma: no cover
if not self._vm.conn.support.domain_open_graphics():
return None
@ -334,6 +334,9 @@ class VNCViewer(Viewer):
for idx in range(int(credList.n_values)):
values.append(credList.get_nth(idx))
if self.config.CLITestOptions.fake_vnc_username:
values.append(GtkVnc.DisplayCredential.USERNAME)
withUsername = False
withPassword = False
for cred in values:
@ -378,23 +381,23 @@ class VNCViewer(Viewer):
def _refresh_grab_keys(self):
if not self._display:
return
return # pragma: no cover
try:
keys = self.config.get_keys_combination()
if not keys:
return
return # pragma: no cover
try:
keys = [int(k) for k in keys.split(',')]
except Exception:
except Exception: # pragma: no cover
log.debug("Error in grab_keys configuration in Gsettings",
exc_info=True)
return
seq = GtkVnc.GrabSequence.new(keys)
self._display.set_grab_keys(seq)
except Exception as e:
except Exception as e: # pragma: no cover
log.debug("Error when getting the grab keys combination: %s",
str(e))
@ -403,7 +406,7 @@ class VNCViewer(Viewer):
def _refresh_keyboard_grab_default(self):
if not self._display:
return
return # pragma: no cover
self._display.set_keyboard_grab(self.config.get_keyboard_grab_default())
def _get_desktop_resolution(self):
@ -508,7 +511,7 @@ class SpiceViewer(Viewer):
autoredir = self.config.get_auto_usbredir()
if autoredir:
gtk_session.set_property("auto-usbredir", True)
except Exception:
except Exception: # pragma: no cover
self._usbdev_manager = None
log.debug("Error initializing spice usb device manager",
exc_info=True)
@ -559,7 +562,7 @@ class SpiceViewer(Viewer):
# Can happen if we close the details window and clear self._tunnels
# while initially connecting to spice and channel FD requests
# are still rolling in
return
return # pragma: no cover
log.debug("Requesting fd for channel: %s", channel)
channel.connect_after("channel-event", self._fd_channel_event_cb)
@ -585,7 +588,7 @@ class SpiceViewer(Viewer):
not self._display):
channel_id = channel.get_property("channel-id")
if channel_id != 0:
if channel_id != 0: # pragma: no cover
log.debug("Spice multi-head unsupported")
return
@ -601,7 +604,7 @@ class SpiceViewer(Viewer):
self._audio = SpiceClientGLib.Audio.get(self._spice_session, None)
def _agent_connected_cb(self, src, val):
self.emit("agent-connected")
self.emit("agent-connected") # pragma: no cover
################################
@ -626,23 +629,23 @@ class SpiceViewer(Viewer):
def _refresh_grab_keys(self):
if not self._display:
return
return # pragma: no cover
try:
keys = self.config.get_keys_combination()
if not keys:
return
return # pragma: no cover
try:
keys = [int(k) for k in keys.split(',')]
except Exception:
except Exception: # pragma: no cover
log.debug("Error in grab_keys configuration in Gsettings",
exc_info=True)
return
seq = SpiceClientGtk.GrabSequence.new(keys)
self._display.set_grab_keys(seq)
except Exception as e:
except Exception as e: # pragma: no cover
log.debug("Error when getting the grab keys combination: %s",
str(e))
@ -652,7 +655,7 @@ class SpiceViewer(Viewer):
def _refresh_keyboard_grab_default(self):
if not self._display:
return
return # pragma: no cover
self._display.set_property("grab-keyboard",
self.config.get_keyboard_grab_default())
@ -663,7 +666,7 @@ class SpiceViewer(Viewer):
def _has_agent(self):
if not self._main_channel:
return False
return False # pragma: no cover
return (self._main_channel.get_property("agent-connected") or
self.config.CLITestOptions.spice_agent)
@ -686,14 +689,14 @@ class SpiceViewer(Viewer):
self._spice_session.open_fd(fd)
def _set_username(self, cred):
ignore = cred
ignore = cred # pragma: no cover
def _set_password(self, cred):
self._spice_session.set_property("password", cred)
fd = self._get_fd_for_open()
if fd is not None:
self._spice_session.open_fd(fd)
else:
self._spice_session.connect()
self._spice_session.connect() # pragma: no cover
def _get_scaling(self):
if self._display:
@ -709,17 +712,17 @@ class SpiceViewer(Viewer):
def _get_resizeguest(self):
if self._display:
return self._display.get_property("resize-guest")
return False
return False # pragma: no cover
def _usbdev_redirect_error(self, spice_usbdev_widget, spice_usb_device,
errstr):
errstr): # pragma: no cover
ignore = spice_usbdev_widget
ignore = spice_usb_device
self.emit("usb-redirect-error", errstr)
def _get_usb_widget(self):
if not self._spice_session:
return
return # pragma: no cover
usbwidget = SpiceClientGtk.UsbDeviceWidget.new(self._spice_session,
None)
@ -728,7 +731,7 @@ class SpiceViewer(Viewer):
def _has_usb_redirection(self):
if not self._spice_session or not self._usbdev_manager:
return False
return False # pragma: no cover
for c in self._spice_session.get_channels():
if c.__class__ is SpiceClientGLib.UsbredirChannel:

View File

@ -108,6 +108,10 @@ class CLITestOptionsClass:
if we are doing firstrun testing
* fake-systemd-success: If doing firstrun testing, fake that
systemd checks for libvirtd succeeded
* fake-vnc-username: Fake VNC username auth request
* 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
"""
def __init__(self, test_options_str):
optset = set()
@ -143,6 +147,8 @@ class CLITestOptionsClass:
self.spice_agent = _get("spice-agent")
self.firstrun_uri = _get_value("firstrun-uri")
self.fake_systemd_success = _get("fake-systemd-success")
self.fake_vnc_username = _get("fake-vnc-username")
self.fake_console_resolution = _get("fake-console-resolution")
if optset: # pragma: no cover
raise RuntimeError("Unknown --test-options keys: %s" % optset)