blob: a1918ba9ecd742b74f5d48a65a89e1daf2034be0 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Copyright (C) 2013 Intel Corporation
2#
3# Released under the MIT license (see COPYING.MIT)
4
5
6# testimage.bbclass enables testing of qemu images using python unittests.
7# Most of the tests are commands run on target image over ssh.
8# To use it add testimage to global inherit and call your target image with -c testimage
9# You can try it out like this:
10# - first build a qemu core-image-sato
11# - add INHERIT += "testimage" in local.conf
12# - then bitbake core-image-sato -c testimage. That will run a standard suite of tests.
13
14# You can set (or append to) TEST_SUITES in local.conf to select the tests
15# which you want to run for your target.
16# The test names are the module names in meta/lib/oeqa/runtime.
17# Each name in TEST_SUITES represents a required test for the image. (no skipping allowed)
18# Appending "auto" means that it will try to run all tests that are suitable for the image (each test decides that on it's own).
19# Note that order in TEST_SUITES is relevant: tests are run in an order such that
20# tests mentioned in @skipUnlessPassed run before the tests that depend on them,
21# but without such dependencies, tests run in the order in which they are listed
22# in TEST_SUITES.
23#
24# A layer can add its own tests in lib/oeqa/runtime, provided it extends BBPATH as normal in its layer.conf.
25
26# TEST_LOG_DIR contains a command ssh log and may contain infromation about what command is running, output and return codes and for qemu a boot log till login.
27# Booting is handled by this class, and it's not a test in itself.
28# TEST_QEMUBOOT_TIMEOUT can be used to set the maximum time in seconds the launch code will wait for the login prompt.
29
30TEST_LOG_DIR ?= "${WORKDIR}/testimage"
31
32TEST_EXPORT_DIR ?= "${TMPDIR}/testimage/${PN}"
33TEST_EXPORT_ONLY ?= "0"
34
35RPMTESTSUITE = "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'smart rpm', '', d)}"
36
37DEFAULT_TEST_SUITES = "ping auto"
38DEFAULT_TEST_SUITES_pn-core-image-minimal = "ping"
39DEFAULT_TEST_SUITES_pn-core-image-sato = "ping ssh df connman syslog xorg scp vnc date dmesg parselogs ${RPMTESTSUITE} \
40 ${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'python', '', d)}"
41DEFAULT_TEST_SUITES_pn-core-image-sato-sdk = "ping ssh df connman syslog xorg scp vnc date perl ldd gcc kernelmodule dmesg python parselogs ${RPMTESTSUITE}"
42DEFAULT_TEST_SUITES_pn-core-image-lsb-sdk = "ping buildcvs buildiptables buildsudoku connman date df gcc kernelmodule ldd pam parselogs perl python scp ${RPMTESTSUITE} ssh syslog logrotate"
43DEFAULT_TEST_SUITES_pn-meta-toolchain = "auto"
44
45# aarch64 has no graphics
46DEFAULT_TEST_SUITES_remove_aarch64 = "xorg vnc"
47
48#qemumips is too slow for buildsudoku
49DEFAULT_TEST_SUITES_remove_qemumips = "buildsudoku"
50
51TEST_SUITES ?= "${DEFAULT_TEST_SUITES}"
52
53TEST_QEMUBOOT_TIMEOUT ?= "1000"
54TEST_TARGET ?= "qemu"
55TEST_TARGET_IP ?= ""
56TEST_SERVER_IP ?= ""
57
58TESTIMAGEDEPENDS = ""
59TESTIMAGEDEPENDS_qemuall = "qemu-native:do_populate_sysroot qemu-helper-native:do_populate_sysroot"
60
61TESTIMAGELOCK = "${TMPDIR}/testimage.lock"
62TESTIMAGELOCK_qemuall = ""
63
64TESTIMAGE_DUMP_DIR ?= "/tmp/oe-saved-tests/"
65
66testimage_dump_target () {
67 top -bn1
68 ps
69 free
70 df
71 # The next command will export the default gateway IP
72 export DEFAULT_GATEWAY=$(ip route | awk '/default/ { print $3}')
73 ping -c3 $DEFAULT_GATEWAY
74 dmesg
75 netstat -an
76 ip address
77 # Next command will dump logs from /var/log/
78 find /var/log/ -type f 2>/dev/null -exec echo "====================" \; -exec echo {} \; -exec echo "====================" \; -exec cat {} \; -exec echo "" \;
79}
80
81testimage_dump_host () {
82 top -bn1
83 ps -ef
84 free
85 df
86 memstat
87 dmesg
88 netstat -an
89}
90
91python do_testimage() {
92 testimage_main(d)
93}
94addtask testimage
95do_testimage[nostamp] = "1"
96do_testimage[depends] += "${TESTIMAGEDEPENDS}"
97do_testimage[lockfiles] += "${TESTIMAGELOCK}"
98
99python do_testsdk() {
100 testsdk_main(d)
101}
102addtask testsdk
103do_testsdk[nostamp] = "1"
104do_testsdk[depends] += "${TESTIMAGEDEPENDS}"
105do_testsdk[lockfiles] += "${TESTIMAGELOCK}"
106
107# get testcase list from specified file
108# if path is a relative path, then relative to build/conf/
109def read_testlist(d, fpath):
110 if not os.path.isabs(fpath):
111 builddir = d.getVar("TOPDIR", True)
112 fpath = os.path.join(builddir, "conf", fpath)
113 if not os.path.exists(fpath):
114 bb.fatal("No such manifest file: ", fpath)
115 tcs = []
116 for line in open(fpath).readlines():
117 line = line.strip()
118 if line and not line.startswith("#"):
119 tcs.append(line)
120 return " ".join(tcs)
121
122def get_tests_list(d, type="runtime"):
123 testsuites = []
124 testslist = []
125 manifests = d.getVar("TEST_SUITES_MANIFEST", True)
126 if manifests is not None:
127 manifests = manifests.split()
128 for manifest in manifests:
129 testsuites.extend(read_testlist(d, manifest).split())
130 else:
131 testsuites = d.getVar("TEST_SUITES", True).split()
132 if type == "sdk":
133 testsuites = (d.getVar("TEST_SUITES_SDK", True) or "auto").split()
134 bbpath = d.getVar("BBPATH", True).split(':')
135
136 # This relies on lib/ under each directory in BBPATH being added to sys.path
137 # (as done by default in base.bbclass)
138 for testname in testsuites:
139 if testname != "auto":
140 if testname.startswith("oeqa."):
141 testslist.append(testname)
142 continue
143 found = False
144 for p in bbpath:
145 if os.path.exists(os.path.join(p, 'lib', 'oeqa', type, testname + '.py')):
146 testslist.append("oeqa." + type + "." + testname)
147 found = True
148 break
149 if not found:
150 bb.fatal('Test %s specified in TEST_SUITES could not be found in lib/oeqa/runtime under BBPATH' % testname)
151
152 if "auto" in testsuites:
153 def add_auto_list(path):
154 if not os.path.exists(os.path.join(path, '__init__.py')):
155 bb.fatal('Tests directory %s exists but is missing __init__.py' % path)
156 files = sorted([f for f in os.listdir(path) if f.endswith('.py') and not f.startswith('_')])
157 for f in files:
158 module = 'oeqa.' + type + '.' + f[:-3]
159 if module not in testslist:
160 testslist.append(module)
161
162 for p in bbpath:
163 testpath = os.path.join(p, 'lib', 'oeqa', type)
164 bb.debug(2, 'Searching for tests in %s' % testpath)
165 if os.path.exists(testpath):
166 add_auto_list(testpath)
167
168 return testslist
169
170
171def exportTests(d,tc):
172 import json
173 import shutil
174 import pkgutil
175
176 exportpath = d.getVar("TEST_EXPORT_DIR", True)
177
178 savedata = {}
179 savedata["d"] = {}
180 savedata["target"] = {}
181 savedata["host_dumper"] = {}
182 for key in tc.__dict__:
183 # special cases
184 if key != "d" and key != "target" and key != "host_dumper":
185 savedata[key] = getattr(tc, key)
186 savedata["target"]["ip"] = tc.target.ip or d.getVar("TEST_TARGET_IP", True)
187 savedata["target"]["server_ip"] = tc.target.server_ip or d.getVar("TEST_SERVER_IP", True)
188
189 keys = [ key for key in d.keys() if not key.startswith("_") and not key.startswith("BB") \
190 and not key.startswith("B_pn") and not key.startswith("do_") and not d.getVarFlag(key, "func")]
191 for key in keys:
192 try:
193 savedata["d"][key] = d.getVar(key, True)
194 except bb.data_smart.ExpansionError:
195 # we don't care about those anyway
196 pass
197
198 savedata["host_dumper"]["parent_dir"] = tc.host_dumper.parent_dir
199 savedata["host_dumper"]["cmds"] = tc.host_dumper.cmds
200
201 with open(os.path.join(exportpath, "testdata.json"), "w") as f:
202 json.dump(savedata, f, skipkeys=True, indent=4, sort_keys=True)
203
204 # now start copying files
205 # we'll basically copy everything under meta/lib/oeqa, with these exceptions
206 # - oeqa/targetcontrol.py - not needed
207 # - oeqa/selftest - something else
208 # That means:
209 # - all tests from oeqa/runtime defined in TEST_SUITES (including from other layers)
210 # - the contents of oeqa/utils and oeqa/runtime/files
211 # - oeqa/oetest.py and oeqa/runexport.py (this will get copied to exportpath not exportpath/oeqa)
212 # - __init__.py files
213 bb.utils.mkdirhier(os.path.join(exportpath, "oeqa/runtime/files"))
214 bb.utils.mkdirhier(os.path.join(exportpath, "oeqa/utils"))
215 # copy test modules, this should cover tests in other layers too
216 for t in tc.testslist:
217 mod = pkgutil.get_loader(t)
218 shutil.copy2(mod.filename, os.path.join(exportpath, "oeqa/runtime"))
219 # copy __init__.py files
220 oeqadir = pkgutil.get_loader("oeqa").filename
221 shutil.copy2(os.path.join(oeqadir, "__init__.py"), os.path.join(exportpath, "oeqa"))
222 shutil.copy2(os.path.join(oeqadir, "runtime/__init__.py"), os.path.join(exportpath, "oeqa/runtime"))
223 # copy oeqa/oetest.py and oeqa/runexported.py
224 shutil.copy2(os.path.join(oeqadir, "oetest.py"), os.path.join(exportpath, "oeqa"))
225 shutil.copy2(os.path.join(oeqadir, "runexported.py"), exportpath)
226 # copy oeqa/utils/*.py
227 for root, dirs, files in os.walk(os.path.join(oeqadir, "utils")):
228 for f in files:
229 if f.endswith(".py"):
230 shutil.copy2(os.path.join(root, f), os.path.join(exportpath, "oeqa/utils"))
231 # copy oeqa/runtime/files/*
232 for root, dirs, files in os.walk(os.path.join(oeqadir, "runtime/files")):
233 for f in files:
234 shutil.copy2(os.path.join(root, f), os.path.join(exportpath, "oeqa/runtime/files"))
235
236 bb.plain("Exported tests to: %s" % exportpath)
237
238
239def testimage_main(d):
240 import unittest
241 import os
242 import oeqa.runtime
243 import time
244 import signal
245 from oeqa.oetest import loadTests, runTests
246 from oeqa.targetcontrol import get_target_controller
247 from oeqa.utils.dump import get_host_dumper
248
249 pn = d.getVar("PN", True)
250 export = oe.utils.conditional("TEST_EXPORT_ONLY", "1", True, False, d)
251 bb.utils.mkdirhier(d.getVar("TEST_LOG_DIR", True))
252 if export:
253 bb.utils.remove(d.getVar("TEST_EXPORT_DIR", True), recurse=True)
254 bb.utils.mkdirhier(d.getVar("TEST_EXPORT_DIR", True))
255
256 # tests in TEST_SUITES become required tests
257 # they won't be skipped even if they aren't suitable for a image (like xorg for minimal)
258 # testslist is what we'll actually pass to the unittest loader
259 testslist = get_tests_list(d)
260 testsrequired = [t for t in d.getVar("TEST_SUITES", True).split() if t != "auto"]
261
262 tagexp = d.getVar("TEST_SUITES_TAGS", True)
263
264 # we need the host dumper in test context
265 host_dumper = get_host_dumper(d)
266
267 # the robot dance
268 target = get_target_controller(d)
269
270 class TestContext(object):
271 def __init__(self):
272 self.d = d
273 self.testslist = testslist
274 self.tagexp = tagexp
275 self.testsrequired = testsrequired
276 self.filesdir = os.path.join(os.path.dirname(os.path.abspath(oeqa.runtime.__file__)),"files")
277 self.target = target
278 self.host_dumper = host_dumper
279 self.imagefeatures = d.getVar("IMAGE_FEATURES", True).split()
280 self.distrofeatures = d.getVar("DISTRO_FEATURES", True).split()
281 manifest = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + ".manifest")
282 self.sigterm = False
283 self.origsigtermhandler = signal.getsignal(signal.SIGTERM)
284 signal.signal(signal.SIGTERM, self.sigterm_exception)
285 try:
286 with open(manifest) as f:
287 self.pkgmanifest = f.read()
288 except IOError as e:
289 bb.fatal("No package manifest file found. Did you build the image?\n%s" % e)
290
291 def sigterm_exception(self, signum, stackframe):
292 bb.warn("TestImage received SIGTERM, shutting down...")
293 self.sigterm = True
294 self.target.stop()
295
296 # test context
297 tc = TestContext()
298
299 # this is a dummy load of tests
300 # we are doing that to find compile errors in the tests themselves
301 # before booting the image
302 try:
303 loadTests(tc)
304 except Exception as e:
305 import traceback
306 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
307
308 target.deploy()
309
310 try:
311 target.start()
312 if export:
313 exportTests(d,tc)
314 else:
315 starttime = time.time()
316 result = runTests(tc)
317 stoptime = time.time()
318 if result.wasSuccessful():
319 bb.plain("%s - Ran %d test%s in %.3fs" % (pn, result.testsRun, result.testsRun != 1 and "s" or "", stoptime - starttime))
320 msg = "%s - OK - All required tests passed" % pn
321 skipped = len(result.skipped)
322 if skipped:
323 msg += " (skipped=%d)" % skipped
324 bb.plain(msg)
325 else:
326 raise bb.build.FuncFailed("%s - FAILED - check the task log and the ssh log" % pn )
327 finally:
328 signal.signal(signal.SIGTERM, tc.origsigtermhandler)
329 target.stop()
330
331testimage_main[vardepsexclude] =+ "BB_ORIGENV"
332
333
334def testsdk_main(d):
335 import unittest
336 import os
337 import glob
338 import oeqa.runtime
339 import oeqa.sdk
340 import time
341 import subprocess
342 from oeqa.oetest import loadTests, runTests
343
344 pn = d.getVar("PN", True)
345 bb.utils.mkdirhier(d.getVar("TEST_LOG_DIR", True))
346
347 # tests in TEST_SUITES become required tests
348 # they won't be skipped even if they aren't suitable.
349 # testslist is what we'll actually pass to the unittest loader
350 testslist = get_tests_list(d, "sdk")
351 testsrequired = [t for t in (d.getVar("TEST_SUITES_SDK", True) or "auto").split() if t != "auto"]
352
353 tcname = d.expand("${SDK_DEPLOY}/${TOOLCHAIN_OUTPUTNAME}.sh")
354 if not os.path.exists(tcname):
355 bb.fatal("The toolchain is not built. Build it before running the tests: 'bitbake <image> -c populate_sdk' .")
356
357 class TestContext(object):
358 def __init__(self):
359 self.d = d
360 self.testslist = testslist
361 self.testsrequired = testsrequired
362 self.filesdir = os.path.join(os.path.dirname(os.path.abspath(oeqa.runtime.__file__)),"files")
363 self.sdktestdir = sdktestdir
364 self.sdkenv = sdkenv
365 self.imagefeatures = d.getVar("IMAGE_FEATURES", True).split()
366 self.distrofeatures = d.getVar("DISTRO_FEATURES", True).split()
367 manifest = d.getVar("SDK_TARGET_MANIFEST", True)
368 try:
369 with open(manifest) as f:
370 self.pkgmanifest = f.read()
371 except IOError as e:
372 bb.fatal("No package manifest file found. Did you build the sdk image?\n%s" % e)
373 hostmanifest = d.getVar("SDK_HOST_MANIFEST", True)
374 try:
375 with open(hostmanifest) as f:
376 self.hostpkgmanifest = f.read()
377 except IOError as e:
378 bb.fatal("No host package manifest file found. Did you build the sdk image?\n%s" % e)
379
380 sdktestdir = d.expand("${WORKDIR}/testimage-sdk/")
381 bb.utils.remove(sdktestdir, True)
382 bb.utils.mkdirhier(sdktestdir)
383 try:
384 subprocess.check_output("cd %s; %s <<EOF\n./tc\nY\nEOF" % (sdktestdir, tcname), shell=True)
385 except subprocess.CalledProcessError as e:
386 bb.fatal("Couldn't install the SDK:\n%s" % e.output)
387
388 try:
389 targets = glob.glob(d.expand(sdktestdir + "/tc/environment-setup-*"))
390 bb.warn(str(targets))
391 for sdkenv in targets:
392 bb.plain("Testing %s" % sdkenv)
393 # test context
394 tc = TestContext()
395
396 # this is a dummy load of tests
397 # we are doing that to find compile errors in the tests themselves
398 # before booting the image
399 try:
400 loadTests(tc, "sdk")
401 except Exception as e:
402 import traceback
403 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
404
405
406 starttime = time.time()
407 result = runTests(tc, "sdk")
408 stoptime = time.time()
409 if result.wasSuccessful():
410 bb.plain("%s SDK(%s):%s - Ran %d test%s in %.3fs" % (pn, os.path.basename(tcname), os.path.basename(sdkenv),result.testsRun, result.testsRun != 1 and "s" or "", stoptime - starttime))
411 msg = "%s - OK - All required tests passed" % pn
412 skipped = len(result.skipped)
413 if skipped:
414 msg += " (skipped=%d)" % skipped
415 bb.plain(msg)
416 else:
417 raise bb.build.FuncFailed("%s - FAILED - check the task log and the commands log" % pn )
418 finally:
419 bb.utils.remove(sdktestdir, True)
420
421testsdk_main[vardepsexclude] =+ "BB_ORIGENV"
422