1c342db35SBrad Bishop# 2eb8dc403SDave Cobbley# Copyright (c) 2013-2014 Intel Corporation 3eb8dc403SDave Cobbley# 4c342db35SBrad Bishop# SPDX-License-Identifier: MIT 5c342db35SBrad Bishop# 6eb8dc403SDave Cobbley 7eb8dc403SDave Cobbley# DESCRIPTION 8eb8dc403SDave Cobbley# This module is mainly used by scripts/oe-selftest and modules under meta/oeqa/selftest 9eb8dc403SDave Cobbley# It provides a class and methods for running commands on the host in a convienent way for tests. 10eb8dc403SDave Cobbley 11eb8dc403SDave Cobbleyimport os 12eb8dc403SDave Cobbleyimport sys 13eb8dc403SDave Cobbleyimport subprocess 14eb8dc403SDave Cobbleyimport threading 15eb8dc403SDave Cobbleyimport time 16eb8dc403SDave Cobbleyimport logging 17eb8dc403SDave Cobbleyfrom oeqa.utils import CommandError 18eb8dc403SDave Cobbleyfrom oeqa.utils import ftools 19eb8dc403SDave Cobbleyimport re 20eb8dc403SDave Cobbleyimport contextlib 218e7b46e2SPatrick Williamsimport errno 22eb8dc403SDave Cobbley# Export test doesn't require bb 23eb8dc403SDave Cobbleytry: 24eb8dc403SDave Cobbley import bb 25eb8dc403SDave Cobbleyexcept ImportError: 26eb8dc403SDave Cobbley pass 27eb8dc403SDave Cobbley 28eb8dc403SDave Cobbleyclass Command(object): 29eb8dc403SDave Cobbley def __init__(self, command, bg=False, timeout=None, data=None, output_log=None, **options): 30eb8dc403SDave Cobbley 31eb8dc403SDave Cobbley self.defaultopts = { 32eb8dc403SDave Cobbley "stdout": subprocess.PIPE, 33eb8dc403SDave Cobbley "stderr": subprocess.STDOUT, 34eb8dc403SDave Cobbley "stdin": None, 35eb8dc403SDave Cobbley "shell": False, 36eb8dc403SDave Cobbley "bufsize": -1, 37eb8dc403SDave Cobbley } 38eb8dc403SDave Cobbley 39eb8dc403SDave Cobbley self.cmd = command 40eb8dc403SDave Cobbley self.bg = bg 41eb8dc403SDave Cobbley self.timeout = timeout 42eb8dc403SDave Cobbley self.data = data 43eb8dc403SDave Cobbley 44eb8dc403SDave Cobbley self.options = dict(self.defaultopts) 45eb8dc403SDave Cobbley if isinstance(self.cmd, str): 46eb8dc403SDave Cobbley self.options["shell"] = True 47eb8dc403SDave Cobbley if self.data: 48eb8dc403SDave Cobbley self.options['stdin'] = subprocess.PIPE 49eb8dc403SDave Cobbley self.options.update(options) 50eb8dc403SDave Cobbley 51eb8dc403SDave Cobbley self.status = None 52eb8dc403SDave Cobbley # We collect chunks of output before joining them at the end. 53eb8dc403SDave Cobbley self._output_chunks = [] 54eb8dc403SDave Cobbley self._error_chunks = [] 55eb8dc403SDave Cobbley self.output = None 56eb8dc403SDave Cobbley self.error = None 57eb8dc403SDave Cobbley self.threads = [] 58eb8dc403SDave Cobbley 59eb8dc403SDave Cobbley self.output_log = output_log 60eb8dc403SDave Cobbley self.log = logging.getLogger("utils.commands") 61eb8dc403SDave Cobbley 62eb8dc403SDave Cobbley def run(self): 63eb8dc403SDave Cobbley self.process = subprocess.Popen(self.cmd, **self.options) 64eb8dc403SDave Cobbley 65eb8dc403SDave Cobbley def readThread(output, stream, logfunc): 66eb8dc403SDave Cobbley if logfunc: 67eb8dc403SDave Cobbley for line in stream: 68eb8dc403SDave Cobbley output.append(line) 69eb8dc403SDave Cobbley logfunc(line.decode("utf-8", errors='replace').rstrip()) 70eb8dc403SDave Cobbley else: 71eb8dc403SDave Cobbley output.append(stream.read()) 72eb8dc403SDave Cobbley 73eb8dc403SDave Cobbley def readStderrThread(): 74eb8dc403SDave Cobbley readThread(self._error_chunks, self.process.stderr, self.output_log.error if self.output_log else None) 75eb8dc403SDave Cobbley 76eb8dc403SDave Cobbley def readStdoutThread(): 77eb8dc403SDave Cobbley readThread(self._output_chunks, self.process.stdout, self.output_log.info if self.output_log else None) 78eb8dc403SDave Cobbley 79eb8dc403SDave Cobbley def writeThread(): 80eb8dc403SDave Cobbley try: 81eb8dc403SDave Cobbley self.process.stdin.write(self.data) 82eb8dc403SDave Cobbley self.process.stdin.close() 83eb8dc403SDave Cobbley except OSError as ex: 84eb8dc403SDave Cobbley # It's not an error when the command does not consume all 85eb8dc403SDave Cobbley # of our data. subprocess.communicate() also ignores that. 868e7b46e2SPatrick Williams if ex.errno != errno.EPIPE: 87eb8dc403SDave Cobbley raise 88eb8dc403SDave Cobbley 89eb8dc403SDave Cobbley # We write in a separate thread because then we can read 90eb8dc403SDave Cobbley # without worrying about deadlocks. The additional thread is 91eb8dc403SDave Cobbley # expected to terminate by itself and we mark it as a daemon, 92eb8dc403SDave Cobbley # so even it should happen to not terminate for whatever 93eb8dc403SDave Cobbley # reason, the main process will still exit, which will then 94eb8dc403SDave Cobbley # kill the write thread. 95eb8dc403SDave Cobbley if self.data: 96d25ed324SAndrew Geissler thread = threading.Thread(target=writeThread, daemon=True) 97d25ed324SAndrew Geissler thread.start() 98d25ed324SAndrew Geissler self.threads.append(thread) 99eb8dc403SDave Cobbley if self.process.stderr: 100eb8dc403SDave Cobbley thread = threading.Thread(target=readStderrThread) 101eb8dc403SDave Cobbley thread.start() 102eb8dc403SDave Cobbley self.threads.append(thread) 103eb8dc403SDave Cobbley if self.output_log: 104eb8dc403SDave Cobbley self.output_log.info('Running: %s' % self.cmd) 105eb8dc403SDave Cobbley thread = threading.Thread(target=readStdoutThread) 106eb8dc403SDave Cobbley thread.start() 107eb8dc403SDave Cobbley self.threads.append(thread) 108eb8dc403SDave Cobbley 109eb8dc403SDave Cobbley self.log.debug("Running command '%s'" % self.cmd) 110eb8dc403SDave Cobbley 111eb8dc403SDave Cobbley if not self.bg: 112eb8dc403SDave Cobbley if self.timeout is None: 113eb8dc403SDave Cobbley for thread in self.threads: 114eb8dc403SDave Cobbley thread.join() 115eb8dc403SDave Cobbley else: 116eb8dc403SDave Cobbley deadline = time.time() + self.timeout 117eb8dc403SDave Cobbley for thread in self.threads: 118eb8dc403SDave Cobbley timeout = deadline - time.time() 119eb8dc403SDave Cobbley if timeout < 0: 120eb8dc403SDave Cobbley timeout = 0 121eb8dc403SDave Cobbley thread.join(timeout) 122eb8dc403SDave Cobbley self.stop() 123eb8dc403SDave Cobbley 124eb8dc403SDave Cobbley def stop(self): 125eb8dc403SDave Cobbley for thread in self.threads: 1266ce62a20SAndrew Geissler if thread.is_alive(): 127eb8dc403SDave Cobbley self.process.terminate() 128eb8dc403SDave Cobbley # let's give it more time to terminate gracefully before killing it 129eb8dc403SDave Cobbley thread.join(5) 1306ce62a20SAndrew Geissler if thread.is_alive(): 131eb8dc403SDave Cobbley self.process.kill() 132eb8dc403SDave Cobbley thread.join() 133eb8dc403SDave Cobbley 134eb8dc403SDave Cobbley def finalize_output(data): 135eb8dc403SDave Cobbley if not data: 136eb8dc403SDave Cobbley data = "" 137eb8dc403SDave Cobbley else: 138eb8dc403SDave Cobbley data = b"".join(data) 139eb8dc403SDave Cobbley data = data.decode("utf-8", errors='replace').rstrip() 140eb8dc403SDave Cobbley return data 141eb8dc403SDave Cobbley 142eb8dc403SDave Cobbley self.output = finalize_output(self._output_chunks) 143eb8dc403SDave Cobbley self._output_chunks = None 144eb8dc403SDave Cobbley # self.error used to be a byte string earlier, probably unintentionally. 145eb8dc403SDave Cobbley # Now it is a normal string, just like self.output. 146eb8dc403SDave Cobbley self.error = finalize_output(self._error_chunks) 147eb8dc403SDave Cobbley self._error_chunks = None 148eb8dc403SDave Cobbley # At this point we know that the process has closed stdout/stderr, so 149eb8dc403SDave Cobbley # it is safe and necessary to wait for the actual process completion. 150eb8dc403SDave Cobbley self.status = self.process.wait() 151f86d0556SBrad Bishop self.process.stdout.close() 152f86d0556SBrad Bishop if self.process.stderr: 153f86d0556SBrad Bishop self.process.stderr.close() 154eb8dc403SDave Cobbley 155eb8dc403SDave Cobbley self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status)) 156eb8dc403SDave Cobbley # logging the complete output is insane 157eb8dc403SDave Cobbley # bitbake -e output is really big 158eb8dc403SDave Cobbley # and makes the log file useless 159eb8dc403SDave Cobbley if self.status: 160eb8dc403SDave Cobbley lout = "\n".join(self.output.splitlines()[-20:]) 161eb8dc403SDave Cobbley self.log.debug("Last 20 lines:\n%s" % lout) 162eb8dc403SDave Cobbley 163eb8dc403SDave Cobbley 164eb8dc403SDave Cobbleyclass Result(object): 165eb8dc403SDave Cobbley pass 166eb8dc403SDave Cobbley 167eb8dc403SDave Cobbley 1684c19ea12SAndrew Geisslerdef runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=True, 16992b42cb3SPatrick Williams native_sysroot=None, target_sys=None, limit_exc_output=0, output_log=None, **options): 170eb8dc403SDave Cobbley result = Result() 171eb8dc403SDave Cobbley 172eb8dc403SDave Cobbley if native_sysroot: 17392b42cb3SPatrick Williams new_env = dict(options.get('env', os.environ)) 17492b42cb3SPatrick Williams paths = new_env["PATH"].split(":") 17592b42cb3SPatrick Williams paths = [ 17692b42cb3SPatrick Williams os.path.join(native_sysroot, "bin"), 17792b42cb3SPatrick Williams os.path.join(native_sysroot, "sbin"), 17892b42cb3SPatrick Williams os.path.join(native_sysroot, "usr", "bin"), 17992b42cb3SPatrick Williams os.path.join(native_sysroot, "usr", "sbin"), 18092b42cb3SPatrick Williams ] + paths 18192b42cb3SPatrick Williams if target_sys: 18292b42cb3SPatrick Williams paths = [os.path.join(native_sysroot, "usr", "bin", target_sys)] + paths 18392b42cb3SPatrick Williams new_env["PATH"] = ":".join(paths) 18492b42cb3SPatrick Williams options['env'] = new_env 185eb8dc403SDave Cobbley 186eb8dc403SDave Cobbley cmd = Command(command, timeout=timeout, output_log=output_log, **options) 187eb8dc403SDave Cobbley cmd.run() 188eb8dc403SDave Cobbley 1894c19ea12SAndrew Geissler # tests can be heavy on IO and if bitbake can't write out its caches, we see timeouts. 1904c19ea12SAndrew Geissler # call sync around the tests to ensure the IO queue doesn't get too large, taking any IO 1914c19ea12SAndrew Geissler # hit here rather than in bitbake shutdown. 1924c19ea12SAndrew Geissler if sync: 193d1e89497SAndrew Geissler p = os.environ['PATH'] 194d1e89497SAndrew Geissler os.environ['PATH'] = "/usr/bin:/bin:/usr/sbin:/sbin:" + p 1954c19ea12SAndrew Geissler os.system("sync") 196d1e89497SAndrew Geissler os.environ['PATH'] = p 1974c19ea12SAndrew Geissler 198eb8dc403SDave Cobbley result.command = command 199eb8dc403SDave Cobbley result.status = cmd.status 200eb8dc403SDave Cobbley result.output = cmd.output 201eb8dc403SDave Cobbley result.error = cmd.error 202eb8dc403SDave Cobbley result.pid = cmd.process.pid 203eb8dc403SDave Cobbley 204eb8dc403SDave Cobbley if result.status and not ignore_status: 205eb8dc403SDave Cobbley exc_output = result.output 206eb8dc403SDave Cobbley if limit_exc_output > 0: 207eb8dc403SDave Cobbley split = result.output.splitlines() 208eb8dc403SDave Cobbley if len(split) > limit_exc_output: 209eb8dc403SDave Cobbley exc_output = "\n... (last %d lines of output)\n" % limit_exc_output + \ 210eb8dc403SDave Cobbley '\n'.join(split[-limit_exc_output:]) 211eb8dc403SDave Cobbley if assert_error: 212eb8dc403SDave Cobbley raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, exc_output)) 213eb8dc403SDave Cobbley else: 214eb8dc403SDave Cobbley raise CommandError(result.status, command, exc_output) 215eb8dc403SDave Cobbley 216eb8dc403SDave Cobbley return result 217eb8dc403SDave Cobbley 218eb8dc403SDave Cobbley 219eb8dc403SDave Cobbleydef bitbake(command, ignore_status=False, timeout=None, postconfig=None, output_log=None, **options): 220eb8dc403SDave Cobbley 221eb8dc403SDave Cobbley if postconfig: 222eb8dc403SDave Cobbley postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf') 223eb8dc403SDave Cobbley ftools.write_file(postconfig_file, postconfig) 224eb8dc403SDave Cobbley extra_args = "-R %s" % postconfig_file 225eb8dc403SDave Cobbley else: 226eb8dc403SDave Cobbley extra_args = "" 227eb8dc403SDave Cobbley 228eb8dc403SDave Cobbley if isinstance(command, str): 229eb8dc403SDave Cobbley cmd = "bitbake " + extra_args + " " + command 230eb8dc403SDave Cobbley else: 231eb8dc403SDave Cobbley cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]] 232eb8dc403SDave Cobbley 233eb8dc403SDave Cobbley try: 234eb8dc403SDave Cobbley return runCmd(cmd, ignore_status, timeout, output_log=output_log, **options) 235eb8dc403SDave Cobbley finally: 236eb8dc403SDave Cobbley if postconfig: 237eb8dc403SDave Cobbley os.remove(postconfig_file) 238eb8dc403SDave Cobbley 239eb8dc403SDave Cobbley 240eb8dc403SDave Cobbleydef get_bb_env(target=None, postconfig=None): 241eb8dc403SDave Cobbley if target: 242eb8dc403SDave Cobbley return bitbake("-e %s" % target, postconfig=postconfig).output 243eb8dc403SDave Cobbley else: 244eb8dc403SDave Cobbley return bitbake("-e", postconfig=postconfig).output 245eb8dc403SDave Cobbley 246eb8dc403SDave Cobbleydef get_bb_vars(variables=None, target=None, postconfig=None): 247eb8dc403SDave Cobbley """Get values of multiple bitbake variables""" 248eb8dc403SDave Cobbley bbenv = get_bb_env(target, postconfig=postconfig) 249eb8dc403SDave Cobbley 250eb8dc403SDave Cobbley if variables is not None: 251eb8dc403SDave Cobbley variables = list(variables) 252eb8dc403SDave Cobbley var_re = re.compile(r'^(export )?(?P<var>\w+(_.*)?)="(?P<value>.*)"$') 253eb8dc403SDave Cobbley unset_re = re.compile(r'^unset (?P<var>\w+)$') 254eb8dc403SDave Cobbley lastline = None 255eb8dc403SDave Cobbley values = {} 256eb8dc403SDave Cobbley for line in bbenv.splitlines(): 257eb8dc403SDave Cobbley match = var_re.match(line) 258eb8dc403SDave Cobbley val = None 259eb8dc403SDave Cobbley if match: 260eb8dc403SDave Cobbley val = match.group('value') 261eb8dc403SDave Cobbley else: 262eb8dc403SDave Cobbley match = unset_re.match(line) 263eb8dc403SDave Cobbley if match: 264eb8dc403SDave Cobbley # Handle [unexport] variables 265eb8dc403SDave Cobbley if lastline.startswith('# "'): 266eb8dc403SDave Cobbley val = lastline.split('"')[1] 267eb8dc403SDave Cobbley if val: 268eb8dc403SDave Cobbley var = match.group('var') 269eb8dc403SDave Cobbley if variables is None: 270eb8dc403SDave Cobbley values[var] = val 271eb8dc403SDave Cobbley else: 272eb8dc403SDave Cobbley if var in variables: 273eb8dc403SDave Cobbley values[var] = val 274eb8dc403SDave Cobbley variables.remove(var) 275eb8dc403SDave Cobbley # Stop after all required variables have been found 276eb8dc403SDave Cobbley if not variables: 277eb8dc403SDave Cobbley break 278eb8dc403SDave Cobbley lastline = line 279eb8dc403SDave Cobbley if variables: 280eb8dc403SDave Cobbley # Fill in missing values 281eb8dc403SDave Cobbley for var in variables: 282eb8dc403SDave Cobbley values[var] = None 283eb8dc403SDave Cobbley return values 284eb8dc403SDave Cobbley 285eb8dc403SDave Cobbleydef get_bb_var(var, target=None, postconfig=None): 286eb8dc403SDave Cobbley return get_bb_vars([var], target, postconfig)[var] 287eb8dc403SDave Cobbley 288*220dafdbSAndrew Geisslerdef get_test_layer(bblayers=None): 289*220dafdbSAndrew Geissler if bblayers is None: 290*220dafdbSAndrew Geissler bblayers = get_bb_var("BBLAYERS") 291*220dafdbSAndrew Geissler layers = bblayers.split() 292eb8dc403SDave Cobbley testlayer = None 293eb8dc403SDave Cobbley for l in layers: 294eb8dc403SDave Cobbley if '~' in l: 295eb8dc403SDave Cobbley l = os.path.expanduser(l) 296eb8dc403SDave Cobbley if "/meta-selftest" in l and os.path.isdir(l): 297eb8dc403SDave Cobbley testlayer = l 298eb8dc403SDave Cobbley break 299eb8dc403SDave Cobbley return testlayer 300eb8dc403SDave Cobbley 301eb8dc403SDave Cobbleydef create_temp_layer(templayerdir, templayername, priority=999, recipepathspec='recipes-*/*'): 302eb8dc403SDave Cobbley os.makedirs(os.path.join(templayerdir, 'conf')) 303517393d9SAndrew Geissler corenames = get_bb_var('LAYERSERIES_CORENAMES') 304eb8dc403SDave Cobbley with open(os.path.join(templayerdir, 'conf', 'layer.conf'), 'w') as f: 305eb8dc403SDave Cobbley f.write('BBPATH .= ":${LAYERDIR}"\n') 306eb8dc403SDave Cobbley f.write('BBFILES += "${LAYERDIR}/%s/*.bb \\' % recipepathspec) 307eb8dc403SDave Cobbley f.write(' ${LAYERDIR}/%s/*.bbappend"\n' % recipepathspec) 308eb8dc403SDave Cobbley f.write('BBFILE_COLLECTIONS += "%s"\n' % templayername) 309eb8dc403SDave Cobbley f.write('BBFILE_PATTERN_%s = "^${LAYERDIR}/"\n' % templayername) 310eb8dc403SDave Cobbley f.write('BBFILE_PRIORITY_%s = "%d"\n' % (templayername, priority)) 311eb8dc403SDave Cobbley f.write('BBFILE_PATTERN_IGNORE_EMPTY_%s = "1"\n' % templayername) 312517393d9SAndrew Geissler f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames)) 313eb8dc403SDave Cobbley 314eb8dc403SDave Cobbley@contextlib.contextmanager 315eb8dc403SDave Cobbleydef runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, discard_writes=True): 316eb8dc403SDave Cobbley """ 317eb8dc403SDave Cobbley launch_cmd means directly run the command, don't need set rootfs or env vars. 318eb8dc403SDave Cobbley """ 319eb8dc403SDave Cobbley 320eb8dc403SDave Cobbley import bb.tinfoil 321eb8dc403SDave Cobbley import bb.build 322eb8dc403SDave Cobbley 323eb8dc403SDave Cobbley # Need a non-'BitBake' logger to capture the runner output 324eb8dc403SDave Cobbley targetlogger = logging.getLogger('TargetRunner') 325eb8dc403SDave Cobbley targetlogger.setLevel(logging.DEBUG) 326eb8dc403SDave Cobbley handler = logging.StreamHandler(sys.stdout) 327eb8dc403SDave Cobbley targetlogger.addHandler(handler) 328eb8dc403SDave Cobbley 329eb8dc403SDave Cobbley tinfoil = bb.tinfoil.Tinfoil() 330eb8dc403SDave Cobbley tinfoil.prepare(config_only=False, quiet=True) 331eb8dc403SDave Cobbley try: 332eb8dc403SDave Cobbley tinfoil.logger.setLevel(logging.WARNING) 333eb8dc403SDave Cobbley import oeqa.targetcontrol 33482c905dcSAndrew Geissler recipedata = tinfoil.parse_recipe(pn) 33582c905dcSAndrew Geissler recipedata.setVar("TEST_LOG_DIR", "${WORKDIR}/testimage") 33682c905dcSAndrew Geissler recipedata.setVar("TEST_QEMUBOOT_TIMEOUT", "1000") 337eb8dc403SDave Cobbley # Tell QemuTarget() whether need find rootfs/kernel or not 338eb8dc403SDave Cobbley if launch_cmd: 33982c905dcSAndrew Geissler recipedata.setVar("FIND_ROOTFS", '0') 340eb8dc403SDave Cobbley else: 34182c905dcSAndrew Geissler recipedata.setVar("FIND_ROOTFS", '1') 342eb8dc403SDave Cobbley 343eb8dc403SDave Cobbley for key, value in overrides.items(): 344eb8dc403SDave Cobbley recipedata.setVar(key, value) 345eb8dc403SDave Cobbley 346eb8dc403SDave Cobbley logdir = recipedata.getVar("TEST_LOG_DIR") 347eb8dc403SDave Cobbley 348eb8dc403SDave Cobbley qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype) 349eb8dc403SDave Cobbley finally: 350eb8dc403SDave Cobbley # We need to shut down tinfoil early here in case we actually want 351eb8dc403SDave Cobbley # to run tinfoil-using utilities with the running QEMU instance. 352eb8dc403SDave Cobbley # Luckily QemuTarget doesn't need it after the constructor. 353eb8dc403SDave Cobbley tinfoil.shutdown() 354eb8dc403SDave Cobbley 355eb8dc403SDave Cobbley try: 356eb8dc403SDave Cobbley qemu.deploy() 357eb8dc403SDave Cobbley try: 358eb8dc403SDave Cobbley qemu.start(params=qemuparams, ssh=ssh, runqemuparams=runqemuparams, launch_cmd=launch_cmd, discard_writes=discard_writes) 35979641f25SBrad Bishop except Exception as e: 36008902b01SBrad Bishop msg = str(e) + '\nFailed to start QEMU - see the logs in %s' % logdir 361f86d0556SBrad Bishop if os.path.exists(qemu.qemurunnerlog): 362f86d0556SBrad Bishop with open(qemu.qemurunnerlog, 'r') as f: 363f86d0556SBrad Bishop msg = msg + "Qemurunner log output from %s:\n%s" % (qemu.qemurunnerlog, f.read()) 364f86d0556SBrad Bishop raise Exception(msg) 365eb8dc403SDave Cobbley 366eb8dc403SDave Cobbley yield qemu 367eb8dc403SDave Cobbley 368eb8dc403SDave Cobbley finally: 369f86d0556SBrad Bishop targetlogger.removeHandler(handler) 370eb8dc403SDave Cobbley qemu.stop() 371eb8dc403SDave Cobbley 372eb8dc403SDave Cobbleydef updateEnv(env_file): 373eb8dc403SDave Cobbley """ 374eb8dc403SDave Cobbley Source a file and update environment. 375eb8dc403SDave Cobbley """ 376eb8dc403SDave Cobbley 377eb8dc403SDave Cobbley cmd = ". %s; env -0" % env_file 378eb8dc403SDave Cobbley result = runCmd(cmd) 379eb8dc403SDave Cobbley 380eb8dc403SDave Cobbley for line in result.output.split("\0"): 381eb8dc403SDave Cobbley (key, _, value) = line.partition("=") 382eb8dc403SDave Cobbley os.environ[key] = value 383