xref: /openbmc/qemu/tests/functional/qemu_test/cmd.py (revision fa32a634)
1*fa32a634SThomas Huth# Test class and utilities for functional tests
2*fa32a634SThomas Huth#
3*fa32a634SThomas Huth# Copyright 2018, 2024 Red Hat, Inc.
4*fa32a634SThomas Huth#
5*fa32a634SThomas Huth# Original Author (Avocado-based tests):
6*fa32a634SThomas Huth#  Cleber Rosa <crosa@redhat.com>
7*fa32a634SThomas Huth#
8*fa32a634SThomas Huth# Adaption for standalone version:
9*fa32a634SThomas Huth#  Thomas Huth <thuth@redhat.com>
10*fa32a634SThomas Huth#
11*fa32a634SThomas Huth# This work is licensed under the terms of the GNU GPL, version 2 or
12*fa32a634SThomas Huth# later.  See the COPYING file in the top-level directory.
13*fa32a634SThomas Huth
14*fa32a634SThomas Huthimport logging
15*fa32a634SThomas Huthimport os
16*fa32a634SThomas Huthimport os.path
17*fa32a634SThomas Huthimport subprocess
18*fa32a634SThomas Huth
19*fa32a634SThomas Huth
20*fa32a634SThomas Huthdef has_cmd(name, args=None):
21*fa32a634SThomas Huth    """
22*fa32a634SThomas Huth    This function is for use in a @skipUnless decorator, e.g.:
23*fa32a634SThomas Huth
24*fa32a634SThomas Huth        @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
25*fa32a634SThomas Huth        def test_something_that_needs_sudo(self):
26*fa32a634SThomas Huth            ...
27*fa32a634SThomas Huth    """
28*fa32a634SThomas Huth
29*fa32a634SThomas Huth    if args is None:
30*fa32a634SThomas Huth        args = ('which', name)
31*fa32a634SThomas Huth
32*fa32a634SThomas Huth    try:
33*fa32a634SThomas Huth        _, stderr, exitcode = run_cmd(args)
34*fa32a634SThomas Huth    except Exception as e:
35*fa32a634SThomas Huth        exitcode = -1
36*fa32a634SThomas Huth        stderr = str(e)
37*fa32a634SThomas Huth
38*fa32a634SThomas Huth    if exitcode != 0:
39*fa32a634SThomas Huth        cmd_line = ' '.join(args)
40*fa32a634SThomas Huth        err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
41*fa32a634SThomas Huth        return (False, err)
42*fa32a634SThomas Huth    else:
43*fa32a634SThomas Huth        return (True, '')
44*fa32a634SThomas Huth
45*fa32a634SThomas Huthdef has_cmds(*cmds):
46*fa32a634SThomas Huth    """
47*fa32a634SThomas Huth    This function is for use in a @skipUnless decorator and
48*fa32a634SThomas Huth    allows checking for the availability of multiple commands, e.g.:
49*fa32a634SThomas Huth
50*fa32a634SThomas Huth        @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
51*fa32a634SThomas Huth                              'cmd2', 'cmd3'))
52*fa32a634SThomas Huth        def test_something_that_needs_cmd1_and_cmd2(self):
53*fa32a634SThomas Huth            ...
54*fa32a634SThomas Huth    """
55*fa32a634SThomas Huth
56*fa32a634SThomas Huth    for cmd in cmds:
57*fa32a634SThomas Huth        if isinstance(cmd, str):
58*fa32a634SThomas Huth            cmd = (cmd,)
59*fa32a634SThomas Huth
60*fa32a634SThomas Huth        ok, errstr = has_cmd(*cmd)
61*fa32a634SThomas Huth        if not ok:
62*fa32a634SThomas Huth            return (False, errstr)
63*fa32a634SThomas Huth
64*fa32a634SThomas Huth    return (True, '')
65*fa32a634SThomas Huth
66*fa32a634SThomas Huthdef run_cmd(args):
67*fa32a634SThomas Huth    subp = subprocess.Popen(args,
68*fa32a634SThomas Huth                            stdout=subprocess.PIPE,
69*fa32a634SThomas Huth                            stderr=subprocess.PIPE,
70*fa32a634SThomas Huth                            universal_newlines=True)
71*fa32a634SThomas Huth    stdout, stderr = subp.communicate()
72*fa32a634SThomas Huth    ret = subp.returncode
73*fa32a634SThomas Huth
74*fa32a634SThomas Huth    return (stdout, stderr, ret)
75*fa32a634SThomas Huth
76*fa32a634SThomas Huthdef is_readable_executable_file(path):
77*fa32a634SThomas Huth    return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
78*fa32a634SThomas Huth
79*fa32a634SThomas Huthdef _console_interaction(test, success_message, failure_message,
80*fa32a634SThomas Huth                         send_string, keep_sending=False, vm=None):
81*fa32a634SThomas Huth    assert not keep_sending or send_string
82*fa32a634SThomas Huth    if vm is None:
83*fa32a634SThomas Huth        vm = test.vm
84*fa32a634SThomas Huth    console = vm.console_file
85*fa32a634SThomas Huth    console_logger = logging.getLogger('console')
86*fa32a634SThomas Huth    while True:
87*fa32a634SThomas Huth        if send_string:
88*fa32a634SThomas Huth            vm.console_socket.sendall(send_string.encode())
89*fa32a634SThomas Huth            if not keep_sending:
90*fa32a634SThomas Huth                send_string = None # send only once
91*fa32a634SThomas Huth
92*fa32a634SThomas Huth        # Only consume console output if waiting for something
93*fa32a634SThomas Huth        if success_message is None and failure_message is None:
94*fa32a634SThomas Huth            if send_string is None:
95*fa32a634SThomas Huth                break
96*fa32a634SThomas Huth            continue
97*fa32a634SThomas Huth
98*fa32a634SThomas Huth        try:
99*fa32a634SThomas Huth            msg = console.readline().decode().strip()
100*fa32a634SThomas Huth        except UnicodeDecodeError:
101*fa32a634SThomas Huth            msg = None
102*fa32a634SThomas Huth        if not msg:
103*fa32a634SThomas Huth            continue
104*fa32a634SThomas Huth        console_logger.debug(msg)
105*fa32a634SThomas Huth        if success_message is None or success_message in msg:
106*fa32a634SThomas Huth            break
107*fa32a634SThomas Huth        if failure_message and failure_message in msg:
108*fa32a634SThomas Huth            console.close()
109*fa32a634SThomas Huth            fail = 'Failure message found in console: "%s". Expected: "%s"' % \
110*fa32a634SThomas Huth                    (failure_message, success_message)
111*fa32a634SThomas Huth            test.fail(fail)
112*fa32a634SThomas Huth
113*fa32a634SThomas Huthdef interrupt_interactive_console_until_pattern(test, success_message,
114*fa32a634SThomas Huth                                                failure_message=None,
115*fa32a634SThomas Huth                                                interrupt_string='\r'):
116*fa32a634SThomas Huth    """
117*fa32a634SThomas Huth    Keep sending a string to interrupt a console prompt, while logging the
118*fa32a634SThomas Huth    console output. Typical use case is to break a boot loader prompt, such:
119*fa32a634SThomas Huth
120*fa32a634SThomas Huth        Press a key within 5 seconds to interrupt boot process.
121*fa32a634SThomas Huth        5
122*fa32a634SThomas Huth        4
123*fa32a634SThomas Huth        3
124*fa32a634SThomas Huth        2
125*fa32a634SThomas Huth        1
126*fa32a634SThomas Huth        Booting default image...
127*fa32a634SThomas Huth
128*fa32a634SThomas Huth    :param test: a  test containing a VM that will have its console
129*fa32a634SThomas Huth                 read and probed for a success or failure message
130*fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
131*fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
132*fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
133*fa32a634SThomas Huth    :param interrupt_string: a string to send to the console before trying
134*fa32a634SThomas Huth                             to read a new line
135*fa32a634SThomas Huth    """
136*fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message,
137*fa32a634SThomas Huth                         interrupt_string, True)
138*fa32a634SThomas Huth
139*fa32a634SThomas Huthdef wait_for_console_pattern(test, success_message, failure_message=None,
140*fa32a634SThomas Huth                             vm=None):
141*fa32a634SThomas Huth    """
142*fa32a634SThomas Huth    Waits for messages to appear on the console, while logging the content
143*fa32a634SThomas Huth
144*fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
145*fa32a634SThomas Huth                 read and probed for a success or failure message
146*fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
147*fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
148*fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
149*fa32a634SThomas Huth    """
150*fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, None, vm=vm)
151*fa32a634SThomas Huth
152*fa32a634SThomas Huthdef exec_command(test, command):
153*fa32a634SThomas Huth    """
154*fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), while logging
155*fa32a634SThomas Huth    the content.
156*fa32a634SThomas Huth
157*fa32a634SThomas Huth    :param test: a test containing a VM.
158*fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
159*fa32a634SThomas Huth    :param command: the command to send
160*fa32a634SThomas Huth    :type command: str
161*fa32a634SThomas Huth    """
162*fa32a634SThomas Huth    _console_interaction(test, None, None, command + '\r')
163*fa32a634SThomas Huth
164*fa32a634SThomas Huthdef exec_command_and_wait_for_pattern(test, command,
165*fa32a634SThomas Huth                                      success_message, failure_message=None):
166*fa32a634SThomas Huth    """
167*fa32a634SThomas Huth    Send a command to a console (appending CRLF characters), then wait
168*fa32a634SThomas Huth    for success_message to appear on the console, while logging the.
169*fa32a634SThomas Huth    content. Mark the test as failed if failure_message is found instead.
170*fa32a634SThomas Huth
171*fa32a634SThomas Huth    :param test: a test containing a VM that will have its console
172*fa32a634SThomas Huth                 read and probed for a success or failure message
173*fa32a634SThomas Huth    :type test: :class:`qemu_test.QemuSystemTest`
174*fa32a634SThomas Huth    :param command: the command to send
175*fa32a634SThomas Huth    :param success_message: if this message appears, test succeeds
176*fa32a634SThomas Huth    :param failure_message: if this message appears, test fails
177*fa32a634SThomas Huth    """
178*fa32a634SThomas Huth    _console_interaction(test, success_message, failure_message, command + '\r')
179