]> git.proxmox.com Git - mirror_qemu.git/blame - tests/avocado/acpi-bits.py
python/machine: remove unused sock_dir argument
[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:
21# Ani Sinha <ani@anisinha.ca>
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
51from avocado_qemu import QemuBaseTest
52
ffa175f2 53deps = ["xorriso", "mformat"] # dependent tools needed in the test setup/box.
77a8e24c
AS
54supported_platforms = ['x86_64'] # supported test platforms.
55
56
57def which(tool):
58 """ looks up the full path for @tool, returns None if not found
59 or if @tool does not have executable permissions.
60 """
61 paths=os.getenv('PATH')
62 for p in paths.split(os.path.pathsep):
63 p = os.path.join(p, tool)
64 if os.path.exists(p) and os.access(p, os.X_OK):
65 return p
66 return None
67
68def missing_deps():
69 """ returns True if any of the test dependent tools are absent.
70 """
71 for dep in deps:
72 if which(dep) is None:
73 return True
74 return False
75
76def supported_platform():
77 """ checks if the test is running on a supported platform.
78 """
79 return platform.machine() in supported_platforms
80
81class QEMUBitsMachine(QEMUMachine): # pylint: disable=too-few-public-methods
82 """
83 A QEMU VM, with isa-debugcon enabled and bits iso passed
84 using -cdrom to QEMU commandline.
85
86 """
87 def __init__(self,
88 binary: str,
89 args: Sequence[str] = (),
90 wrapper: Sequence[str] = (),
91 name: Optional[str] = None,
92 base_temp_dir: str = "/var/tmp",
93 debugcon_log: str = "debugcon-log.txt",
94 debugcon_addr: str = "0x403",
77a8e24c
AS
95 qmp_timer: Optional[float] = None):
96 # pylint: disable=too-many-arguments
97
98 if name is None:
99 name = "qemu-bits-%d" % os.getpid()
77a8e24c
AS
100 super().__init__(binary, args, wrapper=wrapper, name=name,
101 base_temp_dir=base_temp_dir,
46d4747a 102 qmp_timer=qmp_timer)
77a8e24c
AS
103 self.debugcon_log = debugcon_log
104 self.debugcon_addr = debugcon_addr
105 self.base_temp_dir = base_temp_dir
106
107 @property
108 def _base_args(self) -> List[str]:
109 args = super()._base_args
110 args.extend([
111 '-chardev',
112 'file,path=%s,id=debugcon' %os.path.join(self.base_temp_dir,
113 self.debugcon_log),
114 '-device',
115 'isa-debugcon,iobase=%s,chardev=debugcon' %self.debugcon_addr,
116 ])
117 return args
118
119 def base_args(self):
120 """return the base argument to QEMU binary"""
121 return self._base_args
122
1afae3b8
AS
123@skipIf(not supported_platform() or missing_deps(),
124 'unsupported platform or dependencies (%s) not installed' \
125 % ','.join(deps))
77a8e24c
AS
126class AcpiBitsTest(QemuBaseTest): #pylint: disable=too-many-instance-attributes
127 """
128 ACPI and SMBIOS tests using biosbits.
129
130 :avocado: tags=arch:x86_64
131 :avocado: tags=acpi
132
133 """
1b7a07c4
AS
134 # in slower systems the test can take as long as 3 minutes to complete.
135 timeout = 200
136
77a8e24c
AS
137 def __init__(self, *args, **kwargs):
138 super().__init__(*args, **kwargs)
139 self._vm = None
140 self._workDir = None
141 self._baseDir = None
142
143 # following are some standard configuration constants
144 self._bitsInternalVer = 2020
145 self._bitsCommitHash = 'b48b88ff' # commit hash must match
146 # the artifact tag below
147 self._bitsTag = "qemu-bits-10182022" # this is the latest bits
148 # release as of today.
149 self._bitsArtSHA1Hash = 'b04790ac9b99b5662d0416392c73b97580641fe5'
150 self._bitsArtURL = ("https://gitlab.com/qemu-project/"
151 "biosbits-bits/-/jobs/artifacts/%s/"
152 "download?job=qemu-bits-build" %self._bitsTag)
153 self._debugcon_addr = '0x403'
154 self._debugcon_log = 'debugcon-log.txt'
155 logging.basicConfig(level=logging.INFO)
156 self.logger = logging.getLogger('acpi-bits')
157
158 def _print_log(self, log):
159 self.logger.info('\nlogs from biosbits follows:')
160 self.logger.info('==========================================\n')
161 self.logger.info(log)
162 self.logger.info('==========================================\n')
163
164 def copy_bits_config(self):
165 """ copies the bios bits config file into bits.
166 """
167 config_file = 'bits-cfg.txt'
168 bits_config_dir = os.path.join(self._baseDir, 'acpi-bits',
169 'bits-config')
170 target_config_dir = os.path.join(self._workDir,
171 'bits-%d' %self._bitsInternalVer,
172 'boot')
173 self.assertTrue(os.path.exists(bits_config_dir))
174 self.assertTrue(os.path.exists(target_config_dir))
175 self.assertTrue(os.access(os.path.join(bits_config_dir,
176 config_file), os.R_OK))
177 shutil.copy2(os.path.join(bits_config_dir, config_file),
178 target_config_dir)
179 self.logger.info('copied config file %s to %s',
180 config_file, target_config_dir)
181
182 def copy_test_scripts(self):
183 """copies the python test scripts into bits. """
184
185 bits_test_dir = os.path.join(self._baseDir, 'acpi-bits',
186 'bits-tests')
187 target_test_dir = os.path.join(self._workDir,
188 'bits-%d' %self._bitsInternalVer,
189 'boot', 'python')
190
191 self.assertTrue(os.path.exists(bits_test_dir))
192 self.assertTrue(os.path.exists(target_test_dir))
193
194 for filename in os.listdir(bits_test_dir):
195 if os.path.isfile(os.path.join(bits_test_dir, filename)) and \
196 filename.endswith('.py2'):
197 # all test scripts are named with extension .py2 so that
198 # avocado does not try to load them. These scripts are
199 # written for python 2.7 not python 3 and hence if avocado
200 # loaded them, it would complain about python 3 specific
201 # syntaxes.
202 newfilename = os.path.splitext(filename)[0] + '.py'
203 shutil.copy2(os.path.join(bits_test_dir, filename),
204 os.path.join(target_test_dir, newfilename))
205 self.logger.info('copied test file %s to %s',
206 filename, target_test_dir)
207
208 # now remove the pyc test file if it exists, otherwise the
209 # changes in the python test script won't be executed.
210 testfile_pyc = os.path.splitext(filename)[0] + '.pyc'
211 if os.access(os.path.join(target_test_dir, testfile_pyc),
212 os.F_OK):
213 os.remove(os.path.join(target_test_dir, testfile_pyc))
214 self.logger.info('removed compiled file %s',
215 os.path.join(target_test_dir,
216 testfile_pyc))
217
218 def fix_mkrescue(self, mkrescue):
219 """ grub-mkrescue is a bash script with two variables, 'prefix' and
220 'libdir'. They must be pointed to the right location so that the
221 iso can be generated appropriately. We point the two variables to
222 the directory where we have extracted our pre-built bits grub
223 tarball.
224 """
225 grub_x86_64_mods = os.path.join(self._workDir, 'grub-inst-x86_64-efi')
226 grub_i386_mods = os.path.join(self._workDir, 'grub-inst')
227
228 self.assertTrue(os.path.exists(grub_x86_64_mods))
229 self.assertTrue(os.path.exists(grub_i386_mods))
230
231 new_script = ""
232 with open(mkrescue, 'r', encoding='utf-8') as filehandle:
233 orig_script = filehandle.read()
234 new_script = re.sub('(^prefix=)(.*)',
235 r'\1"%s"' %grub_x86_64_mods,
236 orig_script, flags=re.M)
237 new_script = re.sub('(^libdir=)(.*)', r'\1"%s/lib"' %grub_i386_mods,
238 new_script, flags=re.M)
239
240 with open(mkrescue, 'w', encoding='utf-8') as filehandle:
241 filehandle.write(new_script)
242
243 def generate_bits_iso(self):
244 """ Uses grub-mkrescue to generate a fresh bits iso with the python
245 test scripts
246 """
247 bits_dir = os.path.join(self._workDir,
248 'bits-%d' %self._bitsInternalVer)
249 iso_file = os.path.join(self._workDir,
250 'bits-%d.iso' %self._bitsInternalVer)
251 mkrescue_script = os.path.join(self._workDir,
252 'grub-inst-x86_64-efi', 'bin',
253 'grub-mkrescue')
254
255 self.assertTrue(os.access(mkrescue_script,
256 os.R_OK | os.W_OK | os.X_OK))
257
258 self.fix_mkrescue(mkrescue_script)
259
260 self.logger.info('using grub-mkrescue for generating biosbits iso ...')
261
262 try:
04e5bd44 263 if os.getenv('V') or os.getenv('BITS_DEBUG'):
77a8e24c
AS
264 subprocess.check_call([mkrescue_script, '-o', iso_file,
265 bits_dir], stderr=subprocess.STDOUT)
266 else:
267 subprocess.check_call([mkrescue_script, '-o',
268 iso_file, bits_dir],
269 stderr=subprocess.DEVNULL,
270 stdout=subprocess.DEVNULL)
271 except Exception as e: # pylint: disable=broad-except
272 self.skipTest("Error while generating the bits iso. "
273 "Pass V=1 in the environment to get more details. "
274 + str(e))
275
276 self.assertTrue(os.access(iso_file, os.R_OK))
277
278 self.logger.info('iso file %s successfully generated.', iso_file)
279
280 def setUp(self): # pylint: disable=arguments-differ
281 super().setUp('qemu-system-')
282
283 self._baseDir = os.getenv('AVOCADO_TEST_BASEDIR')
284
285 # workdir could also be avocado's own workdir in self.workdir.
286 # At present, I prefer to maintain my own temporary working
287 # directory. It gives us more control over the generated bits
288 # log files and also for debugging, we may chose not to remove
289 # this working directory so that the logs and iso can be
290 # inspected manually and archived if needed.
291 self._workDir = tempfile.mkdtemp(prefix='acpi-bits-',
292 suffix='.tmp')
293 self.logger.info('working dir: %s', self._workDir)
294
295 prebuiltDir = os.path.join(self._workDir, 'prebuilt')
296 if not os.path.isdir(prebuiltDir):
297 os.mkdir(prebuiltDir, mode=0o775)
298
299 bits_zip_file = os.path.join(prebuiltDir, 'bits-%d-%s.zip'
300 %(self._bitsInternalVer,
301 self._bitsCommitHash))
302 grub_tar_file = os.path.join(prebuiltDir,
303 'bits-%d-%s-grub.tar.gz'
304 %(self._bitsInternalVer,
305 self._bitsCommitHash))
306
307 bitsLocalArtLoc = self.fetch_asset(self._bitsArtURL,
308 asset_hash=self._bitsArtSHA1Hash)
309 self.logger.info("downloaded bits artifacts to %s", bitsLocalArtLoc)
310
311 # extract the bits artifact in the temp working directory
312 with zipfile.ZipFile(bitsLocalArtLoc, 'r') as zref:
313 zref.extractall(prebuiltDir)
314
315 # extract the bits software in the temp working directory
316 with zipfile.ZipFile(bits_zip_file, 'r') as zref:
317 zref.extractall(self._workDir)
318
319 with tarfile.open(grub_tar_file, 'r', encoding='utf-8') as tarball:
320 tarball.extractall(self._workDir)
321
322 self.copy_test_scripts()
323 self.copy_bits_config()
324 self.generate_bits_iso()
325
326 def parse_log(self):
327 """parse the log generated by running bits tests and
328 check for failures.
329 """
330 debugconf = os.path.join(self._workDir, self._debugcon_log)
331 log = ""
332 with open(debugconf, 'r', encoding='utf-8') as filehandle:
333 log = filehandle.read()
334
335 matchiter = re.finditer(r'(.*Summary: )(\d+ passed), (\d+ failed).*',
336 log)
337 for match in matchiter:
338 # verify that no test cases failed.
339 try:
340 self.assertEqual(match.group(3).split()[0], '0',
341 'Some bits tests seems to have failed. ' \
342 'Please check the test logs for more info.')
343 except AssertionError as e:
344 self._print_log(log)
345 raise e
346 else:
04e5bd44 347 if os.getenv('V') or os.getenv('BITS_DEBUG'):
77a8e24c
AS
348 self._print_log(log)
349
350 def tearDown(self):
351 """
352 Lets do some cleanups.
353 """
354 if self._vm:
355 self.assertFalse(not self._vm.is_running)
1afae3b8 356 if not os.getenv('BITS_DEBUG') and self._workDir:
04e5bd44
AS
357 self.logger.info('removing the work directory %s', self._workDir)
358 shutil.rmtree(self._workDir)
359 else:
360 self.logger.info('not removing the work directory %s ' \
361 'as BITS_DEBUG is ' \
362 'passed in the environment', self._workDir)
77a8e24c
AS
363 super().tearDown()
364
365 def test_acpi_smbios_bits(self):
96420a30 366 """The main test case implementation."""
77a8e24c
AS
367
368 iso_file = os.path.join(self._workDir,
369 'bits-%d.iso' %self._bitsInternalVer)
370
371 self.assertTrue(os.access(iso_file, os.R_OK))
372
373 self._vm = QEMUBitsMachine(binary=self.qemu_bin,
374 base_temp_dir=self._workDir,
375 debugcon_log=self._debugcon_log,
376 debugcon_addr=self._debugcon_addr)
377
378 self._vm.add_args('-cdrom', '%s' %iso_file)
379 # the vm needs to be run under icount so that TCG emulation is
380 # consistent in terms of timing. smilatency tests have consistent
381 # timing requirements.
382 self._vm.add_args('-icount', 'auto')
383
384 args = " ".join(str(arg) for arg in self._vm.base_args()) + \
385 " " + " ".join(str(arg) for arg in self._vm.args)
386
387 self.logger.info("launching QEMU vm with the following arguments: %s",
388 args)
389
390 self._vm.launch()
391 # biosbits has been configured to run all the specified test suites
392 # in batch mode and then automatically initiate a vm shutdown.
c4d4c40c
JS
393 # Rely on avocado's unit test timeout.
394 self._vm.wait(timeout=None)
77a8e24c 395 self.parse_log()