 64ed6f92ff
			
		
	
	
		64ed6f92ff
		
	
	
	
	
		
			
			The binaries move to the root directory, e.g. qemu-system-i386 or qemu-arm. This requires changes to qtests, CI, etc. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
		
			
				
	
	
		
			195 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Test class and utilities for functional tests
 | |
| #
 | |
| # Copyright (c) 2018 Red Hat, Inc.
 | |
| #
 | |
| # Author:
 | |
| #  Cleber Rosa <crosa@redhat.com>
 | |
| #
 | |
| # This work is licensed under the terms of the GNU GPL, version 2 or
 | |
| # later.  See the COPYING file in the top-level directory.
 | |
| 
 | |
| import logging
 | |
| import os
 | |
| import sys
 | |
| import uuid
 | |
| import tempfile
 | |
| 
 | |
| import avocado
 | |
| 
 | |
| #: The QEMU build root directory.  It may also be the source directory
 | |
| #: if building from the source dir, but it's safer to use BUILD_DIR for
 | |
| #: that purpose.  Be aware that if this code is moved outside of a source
 | |
| #: and build tree, it will not be accurate.
 | |
| BUILD_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
 | |
| 
 | |
| if os.path.islink(os.path.dirname(os.path.dirname(__file__))):
 | |
|     # The link to the acceptance tests dir in the source code directory
 | |
|     lnk = os.path.dirname(os.path.dirname(__file__))
 | |
|     #: The QEMU root source directory
 | |
|     SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk)))
 | |
| else:
 | |
|     SOURCE_DIR = BUILD_DIR
 | |
| 
 | |
| sys.path.append(os.path.join(SOURCE_DIR, 'python'))
 | |
| 
 | |
| from qemu.machine import QEMUMachine
 | |
| 
 | |
| def is_readable_executable_file(path):
 | |
|     return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
 | |
| 
 | |
| 
 | |
| def pick_default_qemu_bin(arch=None):
 | |
|     """
 | |
|     Picks the path of a QEMU binary, starting either in the current working
 | |
|     directory or in the source tree root directory.
 | |
| 
 | |
|     :param arch: the arch to use when looking for a QEMU binary (the target
 | |
|                  will match the arch given).  If None (the default), arch
 | |
|                  will be the current host system arch (as given by
 | |
|                  :func:`os.uname`).
 | |
|     :type arch: str
 | |
|     :returns: the path to the default QEMU binary or None if one could not
 | |
|               be found
 | |
|     :rtype: str or None
 | |
|     """
 | |
|     if arch is None:
 | |
|         arch = os.uname()[4]
 | |
|     # qemu binary path does not match arch for powerpc, handle it
 | |
|     if 'ppc64le' in arch:
 | |
|         arch = 'ppc64'
 | |
|     qemu_bin_relative_path = "./qemu-system-%s" % arch
 | |
|     if is_readable_executable_file(qemu_bin_relative_path):
 | |
|         return qemu_bin_relative_path
 | |
| 
 | |
|     qemu_bin_from_bld_dir_path = os.path.join(BUILD_DIR,
 | |
|                                               qemu_bin_relative_path)
 | |
|     if is_readable_executable_file(qemu_bin_from_bld_dir_path):
 | |
|         return qemu_bin_from_bld_dir_path
 | |
| 
 | |
| 
 | |
| def _console_interaction(test, success_message, failure_message,
 | |
|                          send_string, keep_sending=False, vm=None):
 | |
|     assert not keep_sending or send_string
 | |
|     if vm is None:
 | |
|         vm = test.vm
 | |
|     console = vm.console_socket.makefile()
 | |
|     console_logger = logging.getLogger('console')
 | |
|     while True:
 | |
|         if send_string:
 | |
|             vm.console_socket.sendall(send_string.encode())
 | |
|             if not keep_sending:
 | |
|                 send_string = None # send only once
 | |
|         msg = console.readline().strip()
 | |
|         if not msg:
 | |
|             continue
 | |
|         console_logger.debug(msg)
 | |
|         if success_message in msg:
 | |
|             break
 | |
|         if failure_message and failure_message in msg:
 | |
|             console.close()
 | |
|             fail = 'Failure message found in console: %s' % failure_message
 | |
|             test.fail(fail)
 | |
| 
 | |
| def interrupt_interactive_console_until_pattern(test, success_message,
 | |
|                                                 failure_message=None,
 | |
|                                                 interrupt_string='\r'):
 | |
|     """
 | |
|     Keep sending a string to interrupt a console prompt, while logging the
 | |
|     console output. Typical use case is to break a boot loader prompt, such:
 | |
| 
 | |
|         Press a key within 5 seconds to interrupt boot process.
 | |
|         5
 | |
|         4
 | |
|         3
 | |
|         2
 | |
|         1
 | |
|         Booting default image...
 | |
| 
 | |
|     :param test: an Avocado test containing a VM that will have its console
 | |
|                  read and probed for a success or failure message
 | |
|     :type test: :class:`avocado_qemu.Test`
 | |
|     :param success_message: if this message appears, test succeeds
 | |
|     :param failure_message: if this message appears, test fails
 | |
|     :param interrupt_string: a string to send to the console before trying
 | |
|                              to read a new line
 | |
|     """
 | |
|     _console_interaction(test, success_message, failure_message,
 | |
|                          interrupt_string, True)
 | |
| 
 | |
| def wait_for_console_pattern(test, success_message, failure_message=None,
 | |
|                              vm=None):
 | |
|     """
 | |
|     Waits for messages to appear on the console, while logging the content
 | |
| 
 | |
|     :param test: an Avocado test containing a VM that will have its console
 | |
|                  read and probed for a success or failure message
 | |
|     :type test: :class:`avocado_qemu.Test`
 | |
|     :param success_message: if this message appears, test succeeds
 | |
|     :param failure_message: if this message appears, test fails
 | |
|     """
 | |
|     _console_interaction(test, success_message, failure_message, None, vm=vm)
 | |
| 
 | |
| def exec_command_and_wait_for_pattern(test, command,
 | |
|                                       success_message, failure_message=None):
 | |
|     """
 | |
|     Send a command to a console (appending CRLF characters), then wait
 | |
|     for success_message to appear on the console, while logging the.
 | |
|     content. Mark the test as failed if failure_message is found instead.
 | |
| 
 | |
|     :param test: an Avocado test containing a VM that will have its console
 | |
|                  read and probed for a success or failure message
 | |
|     :type test: :class:`avocado_qemu.Test`
 | |
|     :param command: the command to send
 | |
|     :param success_message: if this message appears, test succeeds
 | |
|     :param failure_message: if this message appears, test fails
 | |
|     """
 | |
|     _console_interaction(test, success_message, failure_message, command + '\r')
 | |
| 
 | |
| class Test(avocado.Test):
 | |
|     def _get_unique_tag_val(self, tag_name):
 | |
|         """
 | |
|         Gets a tag value, if unique for a key
 | |
|         """
 | |
|         vals = self.tags.get(tag_name, [])
 | |
|         if len(vals) == 1:
 | |
|             return vals.pop()
 | |
|         return None
 | |
| 
 | |
|     def setUp(self):
 | |
|         self._vms = {}
 | |
| 
 | |
|         self.arch = self.params.get('arch',
 | |
|                                     default=self._get_unique_tag_val('arch'))
 | |
| 
 | |
|         self.machine = self.params.get('machine',
 | |
|                                        default=self._get_unique_tag_val('machine'))
 | |
| 
 | |
|         default_qemu_bin = pick_default_qemu_bin(arch=self.arch)
 | |
|         self.qemu_bin = self.params.get('qemu_bin',
 | |
|                                         default=default_qemu_bin)
 | |
|         if self.qemu_bin is None:
 | |
|             self.cancel("No QEMU binary defined or found in the build tree")
 | |
| 
 | |
|     def _new_vm(self, *args):
 | |
|         vm = QEMUMachine(self.qemu_bin, sock_dir=tempfile.mkdtemp())
 | |
|         if args:
 | |
|             vm.add_args(*args)
 | |
|         return vm
 | |
| 
 | |
|     @property
 | |
|     def vm(self):
 | |
|         return self.get_vm(name='default')
 | |
| 
 | |
|     def get_vm(self, *args, name=None):
 | |
|         if not name:
 | |
|             name = str(uuid.uuid4())
 | |
|         if self._vms.get(name) is None:
 | |
|             self._vms[name] = self._new_vm(*args)
 | |
|             if self.machine is not None:
 | |
|                 self._vms[name].set_machine(self.machine)
 | |
|         return self._vms[name]
 | |
| 
 | |
|     def tearDown(self):
 | |
|         for vm in self._vms.values():
 | |
|             vm.shutdown()
 |