diff --git a/tests/uitests/test_details.py b/tests/uitests/test_details.py
index b73c063a..0292ac69 100644
--- a/tests/uitests/test_details.py
+++ b/tests/uitests/test_details.py
@@ -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 = ''
+ 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 = ''
+ _change_gfx_xml(xml)
+ _run()
+ _checkcon(".*resolving.*256.256.256.256.*")
+
+ # Listening from some other address
+ _stop()
+ xml = ''
+ _change_gfx_xml(xml)
+ _run()
+ _checkcon(".*resolving.*257.0.0.1.*")
+
+ # Hit a specific error about tls only and ssh
+ _stop()
+ xml = ''
+ _change_gfx_xml(xml)
+ _run()
+ _checkcon(".*configured for TLS only.*")
+
+ # Fake a socket connection
+ _stop()
+ xml = ''
+ _change_gfx_xml(xml)
+ _run()
+ _checkcon(".*SSH tunnel error output.*")
+
+ # Add a listen type='none' check
+ _stop()
+ xml = ''
+ _change_gfx_xml(xml)
+ _run()
+ _checkcon(".*local file descriptor.*")
+
+ # Add a local list + port check
+ _stop()
+ xml = ''
+ _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 = ''
+ 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.*")
diff --git a/tests/uitests/test_livetests.py b/tests/uitests/test_livetests.py
index 8aa78b87..2461087e 100644
--- a/tests/uitests/test_livetests.py
+++ b/tests/uitests/test_livetests.py
@@ -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("")
+ 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
diff --git a/ui/vmwindow.ui b/ui/vmwindow.ui
index 6c78890a..39178706 100644
--- a/ui/vmwindow.ui
+++ b/ui/vmwindow.ui
@@ -1,5 +1,5 @@
-
+
@@ -18,9 +18,6 @@
-
-
-
+
+
+
1
@@ -821,5 +823,8 @@
+
+
+
diff --git a/virtManager/details/console.py b/virtManager/details/console.py
index 21fda86a..67ba90e9 100644
--- a/virtManager/details/console.py
+++ b/virtManager/details/console.py
@@ -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
diff --git a/virtManager/details/sshtunnels.py b/virtManager/details/sshtunnels.py
index 7ed8e9b1..9afc1e13 100644
--- a/virtManager/details/sshtunnels.py
+++ b/virtManager/details/sshtunnels.py
@@ -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)
diff --git a/virtManager/details/viewers.py b/virtManager/details/viewers.py
index 6d75ce4e..569a404e 100644
--- a/virtManager/details/viewers.py
+++ b/virtManager/details/viewers.py
@@ -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:
diff --git a/virtManager/lib/testmock.py b/virtManager/lib/testmock.py
index 6b4ed0d1..928ad9a3 100644
--- a/virtManager/lib/testmock.py
+++ b/virtManager/lib/testmock.py
@@ -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)