diff options
Diffstat (limited to 'meta/lib/oeqa/utils/commands.py')
-rw-r--r-- | meta/lib/oeqa/utils/commands.py | 81 |
1 files changed, 59 insertions, 22 deletions
diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py index a71c16ab14..b60a6e6c38 100644 --- a/meta/lib/oeqa/utils/commands.py +++ b/meta/lib/oeqa/utils/commands.py | |||
@@ -8,11 +8,8 @@ | |||
8 | # This module is mainly used by scripts/oe-selftest and modules under meta/oeqa/selftest | 8 | # This module is mainly used by scripts/oe-selftest and modules under meta/oeqa/selftest |
9 | # It provides a class and methods for running commands on the host in a convienent way for tests. | 9 | # It provides a class and methods for running commands on the host in a convienent way for tests. |
10 | 10 | ||
11 | |||
12 | |||
13 | import os | 11 | import os |
14 | import sys | 12 | import sys |
15 | import signal | ||
16 | import subprocess | 13 | import subprocess |
17 | import threading | 14 | import threading |
18 | import time | 15 | import time |
@@ -21,6 +18,7 @@ from oeqa.utils import CommandError | |||
21 | from oeqa.utils import ftools | 18 | from oeqa.utils import ftools |
22 | import re | 19 | import re |
23 | import contextlib | 20 | import contextlib |
21 | import errno | ||
24 | # Export test doesn't require bb | 22 | # Export test doesn't require bb |
25 | try: | 23 | try: |
26 | import bb | 24 | import bb |
@@ -85,7 +83,7 @@ class Command(object): | |||
85 | except OSError as ex: | 83 | except OSError as ex: |
86 | # It's not an error when the command does not consume all | 84 | # It's not an error when the command does not consume all |
87 | # of our data. subprocess.communicate() also ignores that. | 85 | # of our data. subprocess.communicate() also ignores that. |
88 | if ex.errno != EPIPE: | 86 | if ex.errno != errno.EPIPE: |
89 | raise | 87 | raise |
90 | 88 | ||
91 | # We write in a separate thread because then we can read | 89 | # We write in a separate thread because then we can read |
@@ -117,7 +115,7 @@ class Command(object): | |||
117 | else: | 115 | else: |
118 | deadline = time.time() + self.timeout | 116 | deadline = time.time() + self.timeout |
119 | for thread in self.threads: | 117 | for thread in self.threads: |
120 | timeout = deadline - time.time() | 118 | timeout = deadline - time.time() |
121 | if timeout < 0: | 119 | if timeout < 0: |
122 | timeout = 0 | 120 | timeout = 0 |
123 | thread.join(timeout) | 121 | thread.join(timeout) |
@@ -168,18 +166,22 @@ class Result(object): | |||
168 | 166 | ||
169 | 167 | ||
170 | def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=True, | 168 | def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=True, |
171 | native_sysroot=None, limit_exc_output=0, output_log=None, **options): | 169 | native_sysroot=None, target_sys=None, limit_exc_output=0, output_log=None, **options): |
172 | result = Result() | 170 | result = Result() |
173 | 171 | ||
174 | if native_sysroot: | 172 | if native_sysroot: |
175 | extra_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin" % \ | 173 | new_env = dict(options.get('env', os.environ)) |
176 | (native_sysroot, native_sysroot, native_sysroot) | 174 | paths = new_env["PATH"].split(":") |
177 | extra_libpaths = "%s/lib:%s/usr/lib" % \ | 175 | paths = [ |
178 | (native_sysroot, native_sysroot) | 176 | os.path.join(native_sysroot, "bin"), |
179 | nenv = dict(options.get('env', os.environ)) | 177 | os.path.join(native_sysroot, "sbin"), |
180 | nenv['PATH'] = extra_paths + ':' + nenv.get('PATH', '') | 178 | os.path.join(native_sysroot, "usr", "bin"), |
181 | nenv['LD_LIBRARY_PATH'] = extra_libpaths + ':' + nenv.get('LD_LIBRARY_PATH', '') | 179 | os.path.join(native_sysroot, "usr", "sbin"), |
182 | options['env'] = nenv | 180 | ] + paths |
181 | if target_sys: | ||
182 | paths = [os.path.join(native_sysroot, "usr", "bin", target_sys)] + paths | ||
183 | new_env["PATH"] = ":".join(paths) | ||
184 | options['env'] = new_env | ||
183 | 185 | ||
184 | cmd = Command(command, timeout=timeout, output_log=output_log, **options) | 186 | cmd = Command(command, timeout=timeout, output_log=output_log, **options) |
185 | cmd.run() | 187 | cmd.run() |
@@ -201,6 +203,8 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=T | |||
201 | 203 | ||
202 | if result.status and not ignore_status: | 204 | if result.status and not ignore_status: |
203 | exc_output = result.output | 205 | exc_output = result.output |
206 | if result.error: | ||
207 | exc_output = exc_output + result.error | ||
204 | if limit_exc_output > 0: | 208 | if limit_exc_output > 0: |
205 | split = result.output.splitlines() | 209 | split = result.output.splitlines() |
206 | if len(split) > limit_exc_output: | 210 | if len(split) > limit_exc_output: |
@@ -281,10 +285,25 @@ def get_bb_vars(variables=None, target=None, postconfig=None): | |||
281 | return values | 285 | return values |
282 | 286 | ||
283 | def get_bb_var(var, target=None, postconfig=None): | 287 | def get_bb_var(var, target=None, postconfig=None): |
284 | return get_bb_vars([var], target, postconfig)[var] | 288 | if postconfig: |
285 | 289 | return bitbake("-e %s" % target or "", postconfig=postconfig).output | |
286 | def get_test_layer(): | 290 | else: |
287 | layers = get_bb_var("BBLAYERS").split() | 291 | # Fast-path for the non-postconfig case |
292 | cmd = ["bitbake-getvar", "--quiet", "--value", var] | ||
293 | if target: | ||
294 | cmd.extend(["--recipe", target]) | ||
295 | try: | ||
296 | return subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE).stdout.strip() | ||
297 | except subprocess.CalledProcessError as e: | ||
298 | # We need to return None not the empty string if the variable hasn't been set. | ||
299 | if e.returncode == 1: | ||
300 | return None | ||
301 | raise | ||
302 | |||
303 | def get_test_layer(bblayers=None): | ||
304 | if bblayers is None: | ||
305 | bblayers = get_bb_var("BBLAYERS") | ||
306 | layers = bblayers.split() | ||
288 | testlayer = None | 307 | testlayer = None |
289 | for l in layers: | 308 | for l in layers: |
290 | if '~' in l: | 309 | if '~' in l: |
@@ -296,6 +315,7 @@ def get_test_layer(): | |||
296 | 315 | ||
297 | def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec='recipes-*/*'): | 316 | def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec='recipes-*/*'): |
298 | os.makedirs(os.path.join(templayerdir, 'conf')) | 317 | os.makedirs(os.path.join(templayerdir, 'conf')) |
318 | corenames = get_bb_var('LAYERSERIES_CORENAMES') | ||
299 | with open(os.path.join(templayerdir, 'conf', 'layer.conf'), 'w') as f: | 319 | with open(os.path.join(templayerdir, 'conf', 'layer.conf'), 'w') as f: |
300 | f.write('BBPATH .= ":${LAYERDIR}"\n') | 320 | f.write('BBPATH .= ":${LAYERDIR}"\n') |
301 | f.write('BBFILES += "${LAYERDIR}/%s/*.bb \\' % recipepathspec) | 321 | f.write('BBFILES += "${LAYERDIR}/%s/*.bb \\' % recipepathspec) |
@@ -304,12 +324,29 @@ def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec= | |||
304 | f.write('BBFILE_PATTERN_%s = "^${LAYERDIR}/"\n' % templayername) | 324 | f.write('BBFILE_PATTERN_%s = "^${LAYERDIR}/"\n' % templayername) |
305 | f.write('BBFILE_PRIORITY_%s = "%d"\n' % (templayername, priority)) | 325 | f.write('BBFILE_PRIORITY_%s = "%d"\n' % (templayername, priority)) |
306 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_%s = "1"\n' % templayername) | 326 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_%s = "1"\n' % templayername) |
307 | f.write('LAYERSERIES_COMPAT_%s = "${LAYERSERIES_COMPAT_core}"\n' % templayername) | 327 | f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames)) |
308 | 328 | ||
309 | @contextlib.contextmanager | 329 | @contextlib.contextmanager |
310 | def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, discard_writes=True): | 330 | def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, boot_patterns = {}, discard_writes=True): |
311 | """ | 331 | """ |
312 | launch_cmd means directly run the command, don't need set rootfs or env vars. | 332 | Starts a context manager for a 'oeqa.targetcontrol.QemuTarget' resource. |
333 | The underlying Qemu will be booted into a shell when the generator yields | ||
334 | and stopped when the 'with' block exits. | ||
335 | |||
336 | Usage: | ||
337 | |||
338 | with runqemu('core-image-minimal') as qemu: | ||
339 | qemu.run_serial('cat /proc/cpuinfo') | ||
340 | |||
341 | Args: | ||
342 | pn (str): (image) recipe to run on | ||
343 | ssh (boolean): whether or not to enable SSH (network access) | ||
344 | runqemuparams (str): space-separated list of params to pass to 'runqemu' script (like 'nographics', 'ovmf', etc.) | ||
345 | image_fstype (str): IMAGE_FSTYPE to use | ||
346 | launch_cmd (str): directly run this command and bypass automatic runqemu parameter generation | ||
347 | overrides (dict): dict of "'<bitbake-variable>': value" pairs that allows overriding bitbake variables | ||
348 | boot_patterns (dict): dict of "'<pattern-name>': value" pairs to override default boot patterns, e.g. when not booting Linux | ||
349 | discard_writes (boolean): enables qemu -snapshot feature to prevent modifying original image | ||
313 | """ | 350 | """ |
314 | 351 | ||
315 | import bb.tinfoil | 352 | import bb.tinfoil |
@@ -340,7 +377,7 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, | |||
340 | 377 | ||
341 | logdir = recipedata.getVar("TEST_LOG_DIR") | 378 | logdir = recipedata.getVar("TEST_LOG_DIR") |
342 | 379 | ||
343 | qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype) | 380 | qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype, boot_patterns=boot_patterns) |
344 | finally: | 381 | finally: |
345 | # We need to shut down tinfoil early here in case we actually want | 382 | # We need to shut down tinfoil early here in case we actually want |
346 | # to run tinfoil-using utilities with the running QEMU instance. | 383 | # to run tinfoil-using utilities with the running QEMU instance. |