blob: 08e2cbb906c7e68eb697bcceb26c2f61e842f920 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Copyright (c) 2013-2014 Intel Corporation
2#
3# Released under the MIT license (see COPYING.MIT)
4
5# DESCRIPTION
6# This module is mainly used by scripts/oe-selftest and modules under meta/oeqa/selftest
7# It provides a class and methods for running commands on the host in a convienent way for tests.
8
9
10
11import os
12import sys
13import signal
14import subprocess
15import threading
16import logging
17from oeqa.utils import CommandError
18from oeqa.utils import ftools
19import re
20import contextlib
21
22class Command(object):
23 def __init__(self, command, bg=False, timeout=None, data=None, **options):
24
25 self.defaultopts = {
26 "stdout": subprocess.PIPE,
27 "stderr": subprocess.STDOUT,
28 "stdin": None,
29 "shell": False,
30 "bufsize": -1,
31 }
32
33 self.cmd = command
34 self.bg = bg
35 self.timeout = timeout
36 self.data = data
37
38 self.options = dict(self.defaultopts)
39 if isinstance(self.cmd, basestring):
40 self.options["shell"] = True
41 if self.data:
42 self.options['stdin'] = subprocess.PIPE
43 self.options.update(options)
44
45 self.status = None
46 self.output = None
47 self.error = None
48 self.thread = None
49
50 self.log = logging.getLogger("utils.commands")
51
52 def run(self):
53 self.process = subprocess.Popen(self.cmd, **self.options)
54
55 def commThread():
56 self.output, self.error = self.process.communicate(self.data)
57
58 self.thread = threading.Thread(target=commThread)
59 self.thread.start()
60
61 self.log.debug("Running command '%s'" % self.cmd)
62
63 if not self.bg:
64 self.thread.join(self.timeout)
65 self.stop()
66
67 def stop(self):
68 if self.thread.isAlive():
69 self.process.terminate()
70 # let's give it more time to terminate gracefully before killing it
71 self.thread.join(5)
72 if self.thread.isAlive():
73 self.process.kill()
74 self.thread.join()
75
76 self.output = self.output.rstrip()
77 self.status = self.process.poll()
78
79 self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status))
80 # logging the complete output is insane
81 # bitbake -e output is really big
82 # and makes the log file useless
83 if self.status:
84 lout = "\n".join(self.output.splitlines()[-20:])
85 self.log.debug("Last 20 lines:\n%s" % lout)
86
87
88class Result(object):
89 pass
90
91
92def runCmd(command, ignore_status=False, timeout=None, assert_error=True, **options):
93 result = Result()
94
95 cmd = Command(command, timeout=timeout, **options)
96 cmd.run()
97
98 result.command = command
99 result.status = cmd.status
100 result.output = cmd.output
101 result.pid = cmd.process.pid
102
103 if result.status and not ignore_status:
104 if assert_error:
105 raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, result.output))
106 else:
107 raise CommandError(result.status, command, result.output)
108
109 return result
110
111
112def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **options):
113
114 if postconfig:
115 postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf')
116 ftools.write_file(postconfig_file, postconfig)
117 extra_args = "-R %s" % postconfig_file
118 else:
119 extra_args = ""
120
121 if isinstance(command, basestring):
122 cmd = "bitbake " + extra_args + " " + command
123 else:
124 cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]]
125
126 try:
127 return runCmd(cmd, ignore_status, timeout, **options)
128 finally:
129 if postconfig:
130 os.remove(postconfig_file)
131
132
133def get_bb_env(target=None, postconfig=None):
134 if target:
135 return bitbake("-e %s" % target, postconfig=postconfig).output
136 else:
137 return bitbake("-e", postconfig=postconfig).output
138
139def get_bb_var(var, target=None, postconfig=None):
140 val = None
141 bbenv = get_bb_env(target, postconfig=postconfig)
142 lastline = None
143 for line in bbenv.splitlines():
144 if re.search("^(export )?%s=" % var, line):
145 val = line.split('=', 1)[1]
146 val = val.strip('\"')
147 break
148 elif re.match("unset %s$" % var, line):
149 # Handle [unexport] variables
150 if lastline.startswith('# "'):
151 val = lastline.split('\"')[1]
152 break
153 lastline = line
154 return val
155
156def get_test_layer():
157 layers = get_bb_var("BBLAYERS").split()
158 testlayer = None
159 for l in layers:
160 if '~' in l:
161 l = os.path.expanduser(l)
162 if "/meta-selftest" in l and os.path.isdir(l):
163 testlayer = l
164 break
165 return testlayer
166
167def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec='recipes-*/*'):
168 os.makedirs(os.path.join(templayerdir, 'conf'))
169 with open(os.path.join(templayerdir, 'conf', 'layer.conf'), 'w') as f:
170 f.write('BBPATH .= ":${LAYERDIR}"\n')
171 f.write('BBFILES += "${LAYERDIR}/%s/*.bb \\' % recipepathspec)
172 f.write(' ${LAYERDIR}/%s/*.bbappend"\n' % recipepathspec)
173 f.write('BBFILE_COLLECTIONS += "%s"\n' % templayername)
174 f.write('BBFILE_PATTERN_%s = "^${LAYERDIR}/"\n' % templayername)
175 f.write('BBFILE_PRIORITY_%s = "%d"\n' % (templayername, priority))
176 f.write('BBFILE_PATTERN_IGNORE_EMPTY_%s = "1"\n' % templayername)
177
178
179@contextlib.contextmanager
180def runqemu(pn, test):
181
182 import bb.tinfoil
183 import bb.build
184
185 tinfoil = bb.tinfoil.Tinfoil()
186 tinfoil.prepare(False)
187 try:
188 tinfoil.logger.setLevel(logging.WARNING)
189 import oeqa.targetcontrol
190 tinfoil.config_data.setVar("TEST_LOG_DIR", "${WORKDIR}/testimage")
191 tinfoil.config_data.setVar("TEST_QEMUBOOT_TIMEOUT", "1000")
192 import oe.recipeutils
193 recipefile = oe.recipeutils.pn_to_recipe(tinfoil.cooker, pn)
194 recipedata = oe.recipeutils.parse_recipe(recipefile, [], tinfoil.config_data)
195
196 # The QemuRunner log is saved out, but we need to ensure it is at the right
197 # log level (and then ensure that since it's a child of the BitBake logger,
198 # we disable propagation so we don't then see the log events on the console)
199 logger = logging.getLogger('BitBake.QemuRunner')
200 logger.setLevel(logging.DEBUG)
201 logger.propagate = False
202 logdir = recipedata.getVar("TEST_LOG_DIR", True)
203
204 qemu = oeqa.targetcontrol.QemuTarget(recipedata)
205 finally:
206 # We need to shut down tinfoil early here in case we actually want
207 # to run tinfoil-using utilities with the running QEMU instance.
208 # Luckily QemuTarget doesn't need it after the constructor.
209 tinfoil.shutdown()
210
211 try:
212 qemu.deploy()
213 try:
214 qemu.start()
215 except bb.build.FuncFailed:
216 raise Exception('Failed to start QEMU - see the logs in %s' % logdir)
217
218 yield qemu
219
220 finally:
221 try:
222 qemu.stop()
223 except:
224 pass