mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-27 12:23:53 -06:00

When waiting for expected output, the 'success_message' is a mandatory parameter, with 'failure_message' defaulting to None. The code has logic which indicates it was trying to cope with 'success_message' being None and 'failure_message' being non-None but it does not appear able to actually do anything useful. The check for 'success_message is None' will break out of the loop before any check for 'failure_message' has been performed. IOW, for practcal purposes 'success_message' must be non-None unless 'send_string' is set. Assert this expectation and simplify the loop logic. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> Message-Id: <20241121154218.1423005-15-berrange@redhat.com> Reviewed-by: Thomas Huth <thuth@redhat.com> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20241121165806.476008-15-alex.bennee@linaro.org>
201 lines
6.6 KiB
Python
201 lines
6.6 KiB
Python
# Test class and utilities for functional tests
|
|
#
|
|
# Copyright 2018, 2024 Red Hat, Inc.
|
|
#
|
|
# Original Author (Avocado-based tests):
|
|
# Cleber Rosa <crosa@redhat.com>
|
|
#
|
|
# Adaption for standalone version:
|
|
# Thomas Huth <thuth@redhat.com>
|
|
#
|
|
# This work is licensed under the terms of the GNU GPL, version 2 or
|
|
# later. See the COPYING file in the top-level directory.
|
|
|
|
import logging
|
|
import os
|
|
import os.path
|
|
import subprocess
|
|
|
|
from .config import BUILD_DIR
|
|
|
|
|
|
def has_cmd(name, args=None):
|
|
"""
|
|
This function is for use in a @skipUnless decorator, e.g.:
|
|
|
|
@skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
|
|
def test_something_that_needs_sudo(self):
|
|
...
|
|
"""
|
|
|
|
if args is None:
|
|
args = ('which', name)
|
|
|
|
try:
|
|
_, stderr, exitcode = run_cmd(args)
|
|
except Exception as e:
|
|
exitcode = -1
|
|
stderr = str(e)
|
|
|
|
if exitcode != 0:
|
|
cmd_line = ' '.join(args)
|
|
err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
|
|
return (False, err)
|
|
else:
|
|
return (True, '')
|
|
|
|
def has_cmds(*cmds):
|
|
"""
|
|
This function is for use in a @skipUnless decorator and
|
|
allows checking for the availability of multiple commands, e.g.:
|
|
|
|
@skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
|
|
'cmd2', 'cmd3'))
|
|
def test_something_that_needs_cmd1_and_cmd2(self):
|
|
...
|
|
"""
|
|
|
|
for cmd in cmds:
|
|
if isinstance(cmd, str):
|
|
cmd = (cmd,)
|
|
|
|
ok, errstr = has_cmd(*cmd)
|
|
if not ok:
|
|
return (False, errstr)
|
|
|
|
return (True, '')
|
|
|
|
def run_cmd(args):
|
|
subp = subprocess.Popen(args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True)
|
|
stdout, stderr = subp.communicate()
|
|
ret = subp.returncode
|
|
|
|
return (stdout, stderr, ret)
|
|
|
|
def is_readable_executable_file(path):
|
|
return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
|
|
|
|
def _console_interaction(test, success_message, failure_message,
|
|
send_string, keep_sending=False, vm=None):
|
|
assert not keep_sending or send_string
|
|
assert success_message or send_string
|
|
|
|
if vm is None:
|
|
vm = test.vm
|
|
console = vm.console_file
|
|
console_logger = logging.getLogger('console')
|
|
test.log.debug(
|
|
f"Console interaction: success_msg='{success_message}' " +
|
|
f"failure_msg='{failure_message}' send_string='{send_string}'")
|
|
while True:
|
|
if send_string:
|
|
vm.console_socket.sendall(send_string.encode())
|
|
if not keep_sending:
|
|
send_string = None # send only once
|
|
|
|
# Only consume console output if waiting for something
|
|
if success_message is None:
|
|
if send_string is None:
|
|
break
|
|
continue
|
|
|
|
try:
|
|
msg = console.readline().decode().strip()
|
|
except UnicodeDecodeError:
|
|
msg = None
|
|
if not msg:
|
|
continue
|
|
console_logger.debug(msg)
|
|
if success_message in msg:
|
|
break
|
|
if failure_message and failure_message in msg:
|
|
console.close()
|
|
fail = 'Failure message found in console: "%s". Expected: "%s"' % \
|
|
(failure_message, success_message)
|
|
test.fail(fail)
|
|
|
|
def interrupt_interactive_console_until_pattern(test, success_message,
|
|
failure_message=None,
|
|
interrupt_string='\r'):
|
|
"""
|
|
Keep sending a string to interrupt a console prompt, while logging the
|
|
console output. Typical use case is to break a boot loader prompt, such:
|
|
|
|
Press a key within 5 seconds to interrupt boot process.
|
|
5
|
|
4
|
|
3
|
|
2
|
|
1
|
|
Booting default image...
|
|
|
|
:param test: a test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
:type test: :class:`qemu_test.QemuSystemTest`
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
:param interrupt_string: a string to send to the console before trying
|
|
to read a new line
|
|
"""
|
|
assert success_message
|
|
_console_interaction(test, success_message, failure_message,
|
|
interrupt_string, True)
|
|
|
|
def wait_for_console_pattern(test, success_message, failure_message=None,
|
|
vm=None):
|
|
"""
|
|
Waits for messages to appear on the console, while logging the content
|
|
|
|
:param test: a test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
:type test: :class:`qemu_test.QemuSystemTest`
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
"""
|
|
assert success_message
|
|
_console_interaction(test, success_message, failure_message, None, vm=vm)
|
|
|
|
def exec_command(test, command):
|
|
"""
|
|
Send a command to a console (appending CRLF characters), while logging
|
|
the content.
|
|
|
|
:param test: a test containing a VM.
|
|
:type test: :class:`qemu_test.QemuSystemTest`
|
|
:param command: the command to send
|
|
:type command: str
|
|
"""
|
|
_console_interaction(test, None, None, command + '\r')
|
|
|
|
def exec_command_and_wait_for_pattern(test, command,
|
|
success_message, failure_message=None):
|
|
"""
|
|
Send a command to a console (appending CRLF characters), then wait
|
|
for success_message to appear on the console, while logging the.
|
|
content. Mark the test as failed if failure_message is found instead.
|
|
|
|
:param test: a test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
:type test: :class:`qemu_test.QemuSystemTest`
|
|
:param command: the command to send
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
"""
|
|
assert success_message
|
|
_console_interaction(test, success_message, failure_message, command + '\r')
|
|
|
|
def get_qemu_img(test):
|
|
test.log.debug('Looking for and selecting a qemu-img binary')
|
|
|
|
# If qemu-img has been built, use it, otherwise the system wide one
|
|
# will be used.
|
|
qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
|
|
if os.path.exists(qemu_img):
|
|
return qemu_img
|
|
(has_system_qemu_img, errmsg) = has_cmd('qemu-img')
|
|
if has_system_qemu_img:
|
|
return 'qemu-img'
|
|
test.skipTest(errmsg)
|