From f84f8e71eb4bdd193e89c6550a015277b686b0af Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:31 +0000 Subject: tests/functional: add helpers for building file paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helper methods that construct paths for * log files - to be preserved at the end of a test * scratch files - to be purged at the end of a test * build files - anything relative to the build root * data files - anything relative to the functional test source root * socket files - a short temporary dir to avoid UNIX socket limits These are to be used instead of direct access to the self.workdir, or self.logdir variables, or any other place where paths are built manually. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-11-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 95 ++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 90ae59eb54..89425b737c 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -13,10 +13,12 @@ import logging import os +from pathlib import Path import pycotap import shutil import subprocess import sys +import tempfile import unittest import uuid @@ -37,9 +39,99 @@ class QemuBaseTest(unittest.TestCase): log = None logdir = None + ''' + Create a temporary directory suitable for storing UNIX + socket paths. + + Returns: a tempfile.TemporaryDirectory instance + ''' + def socket_dir(self): + if self.socketdir is None: + self.socketdir = tempfile.TemporaryDirectory( + prefix="qemu_func_test_sock_") + return self.socketdir + + ''' + @params args list of zero or more subdirectories or file + + Construct a path for accessing a data file located + relative to the source directory that is the root for + functional tests. + + @args may be an empty list to reference the root dir + itself, may be a single element to reference a file in + the root directory, or may be multiple elements to + reference a file nested below. The path components + will be joined using the platform appropriate path + separator. + + Returns: string representing a file path + ''' + def data_file(self, *args): + return str(Path(Path(__file__).parent.parent, *args)) + + ''' + @params args list of zero or more subdirectories or file + + Construct a path for accessing a data file located + relative to the build directory root. + + @args may be an empty list to reference the build dir + itself, may be a single element to reference a file in + the build directory, or may be multiple elements to + reference a file nested below. The path components + will be joined using the platform appropriate path + separator. + + Returns: string representing a file path + ''' + def build_file(self, *args): + return str(Path(BUILD_DIR, *args)) + + ''' + @params args list of zero or more subdirectories or file + + Construct a path for accessing/creating a scratch file + located relative to a temporary directory dedicated to + this test case. The directory and its contents will be + purged upon completion of the test. + + @args may be an empty list to reference the scratch dir + itself, may be a single element to reference a file in + the scratch directory, or may be multiple elements to + reference a file nested below. The path components + will be joined using the platform appropriate path + separator. + + Returns: string representing a file path + ''' + def scratch_file(self, *args): + return str(Path(self.workdir, *args)) + + ''' + @params args list of zero or more subdirectories or file + + Construct a path for accessing/creating a log file + located relative to a temporary directory dedicated to + this test case. The directory and its log files will be + preserved upon completion of the test. + + @args may be an empty list to reference the log dir + itself, may be a single element to reference a file in + the log directory, or may be multiple elements to + reference a file nested below. The path components + will be joined using the platform appropriate path + separator. + + Returns: string representing a file path + ''' + def log_file(self, *args): + return str(Path(self.logdir, *args)) + def setUp(self, bin_prefix): self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set') self.arch = self.qemu_bin.split('-')[-1] + self.socketdir = None self.outputdir = os.path.join(BUILD_DIR, 'tests', 'functional', self.arch, self.id()) @@ -65,6 +157,9 @@ class QemuBaseTest(unittest.TestCase): def tearDown(self): if "QEMU_TEST_KEEP_SCRATCH" not in os.environ: shutil.rmtree(self.workdir) + if self.socketdir is not None: + shutil.rmtree(self.socketdir.name) + self.socketdir = None self.machinelog.removeHandler(self._log_fh) self.log.removeHandler(self._log_fh) -- cgit 1.4.1 From bcc12768c242a4862a1725e22371dd87af1c2b7e Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:32 +0000 Subject: tests/functional: switch over to using self.log_file(...) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes direct access of the 'self.logdir' variable. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-12-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 9 ++++----- tests/functional/test_virtio_gpu.py | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 89425b737c..2174fbb155 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -126,7 +126,7 @@ class QemuBaseTest(unittest.TestCase): Returns: string representing a file path ''' def log_file(self, *args): - return str(Path(self.logdir, *args)) + return str(Path(self.outputdir, *args)) def setUp(self, bin_prefix): self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set') @@ -138,8 +138,7 @@ class QemuBaseTest(unittest.TestCase): self.workdir = os.path.join(self.outputdir, 'scratch') os.makedirs(self.workdir, exist_ok=True) - self.logdir = self.outputdir - self.log_filename = os.path.join(self.logdir, 'base.log') + self.log_filename = self.log_file('base.log') self.log = logging.getLogger('qemu-test') self.log.setLevel(logging.DEBUG) self._log_fh = logging.FileHandler(self.log_filename, mode='w') @@ -215,7 +214,7 @@ class QemuSystemTest(QemuBaseTest): console_log = logging.getLogger('console') console_log.setLevel(logging.DEBUG) - self.console_log_name = os.path.join(self.logdir, 'console.log') + self.console_log_name = self.log_file('console.log') self._console_log_fh = logging.FileHandler(self.console_log_name, mode='w') self._console_log_fh.setLevel(logging.DEBUG) @@ -269,7 +268,7 @@ class QemuSystemTest(QemuBaseTest): vm = QEMUMachine(self.qemu_bin, name=name, base_temp_dir=self.workdir, - log_dir=self.logdir) + log_dir=self.log_file()) self.log.debug('QEMUMachine "%s" created', name) self.log.debug('QEMUMachine "%s" temp_dir: %s', name, vm.temp_dir) diff --git a/tests/functional/test_virtio_gpu.py b/tests/functional/test_virtio_gpu.py index 2d298b1f02..7654421e6b 100755 --- a/tests/functional/test_virtio_gpu.py +++ b/tests/functional/test_virtio_gpu.py @@ -100,9 +100,7 @@ class VirtioGPUx86(QemuSystemTest): os.set_inheritable(qemu_sock.fileno(), True) os.set_inheritable(vug_sock.fileno(), True) - self._vug_log_path = os.path.join( - self.logdir, "vhost-user-gpu.log" - ) + self._vug_log_path = self.log_file("vhost-user-gpu.log") self._vug_log_file = open(self._vug_log_path, "wb") self.log.info('Complete vhost-user-gpu.log file can be ' 'found at %s', self._vug_log_path) -- cgit 1.4.1 From 8b5a0dd3a8a4526bb91430b7f548c95d46093dc1 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:33 +0000 Subject: tests/functional: switch over to using self.build_file(...) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes direct access of the 'BUILD_DIR' variable. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-13-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/cmd.py | 5 ++--- tests/functional/qemu_test/testcase.py | 4 ++-- tests/functional/test_aarch64_virt.py | 5 ++--- tests/functional/test_virtio_gpu.py | 11 +++-------- 4 files changed, 9 insertions(+), 16 deletions(-) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/cmd.py b/tests/functional/qemu_test/cmd.py index bebcd46dcf..c8971de00a 100644 --- a/tests/functional/qemu_test/cmd.py +++ b/tests/functional/qemu_test/cmd.py @@ -16,7 +16,6 @@ import os import os.path import subprocess -from .config import BUILD_DIR def which(tool): """ looks up the full path for @tool, returns None if not found @@ -205,10 +204,10 @@ def get_qemu_img(test): # If qemu-img has been built, use it, otherwise the system wide one # will be used. - qemu_img = os.path.join(BUILD_DIR, 'qemu-img') + qemu_img = test.build_file('qemu-img') if os.path.exists(qemu_img): return qemu_img qemu_img = which('qemu-img') if qemu_img is not None: return qemu_img - test.skipTest(f"qemu-img not found in {BUILD_DIR} or '$PATH'") + test.skipTest(f"qemu-img not found in build dir or '$PATH'") diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 2174fbb155..493938240c 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -133,8 +133,8 @@ class QemuBaseTest(unittest.TestCase): self.arch = self.qemu_bin.split('-')[-1] self.socketdir = None - self.outputdir = os.path.join(BUILD_DIR, 'tests', 'functional', - self.arch, self.id()) + self.outputdir = self.build_file('tests', 'functional', + self.arch, self.id()) self.workdir = os.path.join(self.outputdir, 'scratch') os.makedirs(self.workdir, exist_ok=True) diff --git a/tests/functional/test_aarch64_virt.py b/tests/functional/test_aarch64_virt.py index c967da41b4..5bc461b482 100755 --- a/tests/functional/test_aarch64_virt.py +++ b/tests/functional/test_aarch64_virt.py @@ -14,7 +14,6 @@ import time import os import logging -from qemu_test import BUILD_DIR from qemu_test import QemuSystemTest, Asset from qemu_test import exec_command, wait_for_console_pattern from qemu_test import get_qemu_img, run_cmd @@ -54,8 +53,8 @@ class Aarch64VirtMachine(QemuSystemTest): "mte=on," "gic-version=max,iommu=smmuv3") self.vm.add_args("-smp", "2", "-m", "1024") - self.vm.add_args('-bios', os.path.join(BUILD_DIR, 'pc-bios', - 'edk2-aarch64-code.fd')) + self.vm.add_args('-bios', self.build_file('pc-bios', + 'edk2-aarch64-code.fd')) self.vm.add_args("-drive", f"file={iso_path},media=cdrom,format=raw") self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') self.vm.add_args('-object', 'rng-random,id=rng0,filename=/dev/urandom') diff --git a/tests/functional/test_virtio_gpu.py b/tests/functional/test_virtio_gpu.py index 7654421e6b..81c9156d63 100755 --- a/tests/functional/test_virtio_gpu.py +++ b/tests/functional/test_virtio_gpu.py @@ -6,7 +6,6 @@ # later. See the COPYING file in the top-level directory. -from qemu_test import BUILD_DIR from qemu_test import QemuSystemTest, Asset from qemu_test import wait_for_console_pattern from qemu_test import exec_command_and_wait_for_pattern @@ -18,12 +17,8 @@ import socket import subprocess -def pick_default_vug_bin(): - relative_path = "./contrib/vhost-user-gpu/vhost-user-gpu" - if is_readable_executable_file(relative_path): - return relative_path - - bld_dir_path = os.path.join(BUILD_DIR, relative_path) +def pick_default_vug_bin(test): + bld_dir_path = test.build_file("contrib", "vhost-user-gpu", "vhost-user-gpu") if is_readable_executable_file(bld_dir_path): return bld_dir_path @@ -86,7 +81,7 @@ class VirtioGPUx86(QemuSystemTest): # FIXME: should check presence of vhost-user-gpu, virgl, memfd etc self.require_accelerator('kvm') - vug = pick_default_vug_bin() + vug = pick_default_vug_bin(self) if not vug: self.skipTest("Could not find vhost-user-gpu") -- cgit 1.4.1 From 239fd29d6f82724c95a7a7f840c9f9244bcbe6d4 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:43 +0000 Subject: tests/functional: add 'archive_extract' to QemuBaseTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helper wrappers archive.archive_extract, forcing the use of the scratch directory, to ensure any extracted files are cleaned at test termination. If a specific member is requested, then the path to the extracted file is also returned. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-23-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 493938240c..19fb1d0c07 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -25,6 +25,7 @@ import uuid from qemu.machine import QEMUMachine from qemu.utils import kvm_available, tcg_available +from .archive import archive_extract from .asset import Asset from .cmd import run_cmd from .config import BUILD_DIR @@ -39,6 +40,37 @@ class QemuBaseTest(unittest.TestCase): log = None logdir = None + ''' + @params archive: filename, Asset, or file-like object to extract + @params format: optional archive format (tar, zip, deb, cpio) + @params sub_dir: optional sub-directory to extract into + @params member: optional member file to limit extraction to + + Extracts @archive into the scratch directory, or a directory beneath + named by @sub_dir. All files are extracted unless @member specifies + a limit. + + If @format is None, heuristics will be applied to guess the format + from the filename or Asset URL. @format must be non-None if @archive + is a file-like object. + + If @member is non-None, returns the fully qualified path to @member + ''' + def archive_extract(self, archive, format=None, sub_dir=None, member=None): + self.log.debug(f"Extract {archive} format={format}" + + f"sub_dir={sub_dir} member={member}") + if type(archive) == Asset: + archive.fetch() + if sub_dir is None: + archive_extract(archive, self.scratch_file(), format, member) + else: + archive_extract(archive, self.scratch_file(sub_dir), + format, member) + + if member is not None: + return self.scratch_file(member) + return None + ''' Create a temporary directory suitable for storing UNIX socket paths. -- cgit 1.4.1 From fd4abcb00839a310fc668d86177278e789f51688 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:46 +0000 Subject: tests/functional: add 'uncompress' to QemuBaseTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helper wrappers utils.uncompress, forcing the use of the scratch directory, to ensure any uncompressed files are cleaned at test termination. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-26-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 19fb1d0c07..d0bb3141d5 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -29,6 +29,7 @@ from .archive import archive_extract from .asset import Asset from .cmd import run_cmd from .config import BUILD_DIR +from .uncompress import uncompress class QemuBaseTest(unittest.TestCase): @@ -40,6 +41,30 @@ class QemuBaseTest(unittest.TestCase): log = None logdir = None + ''' + @params compressed: filename, Asset, or file-like object to uncompress + @params format: optional compression format (gzip, lzma) + + Uncompresses @compressed into the scratch directory. + + If @format is None, heuristics will be applied to guess the format + from the filename or Asset URL. @format must be non-None if @uncompressed + is a file-like object. + + Returns the fully qualified path to the uncompressed file + ''' + def uncompress(self, compressed, format=None): + self.log.debug(f"Uncompress {compressed} format={format}") + if type(compressed) == Asset: + compressed.fetch() + + (name, ext) = os.path.splitext(str(compressed)) + uncompressed = self.scratch_file(os.path.basename(name)) + + uncompress(compressed, uncompressed, format) + + return uncompressed + ''' @params archive: filename, Asset, or file-like object to extract @params format: optional archive format (tar, zip, deb, cpio) -- cgit 1.4.1 From 37e9b19c34d9500164e33ccf377e1830e956bca0 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:49 +0000 Subject: tests/functional: replace 'run_cmd' with subprocess helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'run_cmd' helper is re-implementing a convenient helper that already exists in the form of the 'run' and 'check_call' methods provided by 'subprocess'. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-29-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/archive.py | 9 +++++---- tests/functional/qemu_test/tesseract.py | 10 +++++----- tests/functional/qemu_test/testcase.py | 31 +++++++++++++++++-------------- tests/functional/qemu_test/tuxruntest.py | 8 +++++--- tests/functional/test_aarch64_virt.py | 6 ++++-- tests/functional/test_ppc64_tuxrun.py | 7 +++++-- 6 files changed, 41 insertions(+), 30 deletions(-) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/archive.py b/tests/functional/qemu_test/archive.py index c439d9413a..c803fdaf6d 100644 --- a/tests/functional/qemu_test/archive.py +++ b/tests/functional/qemu_test/archive.py @@ -14,7 +14,6 @@ from urllib.parse import urlparse import zipfile from .asset import Asset -from .cmd import run_cmd def tar_extract(archive, dest_dir, member=None): @@ -52,9 +51,11 @@ def deb_extract(archive, dest_dir, member=None): cwd = os.getcwd() os.chdir(dest_dir) try: - (stdout, stderr, ret) = run_cmd(['ar', 't', archive]) - file_path = stdout.split()[2] - run_cmd(['ar', 'x', archive, file_path]) + proc = run(['ar', 't', archive], + check=True, capture_output=True, encoding='utf8') + file_path = proc.stdout.split()[2] + check_call(['ar', 'x', archive, file_path], + stdout=DEVNULL, stderr=DEVNULL) tar_extract(file_path, dest_dir, member) finally: os.chdir(cwd) diff --git a/tests/functional/qemu_test/tesseract.py b/tests/functional/qemu_test/tesseract.py index 1b7818090a..ede6c6501e 100644 --- a/tests/functional/qemu_test/tesseract.py +++ b/tests/functional/qemu_test/tesseract.py @@ -6,18 +6,18 @@ # later. See the COPYING file in the top-level directory. import logging +from subprocess import run -from . import run_cmd def tesseract_ocr(image_path, tesseract_args=''): console_logger = logging.getLogger('console') console_logger.debug(image_path) - (stdout, stderr, ret) = run_cmd(['tesseract', image_path, - 'stdout']) - if ret: + proc = run(['tesseract', image_path, 'stdout'], + capture_output=True, encoding='utf8') + if proc.returncode: return None lines = [] - for line in stdout.split('\n'): + for line in proc.stdout.split('\n'): sline = line.strip() if len(sline): console_logger.debug(sline) diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index d0bb3141d5..aa6c9c0d64 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -16,7 +16,7 @@ import os from pathlib import Path import pycotap import shutil -import subprocess +from subprocess import run import sys import tempfile import unittest @@ -27,7 +27,6 @@ from qemu.utils import kvm_available, tcg_available from .archive import archive_extract from .asset import Asset -from .cmd import run_cmd from .config import BUILD_DIR from .uncompress import uncompress @@ -251,11 +250,11 @@ class QemuUserTest(QemuBaseTest): self._ldpath.append(os.path.abspath(ldpath)) def run_cmd(self, bin_path, args=[]): - return subprocess.run([self.qemu_bin] - + ["-L %s" % ldpath for ldpath in self._ldpath] - + [bin_path] - + args, - text=True, capture_output=True) + return run([self.qemu_bin] + + ["-L %s" % ldpath for ldpath in self._ldpath] + + [bin_path] + + args, + text=True, capture_output=True) class QemuSystemTest(QemuBaseTest): """Facilitates system emulation tests.""" @@ -282,7 +281,9 @@ class QemuSystemTest(QemuBaseTest): def set_machine(self, machinename): # TODO: We should use QMP to get the list of available machines if not self._machinehelp: - self._machinehelp = run_cmd([self.qemu_bin, '-M', 'help'])[0]; + self._machinehelp = run( + [self.qemu_bin, '-M', 'help'], + capture_output=True, check=True, encoding='utf8').stdout if self._machinehelp.find(machinename) < 0: self.skipTest('no support for machine ' + machinename) self.machine = machinename @@ -310,15 +311,17 @@ class QemuSystemTest(QemuBaseTest): "available" % accelerator) def require_netdev(self, netdevname): - netdevhelp = run_cmd([self.qemu_bin, - '-M', 'none', '-netdev', 'help'])[0]; - if netdevhelp.find('\n' + netdevname + '\n') < 0: + help = run([self.qemu_bin, + '-M', 'none', '-netdev', 'help'], + capture_output=True, check=True, encoding='utf8').stdout; + if help.find('\n' + netdevname + '\n') < 0: self.skipTest('no support for " + netdevname + " networking') def require_device(self, devicename): - devhelp = run_cmd([self.qemu_bin, - '-M', 'none', '-device', 'help'])[0]; - if devhelp.find(devicename) < 0: + help = run([self.qemu_bin, + '-M', 'none', '-device', 'help'], + capture_output=True, check=True, encoding='utf8').stdout; + if help.find(devicename) < 0: self.skipTest('no support for device ' + devicename) def _new_vm(self, name, *args): diff --git a/tests/functional/qemu_test/tuxruntest.py b/tests/functional/qemu_test/tuxruntest.py index 0b1bb8f0ed..7227a83757 100644 --- a/tests/functional/qemu_test/tuxruntest.py +++ b/tests/functional/qemu_test/tuxruntest.py @@ -11,11 +11,12 @@ import os import stat +from subprocess import check_call, DEVNULL from qemu_test import QemuSystemTest from qemu_test import exec_command_and_wait_for_pattern from qemu_test import wait_for_console_pattern -from qemu_test import which, run_cmd, get_qemu_img +from qemu_test import which, get_qemu_img class TuxRunBaselineTest(QemuSystemTest): @@ -76,8 +77,9 @@ class TuxRunBaselineTest(QemuSystemTest): disk_image = self.scratch_file("rootfs.ext4") - run_cmd(['zstd', "-f", "-d", disk_image_zst, - "-o", disk_image]) + check_call(['zstd', "-f", "-d", disk_image_zst, + "-o", disk_image], + stdout=DEVNULL, stderr=DEVNULL) # zstd copies source archive permissions for the output # file, so must make this writable for QEMU os.chmod(disk_image, stat.S_IRUSR | stat.S_IWUSR) diff --git a/tests/functional/test_aarch64_virt.py b/tests/functional/test_aarch64_virt.py index cc49f8963d..08576b0694 100755 --- a/tests/functional/test_aarch64_virt.py +++ b/tests/functional/test_aarch64_virt.py @@ -12,10 +12,11 @@ import time import logging +from subprocess import check_call, DEVNULL from qemu_test import QemuSystemTest, Asset from qemu_test import exec_command, wait_for_console_pattern -from qemu_test import get_qemu_img, run_cmd +from qemu_test import get_qemu_img class Aarch64VirtMachine(QemuSystemTest): @@ -96,7 +97,8 @@ class Aarch64VirtMachine(QemuSystemTest): logger.info('creating scratch qcow2 image') image_path = self.scratch_file('scratch.qcow2') qemu_img = get_qemu_img(self) - run_cmd([qemu_img, 'create', '-f', 'qcow2', image_path, '8M']) + check_call([qemu_img, 'create', '-f', 'qcow2', image_path, '8M'], + stdout=DEVNULL, stderr=DEVNULL) # Add the device self.vm.add_args('-blockdev', diff --git a/tests/functional/test_ppc64_tuxrun.py b/tests/functional/test_ppc64_tuxrun.py index 03b47e07f2..8a98d18ab3 100755 --- a/tests/functional/test_ppc64_tuxrun.py +++ b/tests/functional/test_ppc64_tuxrun.py @@ -11,9 +11,10 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +from subprocess import check_call, DEVNULL import tempfile -from qemu_test import run_cmd, Asset +from qemu_test import Asset from qemu_test.tuxruntest import TuxRunBaselineTest class TuxRunPPC64Test(TuxRunBaselineTest): @@ -70,7 +71,9 @@ class TuxRunPPC64Test(TuxRunBaselineTest): # Create a temporary qcow2 and launch the test-case with tempfile.NamedTemporaryFile(prefix=prefix, suffix='.qcow2') as qcow2: - run_cmd([self.qemu_img, 'create', '-f', 'qcow2', qcow2.name, ' 1G']) + check_call([self.qemu_img, 'create', '-f', 'qcow2', + qcow2.name, ' 1G'], + stdout=DEVNULL, stderr=DEVNULL) self.vm.add_args('-drive', 'file=' + qcow2.name + ',format=qcow2,if=none,id=' -- cgit 1.4.1 From 6ff217c2d1c231f727c1be356da4a71cdfdd7ec5 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrangé" Date: Tue, 17 Dec 2024 15:59:51 +0000 Subject: tests/functional: skip tests if assets are not available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If downloading of assets has been disabled, then skip running a test if the assets it has registered are not already downloaded. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20241217155953.3950506-31-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/asset.py | 8 +++++++- tests/functional/qemu_test/testcase.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'tests/functional/qemu_test/testcase.py') diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py index c5d3e73c4b..39832b2587 100644 --- a/tests/functional/qemu_test/asset.py +++ b/tests/functional/qemu_test/asset.py @@ -65,6 +65,12 @@ class Asset: def valid(self): return self.cache_file.exists() and self._check(self.cache_file) + def fetchable(self): + return not os.environ.get("QEMU_TEST_NO_DOWNLOAD", False) + + def available(self): + return self.valid() or self.fetchable() + def _wait_for_other_download(self, tmp_cache_file): # Another thread already seems to download the asset, so wait until # it is done, while also checking the size to see whether it is stuck @@ -103,7 +109,7 @@ class Asset: self.cache_file, self.url) return str(self.cache_file) - if os.environ.get("QEMU_TEST_NO_DOWNLOAD", False): + if not self.fetchable(): raise Exception("Asset cache is invalid and downloads disabled") self.log.info("Downloading %s to %s...", self.url, self.cache_file) diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index aa6c9c0d64..869f3949fe 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -184,6 +184,14 @@ class QemuBaseTest(unittest.TestCase): def log_file(self, *args): return str(Path(self.outputdir, *args)) + def assets_available(self): + for name, asset in vars(self.__class__).items(): + if name.startswith("ASSET_") and type(asset) == Asset: + if not asset.available(): + self.log.debug(f"Asset {asset.url} not available") + return False + return True + def setUp(self, bin_prefix): self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set') self.arch = self.qemu_bin.split('-')[-1] @@ -209,6 +217,9 @@ class QemuBaseTest(unittest.TestCase): self.machinelog.setLevel(logging.DEBUG) self.machinelog.addHandler(self._log_fh) + if not self.assets_available(): + self.skipTest('One or more assets is not available') + def tearDown(self): if "QEMU_TEST_KEEP_SCRATCH" not in os.environ: shutil.rmtree(self.workdir) -- cgit 1.4.1