547 lines
17 KiB
Python
547 lines
17 KiB
Python
# This work is licensed under the GNU GPLv2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
import os
|
|
|
|
import libvirt
|
|
import pytest
|
|
|
|
from virtinst import log
|
|
|
|
import tests
|
|
from . import lib
|
|
|
|
|
|
def _vm_wrapper(vmname, uri="qemu:///system", opts=None):
|
|
"""
|
|
Decorator to define+start a VM and clean it up on exit
|
|
"""
|
|
def wrap1(fn):
|
|
def wrapper(app, *args, **kwargs):
|
|
app.error_if_already_running()
|
|
xmlfile = "%s/live/%s.xml" % (tests.utils.UITESTDATADIR, vmname)
|
|
conn = libvirt.open(uri)
|
|
dom = conn.defineXML(open(xmlfile).read())
|
|
try:
|
|
dom.create()
|
|
app.uri = uri
|
|
app.conn = conn
|
|
extra_opts = (opts or [])
|
|
extra_opts += ["--show-domain-console", vmname]
|
|
# Enable stats for more code coverage
|
|
keyfile = "statsonly.ini"
|
|
app.open(extra_opts=extra_opts, keyfile=keyfile)
|
|
fn(app, dom, *args, **kwargs)
|
|
finally:
|
|
try:
|
|
app.stop()
|
|
except Exception:
|
|
pass
|
|
try:
|
|
flags = 0
|
|
if "qemu" in uri:
|
|
flags = libvirt.VIR_DOMAIN_UNDEFINE_NVRAM
|
|
dom.undefineFlags(flags)
|
|
dom.destroy()
|
|
except Exception:
|
|
pass
|
|
return wrapper
|
|
return wrap1
|
|
|
|
|
|
def _destroy(app, win):
|
|
smenu = win.find("Menu", "toggle button")
|
|
smenu.click()
|
|
smenu.find("Force Off", "menu item").click()
|
|
app.click_alert_button("you sure", "Yes")
|
|
run = win.find("Run", "push button")
|
|
lib.utils.check(lambda: run.sensitive)
|
|
|
|
|
|
###############################################
|
|
# Test live console connections with stub VMs #
|
|
###############################################
|
|
|
|
def _checkConsoleStandard(app, dom):
|
|
"""
|
|
Shared logic for general console handling
|
|
"""
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
win.find("Virtual Machine", "menu").click()
|
|
win.find("Take Screenshot", "menu item").click()
|
|
chooser = app.root.find(None, "file chooser")
|
|
fname = chooser.find("Name", "text").text
|
|
app.rawinput.pressKey("Enter")
|
|
lib.utils.check(lambda: os.path.exists(fname))
|
|
os.unlink(fname)
|
|
lib.utils.check(lambda: win.active)
|
|
|
|
win.find("Send Key", "menu").click()
|
|
win.find(r"Ctrl\+Alt\+F1", "menu item").click()
|
|
win.find("Send Key", "menu").click()
|
|
win.find(r"Ctrl\+Alt\+F10", "menu item").click()
|
|
win.find("Send Key", "menu").click()
|
|
win.find(r"Ctrl\+Alt\+Delete", "menu item").click()
|
|
|
|
# 'Resize to VM' testing
|
|
oldsize = win.size
|
|
win.find("^View$", "menu").click()
|
|
win.find("Resize to VM", "menu item").click()
|
|
newsize = win.size
|
|
lib.utils.check(lambda: oldsize != newsize)
|
|
|
|
# Fullscreen testing
|
|
win.find("^View$", "menu").click()
|
|
win.find("Fullscreen", "check menu item").click()
|
|
fstb = win.find("Fullscreen Toolbar")
|
|
lib.utils.check(lambda: fstb.showing)
|
|
lib.utils.check(lambda: win.size != newsize)
|
|
|
|
# Wait for toolbar to hide, then reveal it again
|
|
lib.utils.check(lambda: not fstb.showing, timeout=5)
|
|
app.rawinput.point(win.position[0] + win.size[0] / 2, 0)
|
|
lib.utils.check(lambda: fstb.showing)
|
|
# Move it off and have it hide again
|
|
win.point()
|
|
lib.utils.check(lambda: not fstb.showing, timeout=5)
|
|
app.rawinput.point(win.position[0] + win.size[0] / 2, 0)
|
|
lib.utils.check(lambda: fstb.showing)
|
|
|
|
# Click stuff and exit fullscreen
|
|
win.find("Fullscreen Send Key").click()
|
|
app.rawinput.pressKey("Escape")
|
|
win.find("Fullscreen Exit").click()
|
|
lib.utils.check(lambda: win.size == newsize)
|
|
|
|
# Trigger pointer grab, verify title was updated
|
|
win.click()
|
|
lib.utils.check(lambda: "Control_L" in win.name)
|
|
# Ungrab
|
|
win.keyCombo("<ctrl><alt>")
|
|
lib.utils.check(lambda: "Control_L" not in win.name)
|
|
|
|
# Tweak scaling
|
|
win.window_maximize()
|
|
win.find("^View$", "menu").click()
|
|
scalemenu = win.find("Scale Display", "menu")
|
|
scalemenu.point()
|
|
scalemenu.find("Always", "radio menu item").click()
|
|
win.find("^View$", "menu").click()
|
|
scalemenu = win.find("Scale Display", "menu")
|
|
scalemenu.point()
|
|
scalemenu.find("Never", "radio menu item").click()
|
|
win.find("^View$", "menu").click()
|
|
scalemenu = win.find("Scale Display", "menu")
|
|
scalemenu.point()
|
|
scalemenu.find("Only", "radio menu item").click()
|
|
|
|
win.window_close()
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-standard")
|
|
def testConsoleVNCStandard(app, dom):
|
|
return _checkConsoleStandard(app, dom)
|
|
|
|
|
|
@_vm_wrapper("uitests-spice-standard")
|
|
def testConsoleSpiceStandard(app, dom):
|
|
return _checkConsoleStandard(app, dom)
|
|
|
|
|
|
def _checkConsoleFocus(app, dom):
|
|
"""
|
|
Shared logic for console keyboard grab handling
|
|
"""
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Check that modifiers don't work when console grabs pointer
|
|
win.click()
|
|
app.sleep(.5) # make sure window code has time to adjust modifiers
|
|
win.keyCombo("<ctrl><shift>w")
|
|
lib.utils.check(lambda: win.showing)
|
|
dom.destroy()
|
|
win.find("Guest is not running.")
|
|
win.grab_focus()
|
|
app.sleep(.5) # make sure window code has time to adjust modifiers
|
|
win.keyCombo("<ctrl><shift>w")
|
|
lib.utils.check(lambda: not win.showing)
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-standard")
|
|
def testConsoleVNCFocus(app, dom):
|
|
return _checkConsoleFocus(app, dom)
|
|
|
|
|
|
@_vm_wrapper("uitests-spice-standard")
|
|
def testConsoleSpiceFocus(app, dom):
|
|
return _checkConsoleFocus(app, dom)
|
|
|
|
|
|
def _checkPassword(app):
|
|
"""
|
|
Shared logic for password handling
|
|
"""
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: not con.showing)
|
|
passwd = win.find("Password:", "password text")
|
|
lib.utils.check(lambda: passwd.showing)
|
|
|
|
# Check wrong password handling
|
|
passwd.typeText("xx")
|
|
win.find("Login", "push button").click()
|
|
app.click_alert_button("Viewer authentication error", "OK")
|
|
savecheck = win.find("Save this password", "check box")
|
|
if not savecheck.checked:
|
|
savecheck.click()
|
|
passwd.typeText("yy")
|
|
app.rawinput.pressKey("Enter")
|
|
app.click_alert_button("Viewer authentication error", "OK")
|
|
|
|
# Check proper password
|
|
passwd.text = ""
|
|
passwd.typeText("goodp")
|
|
win.find("Login", "push button").click()
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Restart VM to retrigger console connect
|
|
_destroy(app, win)
|
|
win.find("Run", "push button").click()
|
|
lib.utils.check(lambda: passwd.showing)
|
|
# Password should be filled in
|
|
lib.utils.check(lambda: bool(passwd.text))
|
|
# Uncheck 'Save password' and login, which will delete it from keyring
|
|
savecheck.click()
|
|
win.find("Login", "push button").click()
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Restart VM to retrigger console connect
|
|
_destroy(app, win)
|
|
win.find("Run", "push button").click()
|
|
lib.utils.check(lambda: passwd.showing)
|
|
# Password should be empty now
|
|
lib.utils.check(lambda: not bool(passwd.text))
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-password")
|
|
def testConsoleVNCPassword(app, dom):
|
|
ignore = dom
|
|
return _checkPassword(app)
|
|
|
|
|
|
@_vm_wrapper("uitests-spice-password")
|
|
def testConsoleSpicePassword(app, dom):
|
|
ignore = dom
|
|
return _checkPassword(app)
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-password",
|
|
opts=["--test-options=fake-vnc-username"])
|
|
def testConsoleVNCPasswordUsername(app, dom):
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: not con.showing)
|
|
passwd = win.find("Password:", "password text")
|
|
lib.utils.check(lambda: passwd.showing)
|
|
username = win.find("Username:", "text")
|
|
lib.utils.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()
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-socket")
|
|
def testConsoleVNCSocket(app, dom):
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
def _click_textconsole_menu(msg):
|
|
vmenu = win.find("^View$", "menu")
|
|
vmenu.click()
|
|
tmenu = win.find("Consoles", "menu")
|
|
tmenu.point()
|
|
app.sleep(.5) # give console menu time to dynamically populate
|
|
tmenu.find(msg, "radio menu item").click()
|
|
|
|
# A bit of an extra test, make sure selecting Graphical Console works
|
|
_click_textconsole_menu("Serial 1")
|
|
lib.utils.check(lambda: not con.showing)
|
|
_click_textconsole_menu("Graphical Console")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
|
|
@_vm_wrapper("uitests-spice-standard")
|
|
def testConsoleAutoconnect(app, dom):
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Disable autoconnect
|
|
vmenu = win.find("^View$", "menu")
|
|
vmenu.click()
|
|
vmenu.find("Autoconnect").click()
|
|
dom.destroy()
|
|
label = win.find("Guest is not running.")
|
|
label.check_onscreen()
|
|
dom.create()
|
|
label.check_not_onscreen()
|
|
button = win.find("Connect to console", "push button")
|
|
button.check_onscreen()
|
|
lib.utils.check(lambda: not con.showing)
|
|
button.click()
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
|
|
@_vm_wrapper("uitests-lxc-serial", uri="lxc:///")
|
|
def testConsoleLXCSerial(app, dom):
|
|
"""
|
|
Ensure LXC has serial open, and we can send some data
|
|
"""
|
|
win = app.topwin
|
|
term = win.find("Serial Terminal")
|
|
lib.utils.check(lambda: term.showing)
|
|
term.typeText("help\n")
|
|
lib.utils.check(lambda: "COMMANDS" in term.text)
|
|
|
|
term.doubleClick()
|
|
term.click(button=3)
|
|
menu = app.root.find("serial-popup-menu")
|
|
menu.find("Copy", "menu item").click()
|
|
|
|
term.click()
|
|
term.click(button=3)
|
|
menu = app.root.find("serial-popup-menu")
|
|
menu.find("Paste", "menu item").click()
|
|
|
|
win.find("Details", "radio button").click()
|
|
win.find("Console", "radio button").click()
|
|
_destroy(app, win)
|
|
view = app.root.find("^View$", "menu")
|
|
view.click()
|
|
# Triggers some tooltip cases
|
|
textmenu = view.find("Consoles", "menu")
|
|
textmenu.point()
|
|
lib.utils.check(lambda: textmenu.showing)
|
|
app.sleep(.5) # give console menu time to dynamically populate
|
|
item = textmenu.find("Text Console 1")
|
|
lib.utils.check(lambda: not item.sensitive)
|
|
|
|
# Restart the guest to trigger reconnect code
|
|
view.click()
|
|
win.find("Run", "push button").click()
|
|
term = win.find("Serial Terminal")
|
|
lib.utils.check(lambda: term.showing)
|
|
|
|
# Ensure ctrl+w doesn't close the window, modifiers are disabled
|
|
term.click()
|
|
win.keyCombo("<ctrl><shift>w")
|
|
lib.utils.check(lambda: win.showing)
|
|
# Shut it down, ensure accelerator works again
|
|
_destroy(app, win)
|
|
lib.utils.check(lambda: not dom.isActive())
|
|
win.click_title()
|
|
app.sleep(.3) # make sure window code has time to adjust modifiers
|
|
win.keyCombo("<ctrl><shift>w")
|
|
lib.utils.check(lambda: not win.showing)
|
|
|
|
|
|
@_vm_wrapper("uitests-spice-specific",
|
|
opts=["--test-options=spice-agent",
|
|
"--test-options=fake-console-resolution"])
|
|
def testConsoleSpiceSpecific(app, dom):
|
|
"""
|
|
Spice specific behavior. Has lots of devices that will open
|
|
channels, spice GL + local config, and usbredir
|
|
"""
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Just ensure the dialog pops up, can't really test much more
|
|
# than that
|
|
win.find("Virtual Machine", "menu").click()
|
|
win.find("Redirect USB", "menu item").click()
|
|
|
|
usbwin = app.root.find("vmm dialog", "alert")
|
|
usbwin.find("Select USB devices for redirection", "label")
|
|
usbwin.find("SPICE CD", "check box").click()
|
|
chooser = app.root.find(None, "file chooser")
|
|
# Find the cwd bookmark on the left
|
|
chooser.find("virt-manager", "label").click()
|
|
chooser.find("virt-manager", "label").click()
|
|
chooser.find("COPYING").click()
|
|
app.rawinput.pressKey("Enter")
|
|
lib.utils.check(lambda: not chooser.showing)
|
|
usbwin.find("Close", "push button").click()
|
|
|
|
# Test fake guest resize behavior
|
|
def _click_auto():
|
|
vmenu = win.find("^View$", "menu")
|
|
vmenu.click()
|
|
smenu = vmenu.find("Scale Display", "menu")
|
|
smenu.point()
|
|
smenu.find("Auto resize VM", "check menu item").click()
|
|
_click_auto()
|
|
win.click_title()
|
|
win.window_maximize()
|
|
_click_auto()
|
|
win.click_title()
|
|
win.click_title()
|
|
|
|
|
|
@_vm_wrapper("uitests-vnc-standard")
|
|
def testVNCSpecific(app, dom):
|
|
from gi.repository import GtkVnc
|
|
if not hasattr(GtkVnc.Display, "set_allow_resize"):
|
|
pytest.skip("GtkVnc is too old")
|
|
|
|
ignore = dom
|
|
win = app.topwin
|
|
con = win.find("console-gfx-viewport")
|
|
lib.utils.check(lambda: con.showing)
|
|
|
|
# Test guest resize behavior
|
|
def _click_auto():
|
|
vmenu = win.find("^View$", "menu")
|
|
vmenu.click()
|
|
smenu = vmenu.find("Scale Display", "menu")
|
|
smenu.point()
|
|
smenu.find("Auto resize VM", "check menu item").click()
|
|
_click_auto()
|
|
win.click_title()
|
|
win.window_maximize()
|
|
_click_auto()
|
|
win.click_title()
|
|
win.click_title()
|
|
|
|
|
|
def _testLiveHotplug(app, fname):
|
|
win = app.topwin
|
|
win.find("Details", "radio button").click()
|
|
|
|
# Add a scsi disk, importing the passed path
|
|
win.find("add-hardware", "push button").click()
|
|
addhw = app.find_window("Add New Virtual Hardware")
|
|
addhw.find("Storage", "table cell").click()
|
|
tab = addhw.find("storage-tab", None)
|
|
lib.utils.check(lambda: tab.showing)
|
|
tab.find("Select or create", "radio button").click()
|
|
tab.find("storage-entry").set_text(fname)
|
|
tab.combo_select("Bus type:", "SCSI")
|
|
addhw.find("Finish", "push button").click()
|
|
|
|
# Verify permission dialog pops up, ask to change
|
|
app.click_alert_button(
|
|
"The emulator may not have search permissions", "Yes")
|
|
|
|
# Verify no errors
|
|
lib.utils.check(lambda: not addhw.showing)
|
|
lib.utils.check(lambda: win.active)
|
|
|
|
# Hot unplug the disk
|
|
win.find("SCSI Disk 1", "table cell").click()
|
|
tab = win.find("disk-tab", None)
|
|
lib.utils.check(lambda: tab.showing)
|
|
win.find("config-remove").click()
|
|
delete = app.find_window("Remove Disk")
|
|
delete.find_fuzzy("Delete", "button").click()
|
|
lib.utils.check(lambda: not delete.active)
|
|
lib.utils.check(lambda: os.path.exists(fname))
|
|
|
|
# Change CDROM
|
|
win.find("IDE CDROM 1", "table cell").click()
|
|
tab = win.find("disk-tab", None)
|
|
entry = win.find("media-entry")
|
|
appl = win.find("config-apply")
|
|
lib.utils.check(lambda: tab.showing)
|
|
entry.set_text(fname)
|
|
appl.click()
|
|
lib.utils.check(lambda: not appl.sensitive)
|
|
lib.utils.check(lambda: entry.text == fname)
|
|
entry.click_secondary_icon()
|
|
appl.click()
|
|
lib.utils.check(lambda: not appl.sensitive)
|
|
lib.utils.check(lambda: not entry.text)
|
|
|
|
|
|
@_vm_wrapper("uitests-hotplug")
|
|
def testLiveHotplug(app, 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
|
|
try:
|
|
fname = os.path.join(dname, "test.img")
|
|
os.system("qemu-img create -f qcow2 %s 1M > /dev/null" % fname)
|
|
os.system("chmod 700 %s" % dname)
|
|
_testLiveHotplug(app, fname)
|
|
finally:
|
|
poolname = os.path.basename(dname)
|
|
try:
|
|
pool = app.conn.storagePoolLookupByName(poolname)
|
|
pool.destroy()
|
|
pool.undefine()
|
|
except Exception:
|
|
log.debug("Error cleaning up pool", exc_info=True)
|
|
|
|
|
|
@_vm_wrapper("uitests-firmware-efi")
|
|
def testFirmwareRename(app, dom):
|
|
from virtinst import cli, DeviceDisk
|
|
win = app.topwin
|
|
dom.destroy()
|
|
|
|
# First we refresh the 'nvram' pool, so we can reliably
|
|
# check if nvram files are created/deleted as expected
|
|
conn = cli.getConnection(app.conn.getURI())
|
|
origname = dom.name()
|
|
nvramdir = conn.get_libvirt_data_root_dir() + "/qemu/nvram"
|
|
|
|
fakedisk = DeviceDisk(conn)
|
|
fakedisk.set_source_path(nvramdir + "/FAKE-UITEST-FILE")
|
|
nvram_pool = fakedisk.get_parent_pool()
|
|
nvram_pool.refresh()
|
|
|
|
origpath = "%s/%s_VARS.fd" % (nvramdir, origname)
|
|
newname = "uitests-firmware-efi-renamed"
|
|
newpath = "%s/%s_VARS.fd" % (nvramdir, newname)
|
|
assert DeviceDisk.path_definitely_exists(app.conn, origpath)
|
|
assert not DeviceDisk.path_definitely_exists(app.conn, newpath)
|
|
|
|
# Now do the actual UI clickage
|
|
win.find("Details", "radio button").click()
|
|
win.find("Hypervisor Details", "label")
|
|
win.find("Overview", "table cell").click()
|
|
|
|
newname = "uitests-firmware-efi-renamed"
|
|
win.find("Name:", "text").set_text(newname)
|
|
appl = win.find("config-apply")
|
|
appl.click()
|
|
lib.utils.check(lambda: not appl.sensitive)
|
|
|
|
# Confirm window was updated
|
|
app.find_window("%s on" % newname)
|
|
|
|
# Confirm nvram paths were altered as expected
|
|
assert not DeviceDisk.path_definitely_exists(app.conn, origpath)
|
|
assert DeviceDisk.path_definitely_exists(app.conn, newpath)
|