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