1 # Test class and utilities for functional tests
3 # Copyright (c) 2018 Red Hat, Inc.
6 # Cleber Rosa <crosa@redhat.com>
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.
19 #: The QEMU build root directory. It may also be the source directory
20 #: if building from the source dir, but it's safer to use BUILD_DIR for
21 #: that purpose. Be aware that if this code is moved outside of a source
22 #: and build tree, it will not be accurate.
23 BUILD_DIR
= os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(__file__
))))
25 if os
.path
.islink(os
.path
.dirname(os
.path
.dirname(__file__
))):
26 # The link to the acceptance tests dir in the source code directory
27 lnk
= os
.path
.dirname(os
.path
.dirname(__file__
))
28 #: The QEMU root source directory
29 SOURCE_DIR
= os
.path
.dirname(os
.path
.dirname(os
.readlink(lnk
)))
31 SOURCE_DIR
= BUILD_DIR
33 sys
.path
.append(os
.path
.join(SOURCE_DIR
, 'python'))
35 from qemu
.machine
import QEMUMachine
37 def is_readable_executable_file(path
):
38 return os
.path
.isfile(path
) and os
.access(path
, os
.R_OK | os
.X_OK
)
41 def pick_default_qemu_bin(arch
=None):
43 Picks the path of a QEMU binary, starting either in the current working
44 directory or in the source tree root directory.
46 :param arch: the arch to use when looking for a QEMU binary (the target
47 will match the arch given). If None (the default), arch
48 will be the current host system arch (as given by
51 :returns: the path to the default QEMU binary or None if one could not
57 # qemu binary path does not match arch for powerpc, handle it
60 qemu_bin_relative_path
= "./qemu-system-%s" % arch
61 if is_readable_executable_file(qemu_bin_relative_path
):
62 return qemu_bin_relative_path
64 qemu_bin_from_bld_dir_path
= os
.path
.join(BUILD_DIR
,
65 qemu_bin_relative_path
)
66 if is_readable_executable_file(qemu_bin_from_bld_dir_path
):
67 return qemu_bin_from_bld_dir_path
70 def _console_interaction(test
, success_message
, failure_message
,
71 send_string
, keep_sending
=False, vm
=None):
72 assert not keep_sending
or send_string
75 console
= vm
.console_socket
.makefile()
76 console_logger
= logging
.getLogger('console')
79 vm
.console_socket
.sendall(send_string
.encode())
81 send_string
= None # send only once
82 msg
= console
.readline().strip()
85 console_logger
.debug(msg
)
86 if success_message
in msg
:
88 if failure_message
and failure_message
in msg
:
90 fail
= 'Failure message found in console: %s' % failure_message
93 def interrupt_interactive_console_until_pattern(test
, success_message
,
95 interrupt_string
='\r'):
97 Keep sending a string to interrupt a console prompt, while logging the
98 console output. Typical use case is to break a boot loader prompt, such:
100 Press a key within 5 seconds to interrupt boot process.
106 Booting default image...
108 :param test: an Avocado test containing a VM that will have its console
109 read and probed for a success or failure message
110 :type test: :class:`avocado_qemu.Test`
111 :param success_message: if this message appears, test succeeds
112 :param failure_message: if this message appears, test fails
113 :param interrupt_string: a string to send to the console before trying
116 _console_interaction(test
, success_message
, failure_message
,
117 interrupt_string
, True)
119 def wait_for_console_pattern(test
, success_message
, failure_message
=None,
122 Waits for messages to appear on the console, while logging the content
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 success_message: if this message appears, test succeeds
128 :param failure_message: if this message appears, test fails
130 _console_interaction(test
, success_message
, failure_message
, None, vm
=vm
)
132 def exec_command_and_wait_for_pattern(test
, command
,
133 success_message
, failure_message
=None):
135 Send a command to a console (appending CRLF characters), then wait
136 for success_message to appear on the console, while logging the.
137 content. Mark the test as failed if failure_message is found instead.
139 :param test: an Avocado test containing a VM that will have its console
140 read and probed for a success or failure message
141 :type test: :class:`avocado_qemu.Test`
142 :param command: the command to send
143 :param success_message: if this message appears, test succeeds
144 :param failure_message: if this message appears, test fails
146 _console_interaction(test
, success_message
, failure_message
, command
+ '\r')
148 class Test(avocado
.Test
):
149 def _get_unique_tag_val(self
, tag_name
):
151 Gets a tag value, if unique for a key
153 vals
= self
.tags
.get(tag_name
, [])
161 self
.arch
= self
.params
.get('arch',
162 default
=self
._get
_unique
_tag
_val
('arch'))
164 self
.machine
= self
.params
.get('machine',
165 default
=self
._get
_unique
_tag
_val
('machine'))
167 default_qemu_bin
= pick_default_qemu_bin(arch
=self
.arch
)
168 self
.qemu_bin
= self
.params
.get('qemu_bin',
169 default
=default_qemu_bin
)
170 if self
.qemu_bin
is None:
171 self
.cancel("No QEMU binary defined or found in the build tree")
173 def _new_vm(self
, *args
):
174 vm
= QEMUMachine(self
.qemu_bin
, sock_dir
=tempfile
.mkdtemp())
181 return self
.get_vm(name
='default')
183 def get_vm(self
, *args
, name
=None):
185 name
= str(uuid
.uuid4())
186 if self
._vms
.get(name
) is None:
187 self
._vms
[name
] = self
._new
_vm
(*args
)
188 if self
.machine
is not None:
189 self
._vms
[name
].set_machine(self
.machine
)
190 return self
._vms
[name
]
193 for vm
in self
._vms
.values():
196 def fetch_asset(self
, name
,
197 asset_hash
=None, algorithm
=None,
198 locations
=None, expire
=None,
199 find_only
=False, cancel_on_missing
=True):
200 return super(Test
, self
).fetch_asset(name
,
201 asset_hash
=asset_hash
,
206 cancel_on_missing
=cancel_on_missing
)