xref: /qemu/tests/functional/qemu_test/cmd.py (revision 9132fff802431438a2805389e74402321fb9afed)
1fa32a634SThomas Huth# Test class and utilities for functional tests
2fa32a634SThomas Huth#
3fa32a634SThomas Huth# Copyright 2018, 2024 Red Hat, Inc.
4fa32a634SThomas Huth#
5fa32a634SThomas Huth# Original Author (Avocado-based tests):
6fa32a634SThomas Huth#  Cleber Rosa <crosa@redhat.com>
7fa32a634SThomas Huth#
8fa32a634SThomas Huth# Adaption for standalone version:
9fa32a634SThomas Huth#  Thomas Huth <thuth@redhat.com>
10fa32a634SThomas Huth#
11fa32a634SThomas Huth# This work is licensed under the terms of the GNU GPL, version 2 or
12fa32a634SThomas Huth# later.  See the COPYING file in the top-level directory.
13fa32a634SThomas Huth
14fa32a634SThomas Huthimport logging
15fa32a634SThomas Huthimport os
16fa32a634SThomas Huthimport os.path
17fa32a634SThomas Huthimport subprocess
18fa32a634SThomas Huth
191255f5e4SPhilippe Mathieu-Daudéfrom .config import BUILD_DIR
201255f5e4SPhilippe Mathieu-Daudé
210da341a7SDaniel P. Berrangédef which(tool):
220da341a7SDaniel P. Berrangé    """ looks up the full path for @tool, returns None if not found
230da341a7SDaniel P. Berrangé        or if @tool does not have executable permissions.
240da341a7SDaniel P. Berrangé    """
250da341a7SDaniel P. Berrangé    paths=os.getenv('PATH')
260da341a7SDaniel P. Berrangé    for p in paths.split(os.path.pathsep):
270da341a7SDaniel P. Berrangé        p = os.path.join(p, tool)
28dd6dfc01SDaniel P. Berrangé        if os.access(p, os.X_OK):
290da341a7SDaniel P. Berrangé            return p
300da341a7SDaniel P. Berrangé    return None
31fa32a634SThomas Huth
32fa32a634SThomas Huthdef run_cmd(args):
33fa32a634SThomas Huth    subp = subprocess.Popen(args,
34fa32a634SThomas Huth                            stdout=subprocess.PIPE,
35fa32a634SThomas Huth                            stderr=subprocess.PIPE,
36fa32a634SThomas Huth                            universal_newlines=True)
37fa32a634SThomas Huth    stdout, stderr = subp.communicate()
38fa32a634SThomas Huth    ret = subp.returncode
39fa32a634SThomas Huth
40fa32a634SThomas Huth    return (stdout, stderr, ret)
41fa32a634SThomas Huth
42fa32a634SThomas Huthdef is_readable_executable_file(path):
43fa32a634SThomas Huth    return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
44fa32a634SThomas Huth
45cdad03b7SDaniel P. Berrangé# @test: functional test to fail if @failure is seen
46cdad03b7SDaniel P. Berrangé# @vm: the VM whose console to process
47cdad03b7SDaniel P. Berrangé# @success: a non-None string to look for
48cdad03b7SDaniel P. Berrangé# @failure: a string to look for that triggers test failure, or None
49cdad03b7SDaniel P. Berrangé#
50cdad03b7SDaniel P. Berrangé# Read up to 1 line of text from @vm, looking for @success
51cdad03b7SDaniel P. Berrangé# and optionally @failure.
52cdad03b7SDaniel P. Berrangé#
53cdad03b7SDaniel P. Berrangé# If @success or @failure are seen, immediately return True,
54cdad03b7SDaniel P. Berrangé# even if end of line is not yet seen. ie remainder of the
55cdad03b7SDaniel P. Berrangé# line is left unread.
56cdad03b7SDaniel P. Berrangé#
57cdad03b7SDaniel P. Berrangé# If end of line is seen, with neither @success or @failure
58cdad03b7SDaniel P. Berrangé# return False
59cdad03b7SDaniel P. Berrangé#
60cdad03b7SDaniel P. Berrangé# If @failure is seen, then mark @test as failed
61cdad03b7SDaniel P. Berrangédef _console_read_line_until_match(test, vm, success, failure):
62cdad03b7SDaniel P. Berrangé    msg = bytes([])
63cdad03b7SDaniel P. Berrangé    done = False
64cdad03b7SDaniel P. Berrangé    while True:
65cdad03b7SDaniel P. Berrangé        c = vm.console_socket.recv(1)
66cdad03b7SDaniel P. Berrangé        if c is None:
67cdad03b7SDaniel P. Berrangé            done = True
68cdad03b7SDaniel P. Berrangé            test.fail(
69cdad03b7SDaniel P. Berrangé                f"EOF in console, expected '{success}'")
70cdad03b7SDaniel P. Berrangé            break
71cdad03b7SDaniel P. Berrangé        msg += c
72cdad03b7SDaniel P. Berrangé
73cdad03b7SDaniel P. Berrangé        if success in msg:
74cdad03b7SDaniel P. Berrangé            done = True
75cdad03b7SDaniel P. Berrangé            break
76cdad03b7SDaniel P. Berrangé        if failure and failure in msg:
77cdad03b7SDaniel P. Berrangé            done = True
78cdad03b7SDaniel P. Berrangé            vm.console_socket.close()
79cdad03b7SDaniel P. Berrangé            test.fail(
80cdad03b7SDaniel P. Berrangé                f"'{failure}' found in console, expected '{success}'")
81cdad03b7SDaniel P. Berrangé
82cdad03b7SDaniel P. Berrangé        if c == b'\n':
83cdad03b7SDaniel P. Berrangé            break
84cdad03b7SDaniel P. Berrangé
85cdad03b7SDaniel P. Berrangé    console_logger = logging.getLogger('console')
86cdad03b7SDaniel P. Berrangé    try:
87cdad03b7SDaniel P. Berrangé        console_logger.debug(msg.decode().strip())
88cdad03b7SDaniel P. Berrangé    except:
89cdad03b7SDaniel P. Berrangé        console_logger.debug(msg)
90cdad03b7SDaniel P. Berrangé
91cdad03b7SDaniel P. Berrangé    return done
92cdad03b7SDaniel P. Berrangé
93fa32a634SThomas Huthdef _console_interaction(test, success_message, failure_message,
94fa32a634SThomas Huth                         send_string, keep_sending=False, vm=None):
95fa32a634SThomas Huth    assert not keep_sending or send_string
96f03a8189SDaniel P. Berrangé    assert success_message or send_string
97f03a8189SDaniel P. Berrangé
98fa32a634SThomas Huth    if vm is None:
99fa32a634SThomas Huth        vm = test.vm
100cdad03b7SDaniel P. Berrangé
1016f0942b7SDaniel P. Berrangé    test.log.debug(
1026f0942b7SDaniel P. Berrangé        f"Console interaction: success_msg='{success_message}' " +
1036f0942b7SDaniel P. Berrangé        f"failure_msg='{failure_message}' send_string='{send_string}'")
104cdad03b7SDaniel P. Berrangé
105cdad03b7SDaniel P. Berrangé    # We'll process console in bytes, to avoid having to
106cdad03b7SDaniel P. Berrangé    # deal with unicode decode errors from receiving
107cdad03b7SDaniel P. Berrangé    # partial utf8 byte sequences
108cdad03b7SDaniel P. Berrangé    success_message_b = None
109cdad03b7SDaniel P. Berrangé    if success_message is not None:
110cdad03b7SDaniel P. Berrangé        success_message_b = success_message.encode()
111cdad03b7SDaniel P. Berrangé
112cdad03b7SDaniel P. Berrangé    failure_message_b = None
113cdad03b7SDaniel P. Berrangé    if failure_message is not None:
114cdad03b7SDaniel P. Berrangé        failure_message_b = failure_message.encode()
115cdad03b7SDaniel P. Berrangé
116fa32a634SThomas Huth    while True:
117fa32a634SThomas Huth        if send_string:
118fa32a634SThomas Huth            vm.console_socket.sendall(send_string.encode())
119fa32a634SThomas Huth            if not keep_sending:
120fa32a634SThomas Huth                send_string = None # send only once
121fa32a634SThomas Huth
122fa32a634SThomas Huth        # Only consume console output if waiting for something
123f03a8189SDaniel P. Berrangé        if success_message is None:
124fa32a634SThomas Huth            if send_string is None:
125fa32a634SThomas Huth                break
126fa32a634SThomas Huth            continue
127fa32a634SThomas Huth
128cdad03b7SDaniel P. Berrangé        if _console_read_line_until_match(test, vm,
129cdad03b7SDaniel P. Berrangé                                          success_message_b,
130cdad03b7SDaniel P. Berrangé                                          failure_message_b):
131fa32a634SThomas Huth            break
132fa32a634SThomas Huth
133fa32a634SThomas Huthdef interrupt_interactive_console_until_pattern(test, success_message,
134fa32a634SThomas Huth                                                failure_message=None,
135fa32a634SThomas Huth                                                interrupt_string='\r'):
136fa32a634SThomas Huth    """
137fa32a634SThomas Huth    Keep sending a string to interrupt a console prompt, while logging the
138fa32a634SThomas Huth    console output. Typical use case is to break a boot loader prompt, such:
139fa32a634SThomas Huth
140fa32a634SThomas Huth        Press a key within 5 seconds to interrupt boot process.
141fa32a634SThomas Huth        5
142fa32a634SThomas Huth        4
143fa32a634SThomas Huth        3
144fa32a634SThomas Huth        2
145fa32a634SThomas Huth        1
146fa32a634SThomas Huth        Booting default image...
147fa32a634SThomas Huth
148fa32a634SThomas Huth    :param test: a  test containing a VM that will have its console
149fa32a634SThomas Huth                 read and probed for a success or failure message
150fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
151fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
152fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
153fa32a634SThomas Huth    :param interrupt_string: a string to send to the console before trying
154fa32a634SThomas Huth                             to read a new line
155fa32a634SThomas Huth    """
156f03a8189SDaniel P. Berrangé    assert success_message
157fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message,
158fa32a634SThomas Huth                         interrupt_string, True)
159fa32a634SThomas Huth
160fa32a634SThomas Huthdef wait_for_console_pattern(test, success_message, failure_message=None,
161fa32a634SThomas Huth                             vm=None):
162fa32a634SThomas Huth    """
163fa32a634SThomas Huth    Waits for messages to appear on the console, while logging the content
164fa32a634SThomas Huth
165fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
166fa32a634SThomas Huth                 read and probed for a success or failure message
167fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
168fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
169fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
170fa32a634SThomas Huth    """
171f03a8189SDaniel P. Berrangé    assert success_message
172fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, None, vm=vm)
173fa32a634SThomas Huth
174fa32a634SThomas Huthdef exec_command(test, command):
175fa32a634SThomas Huth    """
176fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), while logging
177fa32a634SThomas Huth    the content.
178fa32a634SThomas Huth
179fa32a634SThomas Huth    :param test: a test containing a VM.
180fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
181fa32a634SThomas Huth    :param command: the command to send
182fa32a634SThomas Huth    :type command: str
183fa32a634SThomas Huth    """
184fa32a634SThomas Huth    _console_interaction(test, None, None, command + '\r')
185fa32a634SThomas Huth
186fa32a634SThomas Huthdef exec_command_and_wait_for_pattern(test, command,
187fa32a634SThomas Huth                                      success_message, failure_message=None):
188fa32a634SThomas Huth    """
189fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), then wait
190fa32a634SThomas Huth    for success_message to appear on the console, while logging the.
191fa32a634SThomas Huth    content. Mark the test as failed if failure_message is found instead.
192fa32a634SThomas Huth
193fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
194fa32a634SThomas Huth                 read and probed for a success or failure message
195fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
196fa32a634SThomas Huth    :param command: the command to send
197fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
198fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
199fa32a634SThomas Huth    """
200f03a8189SDaniel P. Berrangé    assert success_message
201fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, command + '\r')
2021255f5e4SPhilippe Mathieu-Daudé
2031255f5e4SPhilippe Mathieu-Daudédef get_qemu_img(test):
2041255f5e4SPhilippe Mathieu-Daudé    test.log.debug('Looking for and selecting a qemu-img binary')
2051255f5e4SPhilippe Mathieu-Daudé
2061255f5e4SPhilippe Mathieu-Daudé    # If qemu-img has been built, use it, otherwise the system wide one
2071255f5e4SPhilippe Mathieu-Daudé    # will be used.
2081255f5e4SPhilippe Mathieu-Daudé    qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
2091255f5e4SPhilippe Mathieu-Daudé    if os.path.exists(qemu_img):
2101255f5e4SPhilippe Mathieu-Daudé        return qemu_img
211*9132fff8SDaniel P. Berrangé    qemu_img = which('qemu-img')
212*9132fff8SDaniel P. Berrangé    if qemu_img is not None:
213*9132fff8SDaniel P. Berrangé        return qemu_img
214*9132fff8SDaniel P. Berrangé    test.skipTest(f"qemu-img not found in {BUILD_DIR} or '$PATH'")
215