177a8e24cSAni Sinha#!/usr/bin/env python3 205caa062SThomas Huth# 396420a30SMichael Tokarev# Exercise QEMU generated ACPI/SMBIOS tables using biosbits, 477a8e24cSAni Sinha# https://biosbits.org/ 577a8e24cSAni Sinha# 677a8e24cSAni Sinha# This program is free software; you can redistribute it and/or modify 777a8e24cSAni Sinha# it under the terms of the GNU General Public License as published by 877a8e24cSAni Sinha# the Free Software Foundation; either version 2 of the License, or 977a8e24cSAni Sinha# (at your option) any later version. 1077a8e24cSAni Sinha# 1177a8e24cSAni Sinha# This program is distributed in the hope that it will be useful, 1277a8e24cSAni Sinha# but WITHOUT ANY WARRANTY; without even the implied warranty of 1377a8e24cSAni Sinha# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1477a8e24cSAni Sinha# GNU General Public License for more details. 1577a8e24cSAni Sinha# 1677a8e24cSAni Sinha# You should have received a copy of the GNU General Public License 1777a8e24cSAni Sinha# along with this program. If not, see <http://www.gnu.org/licenses/>. 1877a8e24cSAni Sinha# 1977a8e24cSAni Sinha# 2077a8e24cSAni Sinha# Author: 2194cd94f1SAni Sinha# Ani Sinha <anisinha@redhat.com> 2277a8e24cSAni Sinha 2377a8e24cSAni Sinha# pylint: disable=invalid-name 2477a8e24cSAni Sinha# pylint: disable=consider-using-f-string 2577a8e24cSAni Sinha 2677a8e24cSAni Sinha""" 2705caa062SThomas HuthThis is QEMU ACPI/SMBIOS functional tests using biosbits. 2877a8e24cSAni SinhaBiosbits is available originally at https://biosbits.org/. 2977a8e24cSAni SinhaThis test uses a fork of the upstream bits and has numerous fixes 3077a8e24cSAni Sinhaincluding an upgraded acpica. The fork is located here: 3177a8e24cSAni Sinhahttps://gitlab.com/qemu-project/biosbits-bits . 3277a8e24cSAni Sinha""" 3377a8e24cSAni Sinha 3477a8e24cSAni Sinhaimport os 3577a8e24cSAni Sinhaimport re 3677a8e24cSAni Sinhaimport shutil 3777a8e24cSAni Sinhaimport subprocess 3805caa062SThomas Huth 3977a8e24cSAni Sinhafrom typing import ( 4077a8e24cSAni Sinha List, 4177a8e24cSAni Sinha Optional, 4277a8e24cSAni Sinha Sequence, 4377a8e24cSAni Sinha) 4477a8e24cSAni Sinhafrom qemu.machine import QEMUMachine 453d593860SDaniel P. Berrangéfrom qemu_test import (QemuSystemTest, Asset, skipIfMissingCommands, 463d593860SDaniel P. Berrangé skipIfNotMachine) 4777a8e24cSAni Sinha 4877a8e24cSAni Sinha 497ef4c41eSAni Sinha# default timeout of 120 secs is sometimes not enough for bits test. 507ef4c41eSAni SinhaBITS_TIMEOUT = 200 5177a8e24cSAni Sinha 5277a8e24cSAni Sinhaclass QEMUBitsMachine(QEMUMachine): # pylint: disable=too-few-public-methods 5377a8e24cSAni Sinha """ 5477a8e24cSAni Sinha A QEMU VM, with isa-debugcon enabled and bits iso passed 5577a8e24cSAni Sinha using -cdrom to QEMU commandline. 5677a8e24cSAni Sinha 5777a8e24cSAni Sinha """ 5877a8e24cSAni Sinha def __init__(self, 5977a8e24cSAni Sinha binary: str, 6077a8e24cSAni Sinha args: Sequence[str] = (), 6177a8e24cSAni Sinha wrapper: Sequence[str] = (), 6277a8e24cSAni Sinha name: Optional[str] = None, 6377a8e24cSAni Sinha base_temp_dir: str = "/var/tmp", 6477a8e24cSAni Sinha debugcon_log: str = "debugcon-log.txt", 6577a8e24cSAni Sinha debugcon_addr: str = "0x403", 6677a8e24cSAni Sinha qmp_timer: Optional[float] = None): 6777a8e24cSAni Sinha # pylint: disable=too-many-arguments 6877a8e24cSAni Sinha 6977a8e24cSAni Sinha if name is None: 7077a8e24cSAni Sinha name = "qemu-bits-%d" % os.getpid() 7177a8e24cSAni Sinha super().__init__(binary, args, wrapper=wrapper, name=name, 7277a8e24cSAni Sinha base_temp_dir=base_temp_dir, 7346d4747aSJohn Snow qmp_timer=qmp_timer) 7477a8e24cSAni Sinha self.debugcon_log = debugcon_log 7577a8e24cSAni Sinha self.debugcon_addr = debugcon_addr 7677a8e24cSAni Sinha self.base_temp_dir = base_temp_dir 7777a8e24cSAni Sinha 7877a8e24cSAni Sinha @property 7977a8e24cSAni Sinha def _base_args(self) -> List[str]: 8077a8e24cSAni Sinha args = super()._base_args 8177a8e24cSAni Sinha args.extend([ 8277a8e24cSAni Sinha '-chardev', 8377a8e24cSAni Sinha 'file,path=%s,id=debugcon' %os.path.join(self.base_temp_dir, 8477a8e24cSAni Sinha self.debugcon_log), 8577a8e24cSAni Sinha '-device', 8677a8e24cSAni Sinha 'isa-debugcon,iobase=%s,chardev=debugcon' %self.debugcon_addr, 8777a8e24cSAni Sinha ]) 8877a8e24cSAni Sinha return args 8977a8e24cSAni Sinha 9077a8e24cSAni Sinha def base_args(self): 9177a8e24cSAni Sinha """return the base argument to QEMU binary""" 9277a8e24cSAni Sinha return self._base_args 9377a8e24cSAni Sinha 943d593860SDaniel P. Berrangé@skipIfMissingCommands("xorriso", "mformat") 953d593860SDaniel P. Berrangé@skipIfNotMachine("x86_64") 96fe455260SThomas Huthclass AcpiBitsTest(QemuSystemTest): #pylint: disable=too-many-instance-attributes 9777a8e24cSAni Sinha """ 9877a8e24cSAni Sinha ACPI and SMBIOS tests using biosbits. 9977a8e24cSAni Sinha """ 1001b7a07c4SAni Sinha # in slower systems the test can take as long as 3 minutes to complete. 1017ef4c41eSAni Sinha timeout = BITS_TIMEOUT 1021b7a07c4SAni Sinha 10305caa062SThomas Huth # following are some standard configuration constants 10405caa062SThomas Huth # gitlab CI does shallow clones of depth 20 10505caa062SThomas Huth BITS_INTERNAL_VER = 2020 10605caa062SThomas Huth # commit hash must match the artifact tag below 10705caa062SThomas Huth BITS_COMMIT_HASH = 'c7920d2b' 10805caa062SThomas Huth # this is the latest bits release as of today. 10905caa062SThomas Huth BITS_TAG = "qemu-bits-10262023" 11005caa062SThomas Huth 11105caa062SThomas Huth ASSET_BITS = Asset(("https://gitlab.com/qemu-project/" 11205caa062SThomas Huth "biosbits-bits/-/jobs/artifacts/%s/" 11305caa062SThomas Huth "download?job=qemu-bits-build" % BITS_TAG), 11405caa062SThomas Huth '1b8dd612c6831a6b491716a77acc486666aaa867051cdc34f7ce169c2e25f487') 11505caa062SThomas Huth 11677a8e24cSAni Sinha def __init__(self, *args, **kwargs): 11777a8e24cSAni Sinha super().__init__(*args, **kwargs) 11877a8e24cSAni Sinha self._vm = None 11977a8e24cSAni Sinha 12077a8e24cSAni Sinha self._debugcon_addr = '0x403' 12177a8e24cSAni Sinha self._debugcon_log = 'debugcon-log.txt' 12277a8e24cSAni Sinha 12377a8e24cSAni Sinha def _print_log(self, log): 12477a8e24cSAni Sinha self.logger.info('\nlogs from biosbits follows:') 12577a8e24cSAni Sinha self.logger.info('==========================================\n') 12677a8e24cSAni Sinha self.logger.info(log) 12777a8e24cSAni Sinha self.logger.info('==========================================\n') 12877a8e24cSAni Sinha 12977a8e24cSAni Sinha def copy_bits_config(self): 13077a8e24cSAni Sinha """ copies the bios bits config file into bits. 13177a8e24cSAni Sinha """ 132bd96e460SDaniel P. Berrangé bits_config_file = self.data_file('acpi-bits', 133bd96e460SDaniel P. Berrangé 'bits-config', 134bd96e460SDaniel P. Berrangé 'bits-cfg.txt') 135beaf88c8SDaniel P. Berrangé target_config_dir = self.scratch_file('bits-%d' % 136beaf88c8SDaniel P. Berrangé self.BITS_INTERNAL_VER, 13777a8e24cSAni Sinha 'boot') 138bd96e460SDaniel P. Berrangé self.assertTrue(os.path.exists(bits_config_file)) 13977a8e24cSAni Sinha self.assertTrue(os.path.exists(target_config_dir)) 140bd96e460SDaniel P. Berrangé shutil.copy2(bits_config_file, target_config_dir) 14177a8e24cSAni Sinha self.logger.info('copied config file %s to %s', 142bd96e460SDaniel P. Berrangé bits_config_file, target_config_dir) 14377a8e24cSAni Sinha 14477a8e24cSAni Sinha def copy_test_scripts(self): 14577a8e24cSAni Sinha """copies the python test scripts into bits. """ 14677a8e24cSAni Sinha 147bd96e460SDaniel P. Berrangé bits_test_dir = self.data_file('acpi-bits', 'bits-tests') 148beaf88c8SDaniel P. Berrangé target_test_dir = self.scratch_file('bits-%d' % self.BITS_INTERNAL_VER, 14977a8e24cSAni Sinha 'boot', 'python') 15077a8e24cSAni Sinha 15177a8e24cSAni Sinha self.assertTrue(os.path.exists(bits_test_dir)) 15277a8e24cSAni Sinha self.assertTrue(os.path.exists(target_test_dir)) 15377a8e24cSAni Sinha 15477a8e24cSAni Sinha for filename in os.listdir(bits_test_dir): 15577a8e24cSAni Sinha if os.path.isfile(os.path.join(bits_test_dir, filename)) and \ 15677a8e24cSAni Sinha filename.endswith('.py2'): 157ebc88b2dSDaniel P. Berrangé # All test scripts are named with extension .py2 so that 158ebc88b2dSDaniel P. Berrangé # they are not run by accident. 159ebc88b2dSDaniel P. Berrangé # 160ebc88b2dSDaniel P. Berrangé # These scripts are intended to run inside the test VM 161ebc88b2dSDaniel P. Berrangé # and are written for python 2.7 not python 3, hence 162ebc88b2dSDaniel P. Berrangé # would cause syntax errors if loaded ouside the VM. 16377a8e24cSAni Sinha newfilename = os.path.splitext(filename)[0] + '.py' 16477a8e24cSAni Sinha shutil.copy2(os.path.join(bits_test_dir, filename), 16577a8e24cSAni Sinha os.path.join(target_test_dir, newfilename)) 16677a8e24cSAni Sinha self.logger.info('copied test file %s to %s', 16777a8e24cSAni Sinha filename, target_test_dir) 16877a8e24cSAni Sinha 16977a8e24cSAni Sinha # now remove the pyc test file if it exists, otherwise the 17077a8e24cSAni Sinha # changes in the python test script won't be executed. 17177a8e24cSAni Sinha testfile_pyc = os.path.splitext(filename)[0] + '.pyc' 17277a8e24cSAni Sinha if os.access(os.path.join(target_test_dir, testfile_pyc), 17377a8e24cSAni Sinha os.F_OK): 17477a8e24cSAni Sinha os.remove(os.path.join(target_test_dir, testfile_pyc)) 17577a8e24cSAni Sinha self.logger.info('removed compiled file %s', 17677a8e24cSAni Sinha os.path.join(target_test_dir, 17777a8e24cSAni Sinha testfile_pyc)) 17877a8e24cSAni Sinha 17977a8e24cSAni Sinha def fix_mkrescue(self, mkrescue): 18077a8e24cSAni Sinha """ grub-mkrescue is a bash script with two variables, 'prefix' and 18177a8e24cSAni Sinha 'libdir'. They must be pointed to the right location so that the 18277a8e24cSAni Sinha iso can be generated appropriately. We point the two variables to 18377a8e24cSAni Sinha the directory where we have extracted our pre-built bits grub 18477a8e24cSAni Sinha tarball. 18577a8e24cSAni Sinha """ 186beaf88c8SDaniel P. Berrangé grub_x86_64_mods = self.scratch_file('grub-inst-x86_64-efi') 187beaf88c8SDaniel P. Berrangé grub_i386_mods = self.scratch_file('grub-inst') 18877a8e24cSAni Sinha 18977a8e24cSAni Sinha self.assertTrue(os.path.exists(grub_x86_64_mods)) 19077a8e24cSAni Sinha self.assertTrue(os.path.exists(grub_i386_mods)) 19177a8e24cSAni Sinha 19277a8e24cSAni Sinha new_script = "" 19377a8e24cSAni Sinha with open(mkrescue, 'r', encoding='utf-8') as filehandle: 19477a8e24cSAni Sinha orig_script = filehandle.read() 19577a8e24cSAni Sinha new_script = re.sub('(^prefix=)(.*)', 19677a8e24cSAni Sinha r'\1"%s"' %grub_x86_64_mods, 19777a8e24cSAni Sinha orig_script, flags=re.M) 19877a8e24cSAni Sinha new_script = re.sub('(^libdir=)(.*)', r'\1"%s/lib"' %grub_i386_mods, 19977a8e24cSAni Sinha new_script, flags=re.M) 20077a8e24cSAni Sinha 20177a8e24cSAni Sinha with open(mkrescue, 'w', encoding='utf-8') as filehandle: 20277a8e24cSAni Sinha filehandle.write(new_script) 20377a8e24cSAni Sinha 20477a8e24cSAni Sinha def generate_bits_iso(self): 20577a8e24cSAni Sinha """ Uses grub-mkrescue to generate a fresh bits iso with the python 20677a8e24cSAni Sinha test scripts 20777a8e24cSAni Sinha """ 208beaf88c8SDaniel P. Berrangé bits_dir = self.scratch_file('bits-%d' % self.BITS_INTERNAL_VER) 209beaf88c8SDaniel P. Berrangé iso_file = self.scratch_file('bits-%d.iso' % self.BITS_INTERNAL_VER) 210beaf88c8SDaniel P. Berrangé mkrescue_script = self.scratch_file('grub-inst-x86_64-efi', 211beaf88c8SDaniel P. Berrangé 'bin', 21277a8e24cSAni Sinha 'grub-mkrescue') 21377a8e24cSAni Sinha 21477a8e24cSAni Sinha self.assertTrue(os.access(mkrescue_script, 21577a8e24cSAni Sinha os.R_OK | os.W_OK | os.X_OK)) 21677a8e24cSAni Sinha 21777a8e24cSAni Sinha self.fix_mkrescue(mkrescue_script) 21877a8e24cSAni Sinha 21977a8e24cSAni Sinha self.logger.info('using grub-mkrescue for generating biosbits iso ...') 22077a8e24cSAni Sinha 22177a8e24cSAni Sinha try: 22204e5bd44SAni Sinha if os.getenv('V') or os.getenv('BITS_DEBUG'): 22305caa062SThomas Huth proc = subprocess.run([mkrescue_script, '-o', iso_file, 22405caa062SThomas Huth bits_dir], 22505caa062SThomas Huth stdout=subprocess.PIPE, 22605caa062SThomas Huth stderr=subprocess.STDOUT, 22705caa062SThomas Huth check=True) 22805caa062SThomas Huth self.logger.info("grub-mkrescue output %s" % proc.stdout) 22977a8e24cSAni Sinha else: 23077a8e24cSAni Sinha subprocess.check_call([mkrescue_script, '-o', 23177a8e24cSAni Sinha iso_file, bits_dir], 23277a8e24cSAni Sinha stderr=subprocess.DEVNULL, 23377a8e24cSAni Sinha stdout=subprocess.DEVNULL) 23477a8e24cSAni Sinha except Exception as e: # pylint: disable=broad-except 23577a8e24cSAni Sinha self.skipTest("Error while generating the bits iso. " 23677a8e24cSAni Sinha "Pass V=1 in the environment to get more details. " 23777a8e24cSAni Sinha + str(e)) 23877a8e24cSAni Sinha 23977a8e24cSAni Sinha self.assertTrue(os.access(iso_file, os.R_OK)) 24077a8e24cSAni Sinha 24177a8e24cSAni Sinha self.logger.info('iso file %s successfully generated.', iso_file) 24277a8e24cSAni Sinha 24377a8e24cSAni Sinha def setUp(self): # pylint: disable=arguments-differ 244fe455260SThomas Huth super().setUp() 24505caa062SThomas Huth self.logger = self.log 24677a8e24cSAni Sinha 247beaf88c8SDaniel P. Berrangé prebuiltDir = self.scratch_file('prebuilt') 24877a8e24cSAni Sinha if not os.path.isdir(prebuiltDir): 24977a8e24cSAni Sinha os.mkdir(prebuiltDir, mode=0o775) 25077a8e24cSAni Sinha 251beaf88c8SDaniel P. Berrangé bits_zip_file = self.scratch_file('prebuilt', 252beaf88c8SDaniel P. Berrangé 'bits-%d-%s.zip' 25305caa062SThomas Huth %(self.BITS_INTERNAL_VER, 25405caa062SThomas Huth self.BITS_COMMIT_HASH)) 255beaf88c8SDaniel P. Berrangé grub_tar_file = self.scratch_file('prebuilt', 25677a8e24cSAni Sinha 'bits-%d-%s-grub.tar.gz' 25705caa062SThomas Huth %(self.BITS_INTERNAL_VER, 25805caa062SThomas Huth self.BITS_COMMIT_HASH)) 25977a8e24cSAni Sinha 26077a8e24cSAni Sinha # extract the bits artifact in the temp working directory 261*5831ed84SDaniel P. Berrangé self.archive_extract(self.ASSET_BITS, sub_dir='prebuilt', format='zip') 26277a8e24cSAni Sinha 26377a8e24cSAni Sinha # extract the bits software in the temp working directory 264*5831ed84SDaniel P. Berrangé self.archive_extract(bits_zip_file) 265*5831ed84SDaniel P. Berrangé self.archive_extract(grub_tar_file) 26677a8e24cSAni Sinha 26777a8e24cSAni Sinha self.copy_test_scripts() 26877a8e24cSAni Sinha self.copy_bits_config() 26977a8e24cSAni Sinha self.generate_bits_iso() 27077a8e24cSAni Sinha 27177a8e24cSAni Sinha def parse_log(self): 27277a8e24cSAni Sinha """parse the log generated by running bits tests and 27377a8e24cSAni Sinha check for failures. 27477a8e24cSAni Sinha """ 275beaf88c8SDaniel P. Berrangé debugconf = self.scratch_file(self._debugcon_log) 27677a8e24cSAni Sinha log = "" 27777a8e24cSAni Sinha with open(debugconf, 'r', encoding='utf-8') as filehandle: 27877a8e24cSAni Sinha log = filehandle.read() 27977a8e24cSAni Sinha 28077a8e24cSAni Sinha matchiter = re.finditer(r'(.*Summary: )(\d+ passed), (\d+ failed).*', 28177a8e24cSAni Sinha log) 28277a8e24cSAni Sinha for match in matchiter: 28377a8e24cSAni Sinha # verify that no test cases failed. 28477a8e24cSAni Sinha try: 28577a8e24cSAni Sinha self.assertEqual(match.group(3).split()[0], '0', 28677a8e24cSAni Sinha 'Some bits tests seems to have failed. ' \ 28777a8e24cSAni Sinha 'Please check the test logs for more info.') 28877a8e24cSAni Sinha except AssertionError as e: 28977a8e24cSAni Sinha self._print_log(log) 29077a8e24cSAni Sinha raise e 29177a8e24cSAni Sinha else: 29204e5bd44SAni Sinha if os.getenv('V') or os.getenv('BITS_DEBUG'): 29377a8e24cSAni Sinha self._print_log(log) 29477a8e24cSAni Sinha 29577a8e24cSAni Sinha def tearDown(self): 29677a8e24cSAni Sinha """ 29777a8e24cSAni Sinha Lets do some cleanups. 29877a8e24cSAni Sinha """ 29977a8e24cSAni Sinha if self._vm: 30077a8e24cSAni Sinha self.assertFalse(not self._vm.is_running) 30177a8e24cSAni Sinha super().tearDown() 30277a8e24cSAni Sinha 30377a8e24cSAni Sinha def test_acpi_smbios_bits(self): 30496420a30SMichael Tokarev """The main test case implementation.""" 30577a8e24cSAni Sinha 306fe455260SThomas Huth self.set_machine('pc') 307beaf88c8SDaniel P. Berrangé iso_file = self.scratch_file('bits-%d.iso' % self.BITS_INTERNAL_VER) 30877a8e24cSAni Sinha 30977a8e24cSAni Sinha self.assertTrue(os.access(iso_file, os.R_OK)) 31077a8e24cSAni Sinha 31177a8e24cSAni Sinha self._vm = QEMUBitsMachine(binary=self.qemu_bin, 31203d6c237SDaniel P. Berrangé base_temp_dir=self.workdir, 31377a8e24cSAni Sinha debugcon_log=self._debugcon_log, 31477a8e24cSAni Sinha debugcon_addr=self._debugcon_addr) 31577a8e24cSAni Sinha 31677a8e24cSAni Sinha self._vm.add_args('-cdrom', '%s' %iso_file) 31777a8e24cSAni Sinha # the vm needs to be run under icount so that TCG emulation is 31877a8e24cSAni Sinha # consistent in terms of timing. smilatency tests have consistent 31977a8e24cSAni Sinha # timing requirements. 32077a8e24cSAni Sinha self._vm.add_args('-icount', 'auto') 321a874ddc9SAni Sinha # currently there is no support in bits for recognizing 64-bit SMBIOS 322a874ddc9SAni Sinha # entry points. QEMU defaults to 64-bit entry points since the 323a874ddc9SAni Sinha # upstream commit bf376f3020 ("hw/i386/pc: Default to use SMBIOS 3.0 324a874ddc9SAni Sinha # for newer machine models"). Therefore, enforce 32-bit entry point. 325a874ddc9SAni Sinha self._vm.add_args('-machine', 'smbios-entry-point-type=32') 32677a8e24cSAni Sinha 32794cd94f1SAni Sinha # enable console logging 32894cd94f1SAni Sinha self._vm.set_console() 32977a8e24cSAni Sinha self._vm.launch() 33094cd94f1SAni Sinha 33194cd94f1SAni Sinha 33277a8e24cSAni Sinha # biosbits has been configured to run all the specified test suites 33377a8e24cSAni Sinha # in batch mode and then automatically initiate a vm shutdown. 3347ef4c41eSAni Sinha self._vm.event_wait('SHUTDOWN', timeout=BITS_TIMEOUT) 335c4d4c40cSJohn Snow self._vm.wait(timeout=None) 33605caa062SThomas Huth self.logger.debug("Checking console output ...") 33777a8e24cSAni Sinha self.parse_log() 33805caa062SThomas Huth 33905caa062SThomas Huthif __name__ == '__main__': 340fe455260SThomas Huth QemuSystemTest.main() 341