3 # Exercize QEMU generated ACPI/SMBIOS tables using biosbits,
4 # https://biosbits.org/
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.
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.
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/>.
21 # Ani Sinha <ani@anisinha.ca>
23 # pylint: disable=invalid-name
24 # pylint: disable=consider-using-f-string
27 This is QEMU ACPI/SMBIOS avocado tests using biosbits.
28 Biosbits is available originally at https://biosbits.org/.
29 This test uses a fork of the upstream bits and has numerous fixes
30 including an upgraded acpica. The fork is located here:
31 https://gitlab.com/qemu-project/biosbits-bits .
49 from qemu
.machine
import QEMUMachine
50 from avocado
import skipIf
51 from avocado_qemu
import QemuBaseTest
53 deps
= ["xorriso"] # dependent tools needed in the test setup/box.
54 supported_platforms
= ['x86_64'] # supported test platforms.
58 """ looks up the full path for @tool, returns None if not found
59 or if @tool does not have executable permissions.
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
):
69 """ returns True if any of the test dependent tools are absent.
72 if which(dep
) is None:
76 def supported_platform():
77 """ checks if the test is running on a supported platform.
79 return platform
.machine() in supported_platforms
81 class QEMUBitsMachine(QEMUMachine
): # pylint: disable=too-few-public-methods
83 A QEMU VM, with isa-debugcon enabled and bits iso passed
84 using -cdrom to QEMU commandline.
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",
95 sock_dir
: Optional
[str] = None,
96 qmp_timer
: Optional
[float] = None):
97 # pylint: disable=too-many-arguments
100 name
= "qemu-bits-%d" % os
.getpid()
102 sock_dir
= base_temp_dir
103 super().__init
__(binary
, args
, wrapper
=wrapper
, name
=name
,
104 base_temp_dir
=base_temp_dir
,
105 sock_dir
=sock_dir
, qmp_timer
=qmp_timer
)
106 self
.debugcon_log
= debugcon_log
107 self
.debugcon_addr
= debugcon_addr
108 self
.base_temp_dir
= base_temp_dir
111 def _base_args(self
) -> List
[str]:
112 args
= super()._base
_args
115 'file,path=%s,id=debugcon' %os.path
.join(self
.base_temp_dir
,
118 'isa-debugcon,iobase=%s,chardev=debugcon' %self
.debugcon_addr
,
123 """return the base argument to QEMU binary"""
124 return self
._base
_args
126 @skipIf(not supported_platform() or missing_deps() or os
.getenv('GITLAB_CI'),
127 'incorrect platform or dependencies (%s) not installed ' \
128 'or running on GitLab' % ','.join(deps
))
129 class AcpiBitsTest(QemuBaseTest
): #pylint: disable=too-many-instance-attributes
131 ACPI and SMBIOS tests using biosbits.
133 :avocado: tags=arch:x86_64
137 def __init__(self
, *args
, **kwargs
):
138 super().__init
__(*args
, **kwargs
)
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
._bitsArtSHA
1Hash
= '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')
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')
164 def copy_bits_config(self
):
165 """ copies the bios bits config file into bits.
167 config_file
= 'bits-cfg.txt'
168 bits_config_dir
= os
.path
.join(self
._baseDir
, 'acpi-bits',
170 target_config_dir
= os
.path
.join(self
._workDir
,
171 'bits-%d' %self
._bitsInternalVer
,
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
),
179 self
.logger
.info('copied config file %s to %s',
180 config_file
, target_config_dir
)
182 def copy_test_scripts(self
):
183 """copies the python test scripts into bits. """
185 bits_test_dir
= os
.path
.join(self
._baseDir
, 'acpi-bits',
187 target_test_dir
= os
.path
.join(self
._workDir
,
188 'bits-%d' %self
._bitsInternalVer
,
191 self
.assertTrue(os
.path
.exists(bits_test_dir
))
192 self
.assertTrue(os
.path
.exists(target_test_dir
))
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
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
)
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
),
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
,
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
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')
228 self
.assertTrue(os
.path
.exists(grub_x86_64_mods
))
229 self
.assertTrue(os
.path
.exists(grub_i386_mods
))
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
_x
86_64_mods
,
236 orig_script
, flags
=re
.M
)
237 new_script
= re
.sub('(^libdir=)(.*)', r
'\1"%s/lib"' %grub
_i
386_mods
,
238 new_script
, flags
=re
.M
)
240 with
open(mkrescue
, 'w', encoding
='utf-8') as filehandle
:
241 filehandle
.write(new_script
)
243 def generate_bits_iso(self
):
244 """ Uses grub-mkrescue to generate a fresh bits iso with the python
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',
255 self
.assertTrue(os
.access(mkrescue_script
,
256 os
.R_OK | os
.W_OK | os
.X_OK
))
258 self
.fix_mkrescue(mkrescue_script
)
260 self
.logger
.info('using grub-mkrescue for generating biosbits iso ...')
264 subprocess
.check_call([mkrescue_script
, '-o', iso_file
,
265 bits_dir
], stderr
=subprocess
.STDOUT
)
267 subprocess
.check_call([mkrescue_script
, '-o',
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. "
276 self
.assertTrue(os
.access(iso_file
, os
.R_OK
))
278 self
.logger
.info('iso file %s successfully generated.', iso_file
)
280 def setUp(self
): # pylint: disable=arguments-differ
281 super().setUp('qemu-system-')
283 self
._baseDir
= os
.getenv('AVOCADO_TEST_BASEDIR')
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-',
293 self
.logger
.info('working dir: %s', self
._workDir
)
295 prebuiltDir
= os
.path
.join(self
._workDir
, 'prebuilt')
296 if not os
.path
.isdir(prebuiltDir
):
297 os
.mkdir(prebuiltDir
, mode
=0o775)
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
))
307 bitsLocalArtLoc
= self
.fetch_asset(self
._bitsArtURL
,
308 asset_hash
=self
._bitsArtSHA
1Hash
)
309 self
.logger
.info("downloaded bits artifacts to %s", bitsLocalArtLoc
)
311 # extract the bits artifact in the temp working directory
312 with zipfile
.ZipFile(bitsLocalArtLoc
, 'r') as zref
:
313 zref
.extractall(prebuiltDir
)
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
)
319 with tarfile
.open(grub_tar_file
, 'r', encoding
='utf-8') as tarball
:
320 tarball
.extractall(self
._workDir
)
322 self
.copy_test_scripts()
323 self
.copy_bits_config()
324 self
.generate_bits_iso()
327 """parse the log generated by running bits tests and
330 debugconf
= os
.path
.join(self
._workDir
, self
._debugcon
_log
)
332 with
open(debugconf
, 'r', encoding
='utf-8') as filehandle
:
333 log
= filehandle
.read()
335 matchiter
= re
.finditer(r
'(.*Summary: )(\d+ passed), (\d+ failed).*',
337 for match
in matchiter
:
338 # verify that no test cases failed.
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
:
352 Lets do some cleanups.
355 self
.assertFalse(not self
._vm
.is_running
)
356 self
.logger
.info('removing the work directory %s', self
._workDir
)
357 shutil
.rmtree(self
._workDir
)
360 def test_acpi_smbios_bits(self
):
361 """The main test case implementaion."""
363 iso_file
= os
.path
.join(self
._workDir
,
364 'bits-%d.iso' %self
._bitsInternalVer
)
366 self
.assertTrue(os
.access(iso_file
, os
.R_OK
))
368 self
._vm
= QEMUBitsMachine(binary
=self
.qemu_bin
,
369 base_temp_dir
=self
._workDir
,
370 debugcon_log
=self
._debugcon
_log
,
371 debugcon_addr
=self
._debugcon
_addr
)
373 self
._vm
.add_args('-cdrom', '%s' %iso_file
)
374 # the vm needs to be run under icount so that TCG emulation is
375 # consistent in terms of timing. smilatency tests have consistent
376 # timing requirements.
377 self
._vm
.add_args('-icount', 'auto')
379 args
= " ".join(str(arg
) for arg
in self
._vm
.base_args()) + \
380 " " + " ".join(str(arg
) for arg
in self
._vm
.args
)
382 self
.logger
.info("launching QEMU vm with the following arguments: %s",
386 # biosbits has been configured to run all the specified test suites
387 # in batch mode and then automatically initiate a vm shutdown.
388 # Rely on avocado's unit test timeout.
389 self
._vm
.wait(timeout
=None)