]> git.ipfire.org Git - thirdparty/qemu.git/blob - tests/acceptance/avocado_qemu/__init__.py
d4358eb43196eeac89d9682a54ea2534b4ae0e30
[thirdparty/qemu.git] / tests / acceptance / avocado_qemu / __init__.py
1 # Test class and utilities for functional tests
2 #
3 # Copyright (c) 2018 Red Hat, Inc.
4 #
5 # Author:
6 # Cleber Rosa <crosa@redhat.com>
7 #
8 # This work is licensed under the terms of the GNU GPL, version 2 or
9 # later. See the COPYING file in the top-level directory.
10
11 import logging
12 import os
13 import sys
14 import uuid
15 import tempfile
16
17 import avocado
18
19 SRC_ROOT_DIR = os.path.join(os.path.dirname(__file__), '..', '..', '..')
20 sys.path.append(os.path.join(SRC_ROOT_DIR, 'python'))
21
22 from qemu.machine import QEMUMachine
23
24 def is_readable_executable_file(path):
25 return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
26
27
28 def pick_default_qemu_bin(arch=None):
29 """
30 Picks the path of a QEMU binary, starting either in the current working
31 directory or in the source tree root directory.
32
33 :param arch: the arch to use when looking for a QEMU binary (the target
34 will match the arch given). If None (the default), arch
35 will be the current host system arch (as given by
36 :func:`os.uname`).
37 :type arch: str
38 :returns: the path to the default QEMU binary or None if one could not
39 be found
40 :rtype: str or None
41 """
42 if arch is None:
43 arch = os.uname()[4]
44 # qemu binary path does not match arch for powerpc, handle it
45 if 'ppc64le' in arch:
46 arch = 'ppc64'
47 qemu_bin_relative_path = os.path.join("%s-softmmu" % arch,
48 "qemu-system-%s" % arch)
49 if is_readable_executable_file(qemu_bin_relative_path):
50 return qemu_bin_relative_path
51
52 qemu_bin_from_src_dir_path = os.path.join(SRC_ROOT_DIR,
53 qemu_bin_relative_path)
54 if is_readable_executable_file(qemu_bin_from_src_dir_path):
55 return qemu_bin_from_src_dir_path
56
57
58 def _console_interaction(test, success_message, failure_message,
59 send_string, keep_sending=False):
60 assert not keep_sending or send_string
61 console = test.vm.console_socket.makefile()
62 console_logger = logging.getLogger('console')
63 while True:
64 if send_string:
65 test.vm.console_socket.sendall(send_string.encode())
66 if not keep_sending:
67 send_string = None # send only once
68 msg = console.readline().strip()
69 if not msg:
70 continue
71 console_logger.debug(msg)
72 if success_message in msg:
73 break
74 if failure_message and failure_message in msg:
75 console.close()
76 fail = 'Failure message found in console: %s' % failure_message
77 test.fail(fail)
78
79 def interrupt_interactive_console_until_pattern(test, success_message,
80 failure_message=None,
81 interrupt_string='\r'):
82 """
83 Keep sending a string to interrupt a console prompt, while logging the
84 console output. Typical use case is to break a boot loader prompt, such:
85
86 Press a key within 5 seconds to interrupt boot process.
87 5
88 4
89 3
90 2
91 1
92 Booting default image...
93
94 :param test: an Avocado test containing a VM that will have its console
95 read and probed for a success or failure message
96 :type test: :class:`avocado_qemu.Test`
97 :param success_message: if this message appears, test succeeds
98 :param failure_message: if this message appears, test fails
99 :param interrupt_string: a string to send to the console before trying
100 to read a new line
101 """
102 _console_interaction(test, success_message, failure_message,
103 interrupt_string, True)
104
105 def wait_for_console_pattern(test, success_message, failure_message=None):
106 """
107 Waits for messages to appear on the console, while logging the content
108
109 :param test: an Avocado test containing a VM that will have its console
110 read and probed for a success or failure message
111 :type test: :class:`avocado_qemu.Test`
112 :param success_message: if this message appears, test succeeds
113 :param failure_message: if this message appears, test fails
114 """
115 _console_interaction(test, success_message, failure_message, None)
116
117 def exec_command_and_wait_for_pattern(test, command,
118 success_message, failure_message=None):
119 """
120 Send a command to a console (appending CRLF characters), then wait
121 for success_message to appear on the console, while logging the.
122 content. Mark the test as failed if failure_message is found instead.
123
124 :param test: an Avocado test containing a VM that will have its console
125 read and probed for a success or failure message
126 :type test: :class:`avocado_qemu.Test`
127 :param command: the command to send
128 :param success_message: if this message appears, test succeeds
129 :param failure_message: if this message appears, test fails
130 """
131 _console_interaction(test, success_message, failure_message, command + '\r')
132
133 class Test(avocado.Test):
134 def _get_unique_tag_val(self, tag_name):
135 """
136 Gets a tag value, if unique for a key
137 """
138 vals = self.tags.get(tag_name, [])
139 if len(vals) == 1:
140 return vals.pop()
141 return None
142
143 def setUp(self):
144 self._vms = {}
145
146 self.arch = self.params.get('arch',
147 default=self._get_unique_tag_val('arch'))
148
149 self.machine = self.params.get('machine',
150 default=self._get_unique_tag_val('machine'))
151
152 default_qemu_bin = pick_default_qemu_bin(arch=self.arch)
153 self.qemu_bin = self.params.get('qemu_bin',
154 default=default_qemu_bin)
155 if self.qemu_bin is None:
156 self.cancel("No QEMU binary defined or found in the source tree")
157
158 def _new_vm(self, *args):
159 vm = QEMUMachine(self.qemu_bin, sock_dir=tempfile.mkdtemp())
160 if args:
161 vm.add_args(*args)
162 return vm
163
164 @property
165 def vm(self):
166 return self.get_vm(name='default')
167
168 def get_vm(self, *args, name=None):
169 if not name:
170 name = str(uuid.uuid4())
171 if self._vms.get(name) is None:
172 self._vms[name] = self._new_vm(*args)
173 if self.machine is not None:
174 self._vms[name].set_machine(self.machine)
175 return self._vms[name]
176
177 def tearDown(self):
178 for vm in self._vms.values():
179 vm.shutdown()