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