xref: /openbmc/qemu/tests/functional/qemu_test/cmd.py (revision 6f0942b7)
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é
21fa32a634SThomas Huth
22fa32a634SThomas Huthdef has_cmd(name, args=None):
23fa32a634SThomas Huth    """
24fa32a634SThomas Huth    This function is for use in a @skipUnless decorator, e.g.:
25fa32a634SThomas Huth
26fa32a634SThomas Huth        @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
27fa32a634SThomas Huth        def test_something_that_needs_sudo(self):
28fa32a634SThomas Huth            ...
29fa32a634SThomas Huth    """
30fa32a634SThomas Huth
31fa32a634SThomas Huth    if args is None:
32fa32a634SThomas Huth        args = ('which', name)
33fa32a634SThomas Huth
34fa32a634SThomas Huth    try:
35fa32a634SThomas Huth        _, stderr, exitcode = run_cmd(args)
36fa32a634SThomas Huth    except Exception as e:
37fa32a634SThomas Huth        exitcode = -1
38fa32a634SThomas Huth        stderr = str(e)
39fa32a634SThomas Huth
40fa32a634SThomas Huth    if exitcode != 0:
41fa32a634SThomas Huth        cmd_line = ' '.join(args)
42fa32a634SThomas Huth        err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
43fa32a634SThomas Huth        return (False, err)
44fa32a634SThomas Huth    else:
45fa32a634SThomas Huth        return (True, '')
46fa32a634SThomas Huth
47fa32a634SThomas Huthdef has_cmds(*cmds):
48fa32a634SThomas Huth    """
49fa32a634SThomas Huth    This function is for use in a @skipUnless decorator and
50fa32a634SThomas Huth    allows checking for the availability of multiple commands, e.g.:
51fa32a634SThomas Huth
52fa32a634SThomas Huth        @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
53fa32a634SThomas Huth                              'cmd2', 'cmd3'))
54fa32a634SThomas Huth        def test_something_that_needs_cmd1_and_cmd2(self):
55fa32a634SThomas Huth            ...
56fa32a634SThomas Huth    """
57fa32a634SThomas Huth
58fa32a634SThomas Huth    for cmd in cmds:
59fa32a634SThomas Huth        if isinstance(cmd, str):
60fa32a634SThomas Huth            cmd = (cmd,)
61fa32a634SThomas Huth
62fa32a634SThomas Huth        ok, errstr = has_cmd(*cmd)
63fa32a634SThomas Huth        if not ok:
64fa32a634SThomas Huth            return (False, errstr)
65fa32a634SThomas Huth
66fa32a634SThomas Huth    return (True, '')
67fa32a634SThomas Huth
68fa32a634SThomas Huthdef run_cmd(args):
69fa32a634SThomas Huth    subp = subprocess.Popen(args,
70fa32a634SThomas Huth                            stdout=subprocess.PIPE,
71fa32a634SThomas Huth                            stderr=subprocess.PIPE,
72fa32a634SThomas Huth                            universal_newlines=True)
73fa32a634SThomas Huth    stdout, stderr = subp.communicate()
74fa32a634SThomas Huth    ret = subp.returncode
75fa32a634SThomas Huth
76fa32a634SThomas Huth    return (stdout, stderr, ret)
77fa32a634SThomas Huth
78fa32a634SThomas Huthdef is_readable_executable_file(path):
79fa32a634SThomas Huth    return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
80fa32a634SThomas Huth
81fa32a634SThomas Huthdef _console_interaction(test, success_message, failure_message,
82fa32a634SThomas Huth                         send_string, keep_sending=False, vm=None):
83fa32a634SThomas Huth    assert not keep_sending or send_string
84fa32a634SThomas Huth    if vm is None:
85fa32a634SThomas Huth        vm = test.vm
86fa32a634SThomas Huth    console = vm.console_file
87fa32a634SThomas Huth    console_logger = logging.getLogger('console')
88*6f0942b7SDaniel P. Berrangé    test.log.debug(
89*6f0942b7SDaniel P. Berrangé        f"Console interaction: success_msg='{success_message}' " +
90*6f0942b7SDaniel P. Berrangé        f"failure_msg='{failure_message}' send_string='{send_string}'")
91fa32a634SThomas Huth    while True:
92fa32a634SThomas Huth        if send_string:
93fa32a634SThomas Huth            vm.console_socket.sendall(send_string.encode())
94fa32a634SThomas Huth            if not keep_sending:
95fa32a634SThomas Huth                send_string = None # send only once
96fa32a634SThomas Huth
97fa32a634SThomas Huth        # Only consume console output if waiting for something
98fa32a634SThomas Huth        if success_message is None and failure_message is None:
99fa32a634SThomas Huth            if send_string is None:
100fa32a634SThomas Huth                break
101fa32a634SThomas Huth            continue
102fa32a634SThomas Huth
103fa32a634SThomas Huth        try:
104fa32a634SThomas Huth            msg = console.readline().decode().strip()
105fa32a634SThomas Huth        except UnicodeDecodeError:
106fa32a634SThomas Huth            msg = None
107fa32a634SThomas Huth        if not msg:
108fa32a634SThomas Huth            continue
109fa32a634SThomas Huth        console_logger.debug(msg)
110fa32a634SThomas Huth        if success_message is None or success_message in msg:
111fa32a634SThomas Huth            break
112fa32a634SThomas Huth        if failure_message and failure_message in msg:
113fa32a634SThomas Huth            console.close()
114fa32a634SThomas Huth            fail = 'Failure message found in console: "%s". Expected: "%s"' % \
115fa32a634SThomas Huth                    (failure_message, success_message)
116fa32a634SThomas Huth            test.fail(fail)
117fa32a634SThomas Huth
118fa32a634SThomas Huthdef interrupt_interactive_console_until_pattern(test, success_message,
119fa32a634SThomas Huth                                                failure_message=None,
120fa32a634SThomas Huth                                                interrupt_string='\r'):
121fa32a634SThomas Huth    """
122fa32a634SThomas Huth    Keep sending a string to interrupt a console prompt, while logging the
123fa32a634SThomas Huth    console output. Typical use case is to break a boot loader prompt, such:
124fa32a634SThomas Huth
125fa32a634SThomas Huth        Press a key within 5 seconds to interrupt boot process.
126fa32a634SThomas Huth        5
127fa32a634SThomas Huth        4
128fa32a634SThomas Huth        3
129fa32a634SThomas Huth        2
130fa32a634SThomas Huth        1
131fa32a634SThomas Huth        Booting default image...
132fa32a634SThomas Huth
133fa32a634SThomas Huth    :param test: a  test containing a VM that will have its console
134fa32a634SThomas Huth                 read and probed for a success or failure message
135fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
136fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
137fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
138fa32a634SThomas Huth    :param interrupt_string: a string to send to the console before trying
139fa32a634SThomas Huth                             to read a new line
140fa32a634SThomas Huth    """
141fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message,
142fa32a634SThomas Huth                         interrupt_string, True)
143fa32a634SThomas Huth
144fa32a634SThomas Huthdef wait_for_console_pattern(test, success_message, failure_message=None,
145fa32a634SThomas Huth                             vm=None):
146fa32a634SThomas Huth    """
147fa32a634SThomas Huth    Waits for messages to appear on the console, while logging the content
148fa32a634SThomas Huth
149fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
150fa32a634SThomas Huth                 read and probed for a success or failure message
151fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
152fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
153fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
154fa32a634SThomas Huth    """
155fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, None, vm=vm)
156fa32a634SThomas Huth
157fa32a634SThomas Huthdef exec_command(test, command):
158fa32a634SThomas Huth    """
159fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), while logging
160fa32a634SThomas Huth    the content.
161fa32a634SThomas Huth
162fa32a634SThomas Huth    :param test: a test containing a VM.
163fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
164fa32a634SThomas Huth    :param command: the command to send
165fa32a634SThomas Huth    :type command: str
166fa32a634SThomas Huth    """
167fa32a634SThomas Huth    _console_interaction(test, None, None, command + '\r')
168fa32a634SThomas Huth
169fa32a634SThomas Huthdef exec_command_and_wait_for_pattern(test, command,
170fa32a634SThomas Huth                                      success_message, failure_message=None):
171fa32a634SThomas Huth    """
172fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), then wait
173fa32a634SThomas Huth    for success_message to appear on the console, while logging the.
174fa32a634SThomas Huth    content. Mark the test as failed if failure_message is found instead.
175fa32a634SThomas Huth
176fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
177fa32a634SThomas Huth                 read and probed for a success or failure message
178fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
179fa32a634SThomas Huth    :param command: the command to send
180fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
181fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
182fa32a634SThomas Huth    """
183fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, command + '\r')
1841255f5e4SPhilippe Mathieu-Daudé
1851255f5e4SPhilippe Mathieu-Daudédef get_qemu_img(test):
1861255f5e4SPhilippe Mathieu-Daudé    test.log.debug('Looking for and selecting a qemu-img binary')
1871255f5e4SPhilippe Mathieu-Daudé
1881255f5e4SPhilippe Mathieu-Daudé    # If qemu-img has been built, use it, otherwise the system wide one
1891255f5e4SPhilippe Mathieu-Daudé    # will be used.
1901255f5e4SPhilippe Mathieu-Daudé    qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
1911255f5e4SPhilippe Mathieu-Daudé    if os.path.exists(qemu_img):
1921255f5e4SPhilippe Mathieu-Daudé        return qemu_img
19359d10024SThomas Huth    (has_system_qemu_img, errmsg) = has_cmd('qemu-img')
19459d10024SThomas Huth    if has_system_qemu_img:
1951255f5e4SPhilippe Mathieu-Daudé        return 'qemu-img'
19659d10024SThomas Huth    test.skipTest(errmsg)
197