]> git.proxmox.com Git - mirror_qemu.git/blame - tests/avocado/acpi-bits.py
Merge tag 'pull-maintainer-may24-160524-2' of https://gitlab.com/stsquad/qemu into...
[mirror_qemu.git] / tests / avocado / acpi-bits.py
CommitLineData
77a8e24c
AS
1#!/usr/bin/env python3
2# group: rw quick
96420a30 3# Exercise QEMU generated ACPI/SMBIOS tables using biosbits,
77a8e24c
AS
4# https://biosbits.org/
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19#
20# Author:
94cd94f1 21# Ani Sinha <anisinha@redhat.com>
77a8e24c
AS
22
23# pylint: disable=invalid-name
24# pylint: disable=consider-using-f-string
25
26"""
27This is QEMU ACPI/SMBIOS avocado tests using biosbits.
28Biosbits is available originally at https://biosbits.org/.
29This test uses a fork of the upstream bits and has numerous fixes
30including an upgraded acpica. The fork is located here:
31https://gitlab.com/qemu-project/biosbits-bits .
32"""
33
34import logging
35import os
36import platform
37import re
38import shutil
39import subprocess
40import tarfile
41import tempfile
42import time
43import zipfile
44from typing import (
45 List,
46 Optional,
47 Sequence,
48)
49from qemu.machine import QEMUMachine
50from avocado import skipIf
94cd94f1 51from avocado.utils import datadrainer as drainer
77a8e24c
AS
52from avocado_qemu import QemuBaseTest
53
ffa175f2 54deps = ["xorriso", "mformat"] # dependent tools needed in the test setup/box.
77a8e24c
AS
55supported_platforms = ['x86_64'] # supported test platforms.
56
7ef4c41e
AS
57# default timeout of 120 secs is sometimes not enough for bits test.
58BITS_TIMEOUT = 200
77a8e24c
AS
59
60def which(tool):
61 """ looks up the full path for @tool, returns None if not found
62 or if @tool does not have executable permissions.
63 """
64 paths=os.getenv('PATH')
65 for p in paths.split(os.path.pathsep):
66 p = os.path.join(p, tool)
67 if os.path.exists(p) and os.access(p, os.X_OK):
68 return p
69 return None
70
71def missing_deps():
72 """ returns True if any of the test dependent tools are absent.
73 """
74 for dep in deps:
75 if which(dep) is None:
76 return True
77 return False
78
79def supported_platform():
80 """ checks if the test is running on a supported platform.
81 """
82 return platform.machine() in supported_platforms
83
84class QEMUBitsMachine(QEMUMachine): # pylint: disable=too-few-public-methods
85 """
86 A QEMU VM, with isa-debugcon enabled and bits iso passed
87 using -cdrom to QEMU commandline.
88
89 """
90 def __init__(self,
91 binary: str,
92 args: Sequence[str] = (),
93 wrapper: Sequence[str] = (),
94 name: Optional[str] = None,
95 base_temp_dir: str = "/var/tmp",
96 debugcon_log: str = "debugcon-log.txt",
97 debugcon_addr: str = "0x403",
77a8e24c
AS
98 qmp_timer: Optional[float] = None):
99 # pylint: disable=too-many-arguments
100
101 if name is None:
102 name = "qemu-bits-%d" % os.getpid()
77a8e24c
AS
103 super().__init__(binary, args, wrapper=wrapper, name=name,
104 base_temp_dir=base_temp_dir,
46d4747a 105 qmp_timer=qmp_timer)
77a8e24c
AS
106 self.debugcon_log = debugcon_log
107 self.debugcon_addr = debugcon_addr
108 self.base_temp_dir = base_temp_dir
109
110 @property
111 def _base_args(self) -> List[str]:
112 args = super()._base_args
113 args.extend([
114 '-chardev',
115 'file,path=%s,id=debugcon' %os.path.join(self.base_temp_dir,
116 self.debugcon_log),
117 '-device',
118 'isa-debugcon,iobase=%s,chardev=debugcon' %self.debugcon_addr,
119 ])
120 return args
121
122 def base_args(self):
123 """return the base argument to QEMU binary"""
124 return self._base_args
125
1afae3b8
AS
126@skipIf(not supported_platform() or missing_deps(),
127 'unsupported platform or dependencies (%s) not installed' \
128 % ','.join(deps))
77a8e24c
AS
129class AcpiBitsTest(QemuBaseTest): #pylint: disable=too-many-instance-attributes
130 """
131 ACPI and SMBIOS tests using biosbits.
132
133 :avocado: tags=arch:x86_64
134 :avocado: tags=acpi
135
136 """
1b7a07c4 137 # in slower systems the test can take as long as 3 minutes to complete.
7ef4c41e 138 timeout = BITS_TIMEOUT
1b7a07c4 139
77a8e24c
AS
140 def __init__(self, *args, **kwargs):
141 super().__init__(*args, **kwargs)
142 self._vm = None
143 self._workDir = None
144 self._baseDir = None
145
146 # following are some standard configuration constants
94cd94f1
AS
147 self._bitsInternalVer = 2020 # gitlab CI does shallow clones of depth 20
148 self._bitsCommitHash = 'c7920d2b' # commit hash must match
77a8e24c 149 # the artifact tag below
94cd94f1 150 self._bitsTag = "qemu-bits-10262023" # this is the latest bits
77a8e24c 151 # release as of today.
94cd94f1 152 self._bitsArtSHA1Hash = 'b22cdfcfc7453875297d06d626f5474ee36a343f'
77a8e24c
AS
153 self._bitsArtURL = ("https://gitlab.com/qemu-project/"
154 "biosbits-bits/-/jobs/artifacts/%s/"
155 "download?job=qemu-bits-build" %self._bitsTag)
156 self._debugcon_addr = '0x403'
157 self._debugcon_log = 'debugcon-log.txt'
158 logging.basicConfig(level=logging.INFO)
159 self.logger = logging.getLogger('acpi-bits')
160
161 def _print_log(self, log):
162 self.logger.info('\nlogs from biosbits follows:')
163 self.logger.info('==========================================\n')
164 self.logger.info(log)
165 self.logger.info('==========================================\n')
166
167 def copy_bits_config(self):
168 """ copies the bios bits config file into bits.
169 """
170 config_file = 'bits-cfg.txt'
171 bits_config_dir = os.path.join(self._baseDir, 'acpi-bits',
172 'bits-config')
173 target_config_dir = os.path.join(self._workDir,
174 'bits-%d' %self._bitsInternalVer,
175 'boot')
176 self.assertTrue(os.path.exists(bits_config_dir))
177 self.assertTrue(os.path.exists(target_config_dir))
178 self.assertTrue(os.access(os.path.join(bits_config_dir,
179 config_file), os.R_OK))
180 shutil.copy2(os.path.join(bits_config_dir, config_file),
181 target_config_dir)
182 self.logger.info('copied config file %s to %s',
183 config_file, target_config_dir)
184
185 def copy_test_scripts(self):
186 """copies the python test scripts into bits. """
187
188 bits_test_dir = os.path.join(self._baseDir, 'acpi-bits',
189 'bits-tests')
190 target_test_dir = os.path.join(self._workDir,
191 'bits-%d' %self._bitsInternalVer,
192 'boot', 'python')
193
194 self.assertTrue(os.path.exists(bits_test_dir))
195 self.assertTrue(os.path.exists(target_test_dir))
196
197 for filename in os.listdir(bits_test_dir):
198 if os.path.isfile(os.path.join(bits_test_dir, filename)) and \
199 filename.endswith('.py2'):
200 # all test scripts are named with extension .py2 so that
201 # avocado does not try to load them. These scripts are
202 # written for python 2.7 not python 3 and hence if avocado
203 # loaded them, it would complain about python 3 specific
204 # syntaxes.
205 newfilename = os.path.splitext(filename)[0] + '.py'
206 shutil.copy2(os.path.join(bits_test_dir, filename),
207 os.path.join(target_test_dir, newfilename))
208 self.logger.info('copied test file %s to %s',
209 filename, target_test_dir)
210
211 # now remove the pyc test file if it exists, otherwise the
212 # changes in the python test script won't be executed.
213 testfile_pyc = os.path.splitext(filename)[0] + '.pyc'
214 if os.access(os.path.join(target_test_dir, testfile_pyc),
215 os.F_OK):
216 os.remove(os.path.join(target_test_dir, testfile_pyc))
217 self.logger.info('removed compiled file %s',
218 os.path.join(target_test_dir,
219 testfile_pyc))
220
221 def fix_mkrescue(self, mkrescue):
222 """ grub-mkrescue is a bash script with two variables, 'prefix' and
223 'libdir'. They must be pointed to the right location so that the
224 iso can be generated appropriately. We point the two variables to
225 the directory where we have extracted our pre-built bits grub
226 tarball.
227 """
228 grub_x86_64_mods = os.path.join(self._workDir, 'grub-inst-x86_64-efi')
229 grub_i386_mods = os.path.join(self._workDir, 'grub-inst')
230
231 self.assertTrue(os.path.exists(grub_x86_64_mods))
232 self.assertTrue(os.path.exists(grub_i386_mods))
233
234 new_script = ""
235 with open(mkrescue, 'r', encoding='utf-8') as filehandle:
236 orig_script = filehandle.read()
237 new_script = re.sub('(^prefix=)(.*)',
238 r'\1"%s"' %grub_x86_64_mods,
239 orig_script, flags=re.M)
240 new_script = re.sub('(^libdir=)(.*)', r'\1"%s/lib"' %grub_i386_mods,
241 new_script, flags=re.M)
242
243 with open(mkrescue, 'w', encoding='utf-8') as filehandle:
244 filehandle.write(new_script)
245
246 def generate_bits_iso(self):
247 """ Uses grub-mkrescue to generate a fresh bits iso with the python
248 test scripts
249 """
250 bits_dir = os.path.join(self._workDir,
251 'bits-%d' %self._bitsInternalVer)
252 iso_file = os.path.join(self._workDir,
253 'bits-%d.iso' %self._bitsInternalVer)
254 mkrescue_script = os.path.join(self._workDir,
255 'grub-inst-x86_64-efi', 'bin',
256 'grub-mkrescue')
257
258 self.assertTrue(os.access(mkrescue_script,
259 os.R_OK | os.W_OK | os.X_OK))
260
261 self.fix_mkrescue(mkrescue_script)
262
263 self.logger.info('using grub-mkrescue for generating biosbits iso ...')
264
265 try:
04e5bd44 266 if os.getenv('V') or os.getenv('BITS_DEBUG'):
77a8e24c
AS
267 subprocess.check_call([mkrescue_script, '-o', iso_file,
268 bits_dir], stderr=subprocess.STDOUT)
269 else:
270 subprocess.check_call([mkrescue_script, '-o',
271 iso_file, bits_dir],
272 stderr=subprocess.DEVNULL,
273 stdout=subprocess.DEVNULL)
274 except Exception as e: # pylint: disable=broad-except
275 self.skipTest("Error while generating the bits iso. "
276 "Pass V=1 in the environment to get more details. "
277 + str(e))
278
279 self.assertTrue(os.access(iso_file, os.R_OK))
280
281 self.logger.info('iso file %s successfully generated.', iso_file)
282
283 def setUp(self): # pylint: disable=arguments-differ
284 super().setUp('qemu-system-')
285
286 self._baseDir = os.getenv('AVOCADO_TEST_BASEDIR')
287
288 # workdir could also be avocado's own workdir in self.workdir.
289 # At present, I prefer to maintain my own temporary working
290 # directory. It gives us more control over the generated bits
291 # log files and also for debugging, we may chose not to remove
292 # this working directory so that the logs and iso can be
293 # inspected manually and archived if needed.
294 self._workDir = tempfile.mkdtemp(prefix='acpi-bits-',
295 suffix='.tmp')
296 self.logger.info('working dir: %s', self._workDir)
297
298 prebuiltDir = os.path.join(self._workDir, 'prebuilt')
299 if not os.path.isdir(prebuiltDir):
300 os.mkdir(prebuiltDir, mode=0o775)
301
302 bits_zip_file = os.path.join(prebuiltDir, 'bits-%d-%s.zip'
303 %(self._bitsInternalVer,
304 self._bitsCommitHash))
305 grub_tar_file = os.path.join(prebuiltDir,
306 'bits-%d-%s-grub.tar.gz'
307 %(self._bitsInternalVer,
308 self._bitsCommitHash))
309
310 bitsLocalArtLoc = self.fetch_asset(self._bitsArtURL,
311 asset_hash=self._bitsArtSHA1Hash)
312 self.logger.info("downloaded bits artifacts to %s", bitsLocalArtLoc)
313
314 # extract the bits artifact in the temp working directory
315 with zipfile.ZipFile(bitsLocalArtLoc, 'r') as zref:
316 zref.extractall(prebuiltDir)
317
318 # extract the bits software in the temp working directory
319 with zipfile.ZipFile(bits_zip_file, 'r') as zref:
320 zref.extractall(self._workDir)
321
322 with tarfile.open(grub_tar_file, 'r', encoding='utf-8') as tarball:
323 tarball.extractall(self._workDir)
324
325 self.copy_test_scripts()
326 self.copy_bits_config()
327 self.generate_bits_iso()
328
329 def parse_log(self):
330 """parse the log generated by running bits tests and
331 check for failures.
332 """
333 debugconf = os.path.join(self._workDir, self._debugcon_log)
334 log = ""
335 with open(debugconf, 'r', encoding='utf-8') as filehandle:
336 log = filehandle.read()
337
338 matchiter = re.finditer(r'(.*Summary: )(\d+ passed), (\d+ failed).*',
339 log)
340 for match in matchiter:
341 # verify that no test cases failed.
342 try:
343 self.assertEqual(match.group(3).split()[0], '0',
344 'Some bits tests seems to have failed. ' \
345 'Please check the test logs for more info.')
346 except AssertionError as e:
347 self._print_log(log)
348 raise e
349 else:
04e5bd44 350 if os.getenv('V') or os.getenv('BITS_DEBUG'):
77a8e24c
AS
351 self._print_log(log)
352
353 def tearDown(self):
354 """
355 Lets do some cleanups.
356 """
357 if self._vm:
358 self.assertFalse(not self._vm.is_running)
1afae3b8 359 if not os.getenv('BITS_DEBUG') and self._workDir:
04e5bd44
AS
360 self.logger.info('removing the work directory %s', self._workDir)
361 shutil.rmtree(self._workDir)
362 else:
363 self.logger.info('not removing the work directory %s ' \
364 'as BITS_DEBUG is ' \
365 'passed in the environment', self._workDir)
77a8e24c
AS
366 super().tearDown()
367
368 def test_acpi_smbios_bits(self):
96420a30 369 """The main test case implementation."""
77a8e24c
AS
370
371 iso_file = os.path.join(self._workDir,
372 'bits-%d.iso' %self._bitsInternalVer)
373
374 self.assertTrue(os.access(iso_file, os.R_OK))
375
376 self._vm = QEMUBitsMachine(binary=self.qemu_bin,
377 base_temp_dir=self._workDir,
378 debugcon_log=self._debugcon_log,
379 debugcon_addr=self._debugcon_addr)
380
381 self._vm.add_args('-cdrom', '%s' %iso_file)
382 # the vm needs to be run under icount so that TCG emulation is
383 # consistent in terms of timing. smilatency tests have consistent
384 # timing requirements.
385 self._vm.add_args('-icount', 'auto')
a874ddc9
AS
386 # currently there is no support in bits for recognizing 64-bit SMBIOS
387 # entry points. QEMU defaults to 64-bit entry points since the
388 # upstream commit bf376f3020 ("hw/i386/pc: Default to use SMBIOS 3.0
389 # for newer machine models"). Therefore, enforce 32-bit entry point.
390 self._vm.add_args('-machine', 'smbios-entry-point-type=32')
77a8e24c 391
94cd94f1
AS
392 # enable console logging
393 self._vm.set_console()
394 self._vm.launch()
77a8e24c 395
94cd94f1
AS
396 self.logger.debug("Console output from bits VM follows ...")
397 c_drainer = drainer.LineLogger(self._vm.console_socket.fileno(),
398 logger=self.logger.getChild("console"),
399 stop_check=(lambda :
400 not self._vm.is_running()))
401 c_drainer.start()
77a8e24c 402
77a8e24c
AS
403 # biosbits has been configured to run all the specified test suites
404 # in batch mode and then automatically initiate a vm shutdown.
7ef4c41e
AS
405 # Set timeout to BITS_TIMEOUT for SHUTDOWN event from bits VM at par
406 # with the avocado test timeout.
407 self._vm.event_wait('SHUTDOWN', timeout=BITS_TIMEOUT)
c4d4c40c 408 self._vm.wait(timeout=None)
77a8e24c 409 self.parse_log()