xref: /openbmc/qemu/tests/functional/qemu_test/cmd.py (revision 59d10024)
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')
88fa32a634SThomas Huth    while True:
89fa32a634SThomas Huth        if send_string:
90fa32a634SThomas Huth            vm.console_socket.sendall(send_string.encode())
91fa32a634SThomas Huth            if not keep_sending:
92fa32a634SThomas Huth                send_string = None # send only once
93fa32a634SThomas Huth
94fa32a634SThomas Huth        # Only consume console output if waiting for something
95fa32a634SThomas Huth        if success_message is None and failure_message is None:
96fa32a634SThomas Huth            if send_string is None:
97fa32a634SThomas Huth                break
98fa32a634SThomas Huth            continue
99fa32a634SThomas Huth
100fa32a634SThomas Huth        try:
101fa32a634SThomas Huth            msg = console.readline().decode().strip()
102fa32a634SThomas Huth        except UnicodeDecodeError:
103fa32a634SThomas Huth            msg = None
104fa32a634SThomas Huth        if not msg:
105fa32a634SThomas Huth            continue
106fa32a634SThomas Huth        console_logger.debug(msg)
107fa32a634SThomas Huth        if success_message is None or success_message in msg:
108fa32a634SThomas Huth            break
109fa32a634SThomas Huth        if failure_message and failure_message in msg:
110fa32a634SThomas Huth            console.close()
111fa32a634SThomas Huth            fail = 'Failure message found in console: "%s". Expected: "%s"' % \
112fa32a634SThomas Huth                    (failure_message, success_message)
113fa32a634SThomas Huth            test.fail(fail)
114fa32a634SThomas Huth
115fa32a634SThomas Huthdef interrupt_interactive_console_until_pattern(test, success_message,
116fa32a634SThomas Huth                                                failure_message=None,
117fa32a634SThomas Huth                                                interrupt_string='\r'):
118fa32a634SThomas Huth    """
119fa32a634SThomas Huth    Keep sending a string to interrupt a console prompt, while logging the
120fa32a634SThomas Huth    console output. Typical use case is to break a boot loader prompt, such:
121fa32a634SThomas Huth
122fa32a634SThomas Huth        Press a key within 5 seconds to interrupt boot process.
123fa32a634SThomas Huth        5
124fa32a634SThomas Huth        4
125fa32a634SThomas Huth        3
126fa32a634SThomas Huth        2
127fa32a634SThomas Huth        1
128fa32a634SThomas Huth        Booting default image...
129fa32a634SThomas Huth
130fa32a634SThomas Huth    :param test: a  test containing a VM that will have its console
131fa32a634SThomas Huth                 read and probed for a success or failure message
132fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
133fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
134fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
135fa32a634SThomas Huth    :param interrupt_string: a string to send to the console before trying
136fa32a634SThomas Huth                             to read a new line
137fa32a634SThomas Huth    """
138fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message,
139fa32a634SThomas Huth                         interrupt_string, True)
140fa32a634SThomas Huth
141fa32a634SThomas Huthdef wait_for_console_pattern(test, success_message, failure_message=None,
142fa32a634SThomas Huth                             vm=None):
143fa32a634SThomas Huth    """
144fa32a634SThomas Huth    Waits for messages to appear on the console, while logging the content
145fa32a634SThomas Huth
146fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
147fa32a634SThomas Huth                 read and probed for a success or failure message
148fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
149fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
150fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
151fa32a634SThomas Huth    """
152fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, None, vm=vm)
153fa32a634SThomas Huth
154fa32a634SThomas Huthdef exec_command(test, command):
155fa32a634SThomas Huth    """
156fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), while logging
157fa32a634SThomas Huth    the content.
158fa32a634SThomas Huth
159fa32a634SThomas Huth    :param test: a test containing a VM.
160fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
161fa32a634SThomas Huth    :param command: the command to send
162fa32a634SThomas Huth    :type command: str
163fa32a634SThomas Huth    """
164fa32a634SThomas Huth    _console_interaction(test, None, None, command + '\r')
165fa32a634SThomas Huth
166fa32a634SThomas Huthdef exec_command_and_wait_for_pattern(test, command,
167fa32a634SThomas Huth                                      success_message, failure_message=None):
168fa32a634SThomas Huth    """
169fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), then wait
170fa32a634SThomas Huth    for success_message to appear on the console, while logging the.
171fa32a634SThomas Huth    content. Mark the test as failed if failure_message is found instead.
172fa32a634SThomas Huth
173fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
174fa32a634SThomas Huth                 read and probed for a success or failure message
175fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
176fa32a634SThomas Huth    :param command: the command to send
177fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
178fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
179fa32a634SThomas Huth    """
180fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, command + '\r')
1811255f5e4SPhilippe Mathieu-Daudé
1821255f5e4SPhilippe Mathieu-Daudédef get_qemu_img(test):
1831255f5e4SPhilippe Mathieu-Daudé    test.log.debug('Looking for and selecting a qemu-img binary')
1841255f5e4SPhilippe Mathieu-Daudé
1851255f5e4SPhilippe Mathieu-Daudé    # If qemu-img has been built, use it, otherwise the system wide one
1861255f5e4SPhilippe Mathieu-Daudé    # will be used.
1871255f5e4SPhilippe Mathieu-Daudé    qemu_img = os.path.join(BUILD_DIR, 'qemu-img')
1881255f5e4SPhilippe Mathieu-Daudé    if os.path.exists(qemu_img):
1891255f5e4SPhilippe Mathieu-Daudé        return qemu_img
190*59d10024SThomas Huth    (has_system_qemu_img, errmsg) = has_cmd('qemu-img')
191*59d10024SThomas Huth    if has_system_qemu_img:
1921255f5e4SPhilippe Mathieu-Daudé        return 'qemu-img'
193*59d10024SThomas Huth    test.skipTest(errmsg)
194