On Thu, Jul 14, 2022 at 19:49 Ani Sinha <a...@anisinha.ca> wrote: > > > On Thu, 14 Jul 2022, Daniel P. Berrangé wrote: > > > On Sun, Jul 10, 2022 at 10:30:10PM +0530, Ani Sinha wrote: > > > This change adds python based test environment that can be used to run > pytest > > > from within a virtual environment. A bash script sets up a virtual > environment > > > and then runs the python based tests from within that environment. > > > All dependent python packages are installed in the virtual environment > using > > > pip python module. QEMU python test modules are also available in the > environment > > > for spawning the QEMU based VMs. > > > > > > It also introduces QEMU acpi/smbios biosbits python test script which > is run > > > from within the python virtual environment. When the bios bits tests > are run, > > > bios bits binaries are downloaded from an external repo/location. > > > Currently, the test points to an external private github repo where > the bits > > > archives are checked in. > > > > > > Signed-off-by: Ani Sinha <a...@anisinha.ca> > > > --- > > > tests/pytest/acpi-bits/acpi-bits-test-venv.sh | 59 +++ > > > tests/pytest/acpi-bits/acpi-bits-test.py | 382 ++++++++++++++++++ > > > tests/pytest/acpi-bits/meson.build | 33 ++ > > > tests/pytest/acpi-bits/requirements.txt | 1 + > > > 4 files changed, 475 insertions(+) > > > create mode 100644 tests/pytest/acpi-bits/acpi-bits-test-venv.sh > > > create mode 100644 tests/pytest/acpi-bits/acpi-bits-test.py > > > create mode 100644 tests/pytest/acpi-bits/meson.build > > > create mode 100644 tests/pytest/acpi-bits/requirements.txt > > > > > > diff --git a/tests/pytest/acpi-bits/acpi-bits-test-venv.sh > b/tests/pytest/acpi-bits/acpi-bits-test-venv.sh > > > new file mode 100644 > > > index 0000000000..186395473b > > > --- /dev/null > > > +++ b/tests/pytest/acpi-bits/acpi-bits-test-venv.sh > > > @@ -0,0 +1,59 @@ > > > +#!/usr/bin/env bash > > > +# Generates a python virtual environment for the test to run. > > > +# Then runs python test scripts from within that virtual environment. > > > +# > > > +# This program is free software; you can redistribute it and/or modify > > > +# it under the terms of the GNU General Public License as published by > > > +# the Free Software Foundation; either version 2 of the License, or > > > +# (at your option) any later version. > > > +# > > > +# This program is distributed in the hope that it will be useful, > > > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > > +# GNU General Public License for more details. > > > +# > > > +# You should have received a copy of the GNU General Public License > > > +# along with this program. If not, see <http://www.gnu.org/licenses/ > >. > > > +# > > > +# Author: Ani Sinha <a...@anisinha.ca> > > > + > > > +set -e > > > + > > > +MYPATH=$(realpath ${BASH_SOURCE:-$0}) > > > +MYDIR=$(dirname $MYPATH) > > > + > > > +if [ -z "$PYTEST_SOURCE_ROOT" ]; then > > > + echo -n "Please set QTEST_SOURCE_ROOT env pointing" > > > + echo " to the root of the qemu source tree." > > > + echo -n "This is required so that the test can find the " > > > + echo "python modules that it needs for execution." > > > + exit 1 > > > +fi > > > +SRCDIR=$PYTEST_SOURCE_ROOT > > > +TESTSCRIPTS=("acpi-bits-test.py") > > > +PIPCMD="-m pip -q --disable-pip-version-check" > > > +# we need to save the old value of PWD before we do a change-dir later > > > +PYTEST_PWD=$PWD > > > + > > > +TESTS_PYTHON=/usr/bin/python3 > > > +TESTS_VENV_REQ=requirements.txt > > > + > > > +# sadly for pip -e and -t options do not work together. > > > +# please see https://github.com/pypa/pip/issues/562 > > > +cd $MYDIR > > > + > > > +$TESTS_PYTHON -m venv . > > > +$TESTS_PYTHON $PIPCMD install -e $SRCDIR/python/ > > > +[ -f $TESTS_VENV_REQ ] && \ > > > + $TESTS_PYTHON $PIPCMD install -r $TESTS_VENV_REQ || exit 0 > > > + > > > +# venv is activated at this point. > > > + > > > +# run the test > > > +for testscript in ${TESTSCRIPTS[@]} ; do > > > + export PYTEST_PWD; python3 $testscript > > > +done > > > + > > > +cd $PYTEST_PWD > > > + > > > +exit 0 > > > diff --git a/tests/pytest/acpi-bits/acpi-bits-test.py > b/tests/pytest/acpi-bits/acpi-bits-test.py > > > new file mode 100644 > > > index 0000000000..97e61eb709 > > > --- /dev/null > > > +++ b/tests/pytest/acpi-bits/acpi-bits-test.py > > > @@ -0,0 +1,382 @@ > > > +#!/usr/bin/env python3 > > > +# group: rw quick > > > +# Exercize QEMU generated ACPI/SMBIOS tables using biosbits, > > > +# https://biosbits.org/ > > > +# > > > +# This program is free software; you can redistribute it and/or modify > > > +# it under the terms of the GNU General Public License as published by > > > +# the Free Software Foundation; either version 2 of the License, or > > > +# (at your option) any later version. > > > +# > > > +# This program is distributed in the hope that it will be useful, > > > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > > +# GNU General Public License for more details. > > > +# > > > +# You should have received a copy of the GNU General Public License > > > +# along with this program. If not, see <http://www.gnu.org/licenses/ > >. > > > +# > > > +# Some parts are slightly taken from qtest.py and iotests.py > > > +# > > > +# Authors: > > > +# Ani Sinha <a...@anisinha.ca> > > > + > > > +# pylint: disable=invalid-name > > > + > > > +""" > > > +QEMU bios tests using biosbits available at > > > +https://biosbits.org/. > > > +""" > > > + > > > +import logging > > > +import os > > > +import re > > > +import shutil > > > +import subprocess > > > +import sys > > > +import tarfile > > > +import tempfile > > > +import time > > > +import unittest > > > +from urllib import request > > > +import zipfile > > > +from typing import ( > > > + List, > > > + Optional, > > > + Sequence, > > > +) > > > +from tap import TAPTestRunner > > > +from qemu.machine import QEMUMachine > > > + > > > +PYTESTQEMUBIN = os.getenv('PYTEST_QEMU_BINARY') > > > +PYTEST_PWD = os.getenv('PYTEST_PWD') > > > + > > > +def get_arch(): > > > + """finds the arch from the qemu binary name""" > > > + match = re.search('.*qemu-system-(.*)', PYTESTQEMUBIN) > > > + if match: > > > + return match.group(1) > > > + return 'x86_64' > > > + > > > +ARCH = get_arch() > > > + > > > +class QEMUBitsMachine(QEMUMachine): > > > + """ > > > + A QEMU VM, with isa-debugcon enabled and bits iso passed > > > + using -cdrom to QEMU commandline. > > > + """ > > > + def __init__(self, > > > + binary: str, > > > + args: Sequence[str] = (), > > > + wrapper: Sequence[str] = (), > > > + name: Optional[str] = None, > > > + base_temp_dir: str = "/var/tmp", > > > + debugcon_log: str = "debugcon-log.txt", > > > + debugcon_addr: str = "0x403", > > > + sock_dir: Optional[str] = None, > > > + qmp_timer: Optional[float] = None): > > > + # pylint: disable=too-many-arguments > > > + > > > + if name is None: > > > + name = "qemu-bits-%d" % os.getpid() > > > + if sock_dir is None: > > > + sock_dir = base_temp_dir > > > + super().__init__(binary, args, wrapper=wrapper, name=name, > > > + base_temp_dir=base_temp_dir, > > > + sock_dir=sock_dir, qmp_timer=qmp_timer) > > > + self.debugcon_log = debugcon_log > > > + self.debugcon_addr = debugcon_addr > > > + self.base_temp_dir = base_temp_dir > > > + > > > + @property > > > + def _base_args(self) -> List[str]: > > > + args = super()._base_args > > > + args.extend([ > > > + '-chardev', > > > + 'file,path=%s,id=debugcon' > %os.path.join(self.base_temp_dir, > > > + > self.debugcon_log), > > > + '-device', > > > + 'isa-debugcon,iobase=%s,chardev=debugcon' > %self.debugcon_addr, > > > + ]) > > > + return args > > > + > > > + def base_args(self): > > > + """return the base argument to QEMU binary""" > > > + return self._base_args > > > + > > > +class AcpiBitsTest(unittest.TestCase): > > > + """ACPI and SMBIOS tests using biosbits.""" > > > + def __init__(self, *args, **kwargs): > > > + super().__init__(*args, **kwargs) > > > + self._vm = None > > > + self._workDir = None > > > + self._bitsVer = 2100 > > > + self._bitsLoc = " > https://github.com/ani-sinha/bits/raw/bits-builds/" > > > > This URL location gives a 404 - was it supposed to be poiinting to the > > bits-builds branch ? eg to form a URL like: > > > > You are looking at a partial URL. The code completes the URL based on the > version we are using. Append to this URL the archive name, like > bits-2100.zip or something. > > eg, https://github.com/ani-sinha/bits/raw/bits-builds/bits-2100.zip > > > > https://github.com/ani-sinha/bits/blob/bits-builds/bits-2100.zip?raw=true > > > > > + self._debugcon_addr = '0x403' > > > + self._debugcon_log = 'debugcon-log.txt' > > > + logging.basicConfig(level=logging.INFO) > > > + > > > + def copy_bits_config(self): > > > + """ copies the bios bits config file into bits. > > > + """ > > > + config_file = 'bits-cfg.txt' > > > + qemu_bits_config_dir = os.path.join(os.getcwd(), > 'bits-config') > > > + target_config_dir = os.path.join(self._workDir, > > > + 'bits-%d' %self._bitsVer, > 'boot') > > > + self.assertTrue(os.path.exists(qemu_bits_config_dir)) > > > + self.assertTrue(os.path.exists(target_config_dir)) > > > + self.assertTrue(os.access(os.path.join(qemu_bits_config_dir, > > > + config_file), os.R_OK)) > > > + shutil.copy2(os.path.join(qemu_bits_config_dir, config_file), > > > + target_config_dir) > > > + logging.info('copied config file %s to %s', > > > + config_file, target_config_dir) > > > + > > > + def copy_test_scripts(self): > > > + """copies the python test scripts into bits. """ > > > + qemu_test_dir = os.path.join(os.getcwd(), 'bits-tests') > > > + target_test_dir = os.path.join(self._workDir, 'bits-%d' > %self._bitsVer, > > > + 'boot', 'python') > > > + > > > + self.assertTrue(os.path.exists(qemu_test_dir)) > > > + self.assertTrue(os.path.exists(target_test_dir)) > > > + > > > + for filename in os.listdir(qemu_test_dir): > > > + if os.path.isfile(os.path.join(qemu_test_dir, filename)) > and \ > > > + filename.endswith('.py'): > > > + shutil.copy2(os.path.join(qemu_test_dir, filename), > > > + target_test_dir) > > > + logging.info('copied test file %s to %s', > > > + filename, target_test_dir) > > > + > > > + # now remove the pyc test file if it exists, > otherwise the > > > + # changes in the python test script won't be executed. > > > + testfile_pyc = os.path.splitext(filename)[0] + '.pyc' > > > + if os.access(os.path.join(target_test_dir, > testfile_pyc), > > > + os.F_OK): > > > + os.remove(os.path.join(target_test_dir, > testfile_pyc)) > > > + logging.info('removed compiled file %s', > > > + os.path.join(target_test_dir, > testfile_pyc)) > > > + > > > + def fix_mkrescue(self, mkrescue): > > > + """ grub-mkrescue is a bash script with two variables, > 'prefix' and > > > + 'libdir'. They must be pointed to the right location so > that the > > > + iso can be generated appropriately. We point the two > variables to > > > + the directory where we have extracted our pre-built bits > grub > > > + tarball. > > > + """ > > > + grub_x86_64_mods = os.path.join(self._workDir, > 'grub-inst-x86_64-efi') > > > + grub_i386_mods = os.path.join(self._workDir, 'grub-inst') > > > + > > > + self.assertTrue(os.path.exists(grub_x86_64_mods)) > > > + self.assertTrue(os.path.exists(grub_i386_mods)) > > > + > > > + new_script = "" > > > + with open(mkrescue, 'r') as filehandle: > > > + orig_script = filehandle.read() > > > + new_script = re.sub('(^prefix=)(.*)', > > > + r'\1"%s"' %grub_x86_64_mods, > > > + orig_script, flags=re.M) > > > + new_script = re.sub('(^libdir=)(.*)', r'\1"%s/lib"' > %grub_i386_mods, > > > + new_script, flags=re.M) > > > + > > > + with open(mkrescue, 'w') as filehandle: > > > + filehandle.write(new_script) > > > + > > > + def generate_bits_iso(self): > > > + """ Uses grub-mkrescue to generate a fresh bits iso with the > python > > > + test scripts > > > + """ > > > + bits_dir = os.path.join(self._workDir, 'bits-%d' > %self._bitsVer) > > > + iso_file = os.path.join(self._workDir, 'bits-%d.iso' > %self._bitsVer) > > > + mkrescue_script = os.path.join(self._workDir, > > > + 'grub-inst-x86_64-efi', 'bin', > > > + 'grub-mkrescue') > > > + > > > + self.assertTrue(os.access(mkrescue_script, > > > + os.R_OK | os.W_OK | os.X_OK)) > > > + > > > + self.fix_mkrescue(mkrescue_script) > > > + > > > + logging.info('calling grub-mkrescue to generate the biosbits > iso ...') > > > + > > > + try: > > > + if os.getenv('V'): > > > + subprocess.check_call([mkrescue_script, '-o', > > > + iso_file, bits_dir], > > > + stdout=subprocess.DEVNULL) > > > + else: > > > + subprocess.check_call([mkrescue_script, '-o', > > > + iso_file, bits_dir], > > > + stderr=subprocess.DEVNULL, > > > + stdout=subprocess.DEVNULL) > > > + except Exception as e: # pylint: disable=broad-except > > > + self.skipTest("Error while generating the bits iso. " > > > + "Pass V=1 in the environment to get more > details. " > > > + + str(e)) > > > + > > > + self.assertTrue(os.access(iso_file, os.R_OK)) > > > + > > > + logging.info('iso file %s successfully generated.', iso_file) > > > + > > > + def setUp(self): > > > + BITS_LOC = os.getenv("PYTEST_BITSLOC") > > > + if BITS_LOC: > > > + prefix = BITS_LOC > > > + else: > > > + prefix = os.path.join(os.getcwd(), 'prebuilt') > > > + if not os.path.isdir(prefix): > > > + os.mkdir(prefix, mode=0o775) > > > + > > > + bits_zip_file = os.path.join(prefix, 'bits-%d.zip' > > > + %self._bitsVer) > > > + grub_tar_file = os.path.join(prefix, > > > + 'bits-%d-grub.tar.gz' > %self._bitsVer) > > > + # if the location of the bits binaries has been specified by > the user > > > + # and they are not found in that location, skip the test. > > > + if BITS_LOC and not os.access(bits_zip_file, os.F_OK): > > > + self.skipTest("test skipped since biosbits binaries " + > > > + "could not be found in the specified > location %s." \ > > > + %BITS_LOC) > > > + if BITS_LOC and not os.access(grub_tar_file, os.F_OK): > > > + self.skipTest("test skipped since biosbits binaries " + > > > + "could not be found in the specified > location %s." \ > > > + %BITS_LOC) > > > + > > > + self._workDir = tempfile.mkdtemp(prefix='acpi-bits-', > > > + suffix='.tmp') > > > + logging.info('working dir: %s', self._workDir) > > > + > > > + localArchive = "bits-%d.zip" % self._bitsVer > > > + if not os.access(bits_zip_file, os.F_OK): > > > + logging.info("archive %s not found in %s, downloading > ...", > > > + localArchive, bits_zip_file) > > > + try: > > > + req = request.urlopen(self._bitsLoc + localArchive) > > > + with open(os.path.join(prefix, localArchive), > > > + 'wb') as archivef: > > > + archivef.write(req.read()) > > > + except Exception as e: # pylint: disable=broad-except > > > + self.skipTest("test skipped since biosbits binaries " > + > > > + "could not be obtained." + str(e)) > > > + else: > > > + logging.info('using locally found %s', localArchive) > > > > so you skip downlaod if it already exists locally. IIUC it is looking > > in the CWD, which is presumably the directory the QEMU build is > > performed in ? > > Yes, build/test/pytest/bits-test > > > So if dev cleans their build tree, the cache is lost ? > > Yes. > > > > > Avocado has a more persistent cache outside the build tree IIUC.
To be honest I’m not sure if I like that. If I clear out my build directory I would want all build related artifacts to go away, including downloaded build stuff from the cache. > > > > So that is why I have the environment variable mechanism for passing to > the test that will point to the location of the archives. The developer > can download the files there and point to the test. Or I can change the > scriprt accordingly if we know where we were downloading. We were > discussing submodules and it was fiercely hated. So mst suggested another > repo where to keep the binaries. My script that check out that repo > somewhere outside the build directory and point the test to that location. > > I have kept several options open. We just need to make some decisions. > > > > With regards, > > Daniel > > -- > > |: https://berrange.com -o- > https://www.flickr.com/photos/dberrange :| > > |: https://libvirt.org -o- > https://fstop138.berrange.com :| > > |: https://entangle-photo.org -o- > https://www.instagram.com/dberrange :| > > > >