|
@@ -11,13 +11,14 @@
|
|
import logging
|
|
import logging
|
|
import os
|
|
import os
|
|
import shutil
|
|
import shutil
|
|
|
|
+import subprocess
|
|
import sys
|
|
import sys
|
|
import tempfile
|
|
import tempfile
|
|
import time
|
|
import time
|
|
import uuid
|
|
import uuid
|
|
|
|
|
|
import avocado
|
|
import avocado
|
|
-from avocado.utils import cloudinit, datadrainer, network, ssh, vmimage
|
|
|
|
|
|
+from avocado.utils import cloudinit, datadrainer, network, process, ssh, vmimage
|
|
from avocado.utils.path import find_command
|
|
from avocado.utils.path import find_command
|
|
|
|
|
|
#: The QEMU build root directory. It may also be the source directory
|
|
#: The QEMU build root directory. It may also be the source directory
|
|
@@ -27,7 +28,7 @@
|
|
BUILD_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
|
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__))):
|
|
if os.path.islink(os.path.dirname(os.path.dirname(__file__))):
|
|
- # The link to the acceptance tests dir in the source code directory
|
|
|
|
|
|
+ # The link to the avocado tests dir in the source code directory
|
|
lnk = os.path.dirname(os.path.dirname(__file__))
|
|
lnk = os.path.dirname(os.path.dirname(__file__))
|
|
#: The QEMU root source directory
|
|
#: The QEMU root source directory
|
|
SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk)))
|
|
SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk)))
|
|
@@ -41,11 +42,67 @@
|
|
tcg_available)
|
|
tcg_available)
|
|
|
|
|
|
|
|
|
|
|
|
+def has_cmd(name, args=None):
|
|
|
|
+ """
|
|
|
|
+ This function is for use in a @avocado.skipUnless decorator, e.g.:
|
|
|
|
+
|
|
|
|
+ @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
|
|
|
|
+ def test_something_that_needs_sudo(self):
|
|
|
|
+ ...
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ if args is None:
|
|
|
|
+ args = ('which', name)
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ _, stderr, exitcode = run_cmd(args)
|
|
|
|
+ except Exception as e:
|
|
|
|
+ exitcode = -1
|
|
|
|
+ stderr = str(e)
|
|
|
|
+
|
|
|
|
+ if exitcode != 0:
|
|
|
|
+ cmd_line = ' '.join(args)
|
|
|
|
+ err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
|
|
|
|
+ return (False, err)
|
|
|
|
+ else:
|
|
|
|
+ return (True, '')
|
|
|
|
+
|
|
|
|
+def has_cmds(*cmds):
|
|
|
|
+ """
|
|
|
|
+ This function is for use in a @avocado.skipUnless decorator and
|
|
|
|
+ allows checking for the availability of multiple commands, e.g.:
|
|
|
|
+
|
|
|
|
+ @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
|
|
|
|
+ 'cmd2', 'cmd3'))
|
|
|
|
+ def test_something_that_needs_cmd1_and_cmd2(self):
|
|
|
|
+ ...
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ for cmd in cmds:
|
|
|
|
+ if isinstance(cmd, str):
|
|
|
|
+ cmd = (cmd,)
|
|
|
|
+
|
|
|
|
+ ok, errstr = has_cmd(*cmd)
|
|
|
|
+ if not ok:
|
|
|
|
+ return (False, errstr)
|
|
|
|
+
|
|
|
|
+ return (True, '')
|
|
|
|
+
|
|
|
|
+def run_cmd(args):
|
|
|
|
+ subp = subprocess.Popen(args,
|
|
|
|
+ stdout=subprocess.PIPE,
|
|
|
|
+ stderr=subprocess.PIPE,
|
|
|
|
+ universal_newlines=True)
|
|
|
|
+ stdout, stderr = subp.communicate()
|
|
|
|
+ ret = subp.returncode
|
|
|
|
+
|
|
|
|
+ return (stdout, stderr, ret)
|
|
|
|
+
|
|
def is_readable_executable_file(path):
|
|
def is_readable_executable_file(path):
|
|
return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
|
|
return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK)
|
|
|
|
|
|
|
|
|
|
-def pick_default_qemu_bin(arch=None):
|
|
|
|
|
|
+def pick_default_qemu_bin(bin_prefix='qemu-system-', arch=None):
|
|
"""
|
|
"""
|
|
Picks the path of a QEMU binary, starting either in the current working
|
|
Picks the path of a QEMU binary, starting either in the current working
|
|
directory or in the source tree root directory.
|
|
directory or in the source tree root directory.
|
|
@@ -64,7 +121,7 @@ def pick_default_qemu_bin(arch=None):
|
|
# qemu binary path does not match arch for powerpc, handle it
|
|
# qemu binary path does not match arch for powerpc, handle it
|
|
if 'ppc64le' in arch:
|
|
if 'ppc64le' in arch:
|
|
arch = 'ppc64'
|
|
arch = 'ppc64'
|
|
- qemu_bin_relative_path = "./qemu-system-%s" % arch
|
|
|
|
|
|
+ qemu_bin_relative_path = os.path.join(".", bin_prefix + arch)
|
|
if is_readable_executable_file(qemu_bin_relative_path):
|
|
if is_readable_executable_file(qemu_bin_relative_path):
|
|
return qemu_bin_relative_path
|
|
return qemu_bin_relative_path
|
|
|
|
|
|
@@ -119,7 +176,7 @@ def interrupt_interactive_console_until_pattern(test, success_message,
|
|
|
|
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
read and probed for a success or failure message
|
|
- :type test: :class:`avocado_qemu.Test`
|
|
|
|
|
|
+ :type test: :class:`avocado_qemu.QemuSystemTest`
|
|
:param success_message: if this message appears, test succeeds
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
:param failure_message: if this message appears, test fails
|
|
:param interrupt_string: a string to send to the console before trying
|
|
:param interrupt_string: a string to send to the console before trying
|
|
@@ -135,7 +192,7 @@ def wait_for_console_pattern(test, success_message, failure_message=None,
|
|
|
|
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
read and probed for a success or failure message
|
|
- :type test: :class:`avocado_qemu.Test`
|
|
|
|
|
|
+ :type test: :class:`avocado_qemu.QemuSystemTest`
|
|
:param success_message: if this message appears, test succeeds
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
:param failure_message: if this message appears, test fails
|
|
"""
|
|
"""
|
|
@@ -147,7 +204,7 @@ def exec_command(test, command):
|
|
the content.
|
|
the content.
|
|
|
|
|
|
:param test: an Avocado test containing a VM.
|
|
:param test: an Avocado test containing a VM.
|
|
- :type test: :class:`avocado_qemu.Test`
|
|
|
|
|
|
+ :type test: :class:`avocado_qemu.QemuSystemTest`
|
|
:param command: the command to send
|
|
:param command: the command to send
|
|
:type command: str
|
|
:type command: str
|
|
"""
|
|
"""
|
|
@@ -162,14 +219,14 @@ def exec_command_and_wait_for_pattern(test, command,
|
|
|
|
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
:param test: an Avocado test containing a VM that will have its console
|
|
read and probed for a success or failure message
|
|
read and probed for a success or failure message
|
|
- :type test: :class:`avocado_qemu.Test`
|
|
|
|
|
|
+ :type test: :class:`avocado_qemu.QemuSystemTest`
|
|
:param command: the command to send
|
|
:param command: the command to send
|
|
:param success_message: if this message appears, test succeeds
|
|
:param success_message: if this message appears, test succeeds
|
|
:param failure_message: if this message appears, test fails
|
|
:param failure_message: if this message appears, test fails
|
|
"""
|
|
"""
|
|
_console_interaction(test, success_message, failure_message, command + '\r')
|
|
_console_interaction(test, success_message, failure_message, command + '\r')
|
|
|
|
|
|
-class Test(avocado.Test):
|
|
|
|
|
|
+class QemuBaseTest(avocado.Test):
|
|
def _get_unique_tag_val(self, tag_name):
|
|
def _get_unique_tag_val(self, tag_name):
|
|
"""
|
|
"""
|
|
Gets a tag value, if unique for a key
|
|
Gets a tag value, if unique for a key
|
|
@@ -179,6 +236,43 @@ def _get_unique_tag_val(self, tag_name):
|
|
return vals.pop()
|
|
return vals.pop()
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
+ def setUp(self, bin_prefix):
|
|
|
|
+ self.arch = self.params.get('arch',
|
|
|
|
+ default=self._get_unique_tag_val('arch'))
|
|
|
|
+
|
|
|
|
+ self.cpu = self.params.get('cpu',
|
|
|
|
+ default=self._get_unique_tag_val('cpu'))
|
|
|
|
+
|
|
|
|
+ default_qemu_bin = pick_default_qemu_bin(bin_prefix, 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 fetch_asset(self, name,
|
|
|
|
+ asset_hash=None, algorithm=None,
|
|
|
|
+ locations=None, expire=None,
|
|
|
|
+ find_only=False, cancel_on_missing=True):
|
|
|
|
+ return super().fetch_asset(name,
|
|
|
|
+ asset_hash=asset_hash,
|
|
|
|
+ algorithm=algorithm,
|
|
|
|
+ locations=locations,
|
|
|
|
+ expire=expire,
|
|
|
|
+ find_only=find_only,
|
|
|
|
+ cancel_on_missing=cancel_on_missing)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class QemuSystemTest(QemuBaseTest):
|
|
|
|
+ """Facilitates system emulation tests."""
|
|
|
|
+
|
|
|
|
+ def setUp(self):
|
|
|
|
+ self._vms = {}
|
|
|
|
+
|
|
|
|
+ super().setUp('qemu-system-')
|
|
|
|
+
|
|
|
|
+ self.machine = self.params.get('machine',
|
|
|
|
+ default=self._get_unique_tag_val('machine'))
|
|
|
|
+
|
|
def require_accelerator(self, accelerator):
|
|
def require_accelerator(self, accelerator):
|
|
"""
|
|
"""
|
|
Requires an accelerator to be available for the test to continue
|
|
Requires an accelerator to be available for the test to continue
|
|
@@ -201,24 +295,6 @@ def require_accelerator(self, accelerator):
|
|
self.cancel("%s accelerator does not seem to be "
|
|
self.cancel("%s accelerator does not seem to be "
|
|
"available" % accelerator)
|
|
"available" % accelerator)
|
|
|
|
|
|
- def setUp(self):
|
|
|
|
- self._vms = {}
|
|
|
|
-
|
|
|
|
- self.arch = self.params.get('arch',
|
|
|
|
- default=self._get_unique_tag_val('arch'))
|
|
|
|
-
|
|
|
|
- self.cpu = self.params.get('cpu',
|
|
|
|
- default=self._get_unique_tag_val('cpu'))
|
|
|
|
-
|
|
|
|
- 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, name, *args):
|
|
def _new_vm(self, name, *args):
|
|
self._sd = tempfile.TemporaryDirectory(prefix="avo_qemu_sock_")
|
|
self._sd = tempfile.TemporaryDirectory(prefix="avo_qemu_sock_")
|
|
vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir,
|
|
vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir,
|
|
@@ -272,17 +348,22 @@ def tearDown(self):
|
|
self._sd = None
|
|
self._sd = None
|
|
super().tearDown()
|
|
super().tearDown()
|
|
|
|
|
|
- def fetch_asset(self, name,
|
|
|
|
- asset_hash=None, algorithm=None,
|
|
|
|
- locations=None, expire=None,
|
|
|
|
- find_only=False, cancel_on_missing=True):
|
|
|
|
- return super().fetch_asset(name,
|
|
|
|
- asset_hash=asset_hash,
|
|
|
|
- algorithm=algorithm,
|
|
|
|
- locations=locations,
|
|
|
|
- expire=expire,
|
|
|
|
- find_only=find_only,
|
|
|
|
- cancel_on_missing=cancel_on_missing)
|
|
|
|
|
|
+
|
|
|
|
+class QemuUserTest(QemuBaseTest):
|
|
|
|
+ """Facilitates user-mode emulation tests."""
|
|
|
|
+
|
|
|
|
+ def setUp(self):
|
|
|
|
+ self._ldpath = []
|
|
|
|
+ super().setUp('qemu-')
|
|
|
|
+
|
|
|
|
+ def add_ldpath(self, ldpath):
|
|
|
|
+ self._ldpath.append(os.path.abspath(ldpath))
|
|
|
|
+
|
|
|
|
+ def run(self, bin_path, args=[]):
|
|
|
|
+ qemu_args = " ".join(["-L %s" % ldpath for ldpath in self._ldpath])
|
|
|
|
+ bin_args = " ".join(args)
|
|
|
|
+ return process.run("%s %s %s %s" % (self.qemu_bin, qemu_args,
|
|
|
|
+ bin_path, bin_args))
|
|
|
|
|
|
|
|
|
|
class LinuxSSHMixIn:
|
|
class LinuxSSHMixIn:
|
|
@@ -424,11 +505,11 @@ def default_kernel_params(self):
|
|
return self._info.get('kernel_params', None)
|
|
return self._info.get('kernel_params', None)
|
|
|
|
|
|
|
|
|
|
-class LinuxTest(LinuxSSHMixIn, Test):
|
|
|
|
|
|
+class LinuxTest(LinuxSSHMixIn, QemuSystemTest):
|
|
"""Facilitates having a cloud-image Linux based available.
|
|
"""Facilitates having a cloud-image Linux based available.
|
|
|
|
|
|
- For tests that indend to interact with guests, this is a better choice
|
|
|
|
- to start with than the more vanilla `Test` class.
|
|
|
|
|
|
+ For tests that indent to interact with guests, this is a better choice
|
|
|
|
+ to start with than the more vanilla `QemuSystemTest` class.
|
|
"""
|
|
"""
|
|
|
|
|
|
timeout = 900
|
|
timeout = 900
|