xref: /qemu/tests/functional/test_acpi_bits.py (revision 23686dfb763d22d95c05c49382116d77133637b7)
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