mirror of
https://git.yoctoproject.org/poky
synced 2026-01-29 21:08:42 +01:00
In the current state if a SIGTERM is sent to the testimage worker, the worker will exit but runqemu and qemu won't exit and the processes need to be killed manually to free the bitbake lock. This allows to catch the SIGTERM signal in testimage, this way it is possible to stop runqemu and qemu and allow to free the bitbake lock. Also this allows to skip the rest of the tests when running the tests in qemu or real hardware. This also solves minimal breaks in the setup of the runtime test when checking if qemu is alive. [YOCTO #8239] (From OE-Core rev: 2694d2f17d597b44fcc7aed5f6836081fa88a6b3) Signed-off-by: Benjamin Esquivel <benjamin.esquivel@linux.intel.com> Signed-off-by: Mariano Lopez <mariano.lopez@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
415 lines
16 KiB
Plaintext
415 lines
16 KiB
Plaintext
# Copyright (C) 2013 Intel Corporation
|
|
#
|
|
# Released under the MIT license (see COPYING.MIT)
|
|
|
|
|
|
# testimage.bbclass enables testing of qemu images using python unittests.
|
|
# Most of the tests are commands run on target image over ssh.
|
|
# To use it add testimage to global inherit and call your target image with -c testimage
|
|
# You can try it out like this:
|
|
# - first build a qemu core-image-sato
|
|
# - add INHERIT += "testimage" in local.conf
|
|
# - then bitbake core-image-sato -c testimage. That will run a standard suite of tests.
|
|
|
|
# You can set (or append to) TEST_SUITES in local.conf to select the tests
|
|
# which you want to run for your target.
|
|
# The test names are the module names in meta/lib/oeqa/runtime.
|
|
# Each name in TEST_SUITES represents a required test for the image. (no skipping allowed)
|
|
# 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).
|
|
# Note that order in TEST_SUITES is relevant: tests are run in an order such that
|
|
# tests mentioned in @skipUnlessPassed run before the tests that depend on them,
|
|
# but without such dependencies, tests run in the order in which they are listed
|
|
# in TEST_SUITES.
|
|
#
|
|
# A layer can add its own tests in lib/oeqa/runtime, provided it extends BBPATH as normal in its layer.conf.
|
|
|
|
# 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.
|
|
# Booting is handled by this class, and it's not a test in itself.
|
|
# TEST_QEMUBOOT_TIMEOUT can be used to set the maximum time in seconds the launch code will wait for the login prompt.
|
|
|
|
TEST_LOG_DIR ?= "${WORKDIR}/testimage"
|
|
|
|
TEST_EXPORT_DIR ?= "${TMPDIR}/testimage/${PN}"
|
|
TEST_EXPORT_ONLY ?= "0"
|
|
|
|
DEFAULT_TEST_SUITES = "ping auto"
|
|
DEFAULT_TEST_SUITES_pn-core-image-minimal = "ping"
|
|
DEFAULT_TEST_SUITES_pn-core-image-sato = "ping ssh df connman syslog xorg scp vnc date dmesg parselogs \
|
|
${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'python smart rpm', '', d)}"
|
|
DEFAULT_TEST_SUITES_pn-core-image-sato-sdk = "ping ssh df connman syslog xorg scp vnc date perl ldd gcc kernelmodule dmesg python parselogs \
|
|
${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'smart rpm', '', d)}"
|
|
DEFAULT_TEST_SUITES_pn-meta-toolchain = "auto"
|
|
|
|
# aarch64 has no graphics
|
|
DEFAULT_TEST_SUITES_remove_aarch64 = "xorg vnc"
|
|
|
|
TEST_SUITES ?= "${DEFAULT_TEST_SUITES}"
|
|
|
|
TEST_QEMUBOOT_TIMEOUT ?= "1000"
|
|
TEST_TARGET ?= "qemu"
|
|
TEST_TARGET_IP ?= ""
|
|
TEST_SERVER_IP ?= ""
|
|
|
|
TESTIMAGEDEPENDS = ""
|
|
TESTIMAGEDEPENDS_qemuall = "qemu-native:do_populate_sysroot qemu-helper-native:do_populate_sysroot"
|
|
|
|
TESTIMAGELOCK = "${TMPDIR}/testimage.lock"
|
|
TESTIMAGELOCK_qemuall = ""
|
|
|
|
TESTIMAGE_DUMP_DIR ?= "/tmp/oe-saved-tests/"
|
|
|
|
testimage_dump_target () {
|
|
top -bn1
|
|
ps
|
|
free
|
|
df
|
|
# The next command will export the default gateway IP
|
|
export DEFAULT_GATEWAY=$(ip route | awk '/default/ { print $3}')
|
|
ping -c3 $DEFAULT_GATEWAY
|
|
dmesg
|
|
netstat -an
|
|
ip address
|
|
# Next command will dump logs from /var/log/
|
|
find /var/log/ -type f 2>/dev/null -exec echo "====================" \; -exec echo {} \; -exec echo "====================" \; -exec cat {} \; -exec echo "" \;
|
|
}
|
|
|
|
testimage_dump_host () {
|
|
top -bn1
|
|
ps -ef
|
|
free
|
|
df
|
|
memstat
|
|
dmesg
|
|
netstat -an
|
|
}
|
|
|
|
python do_testimage() {
|
|
testimage_main(d)
|
|
}
|
|
addtask testimage
|
|
do_testimage[nostamp] = "1"
|
|
do_testimage[depends] += "${TESTIMAGEDEPENDS}"
|
|
do_testimage[lockfiles] += "${TESTIMAGELOCK}"
|
|
|
|
python do_testsdk() {
|
|
testsdk_main(d)
|
|
}
|
|
addtask testsdk
|
|
do_testsdk[nostamp] = "1"
|
|
do_testsdk[depends] += "${TESTIMAGEDEPENDS}"
|
|
do_testsdk[lockfiles] += "${TESTIMAGELOCK}"
|
|
|
|
# get testcase list from specified file
|
|
# if path is a relative path, then relative to build/conf/
|
|
def read_testlist(d, fpath):
|
|
if not os.path.isabs(fpath):
|
|
builddir = d.getVar("TOPDIR", True)
|
|
fpath = os.path.join(builddir, "conf", fpath)
|
|
if not os.path.exists(fpath):
|
|
bb.fatal("No such manifest file: ", fpath)
|
|
tcs = []
|
|
for line in open(fpath).readlines():
|
|
line = line.strip()
|
|
if line and not line.startswith("#"):
|
|
tcs.append(line)
|
|
return " ".join(tcs)
|
|
|
|
def get_tests_list(d, type="runtime"):
|
|
testsuites = []
|
|
testslist = []
|
|
manifests = d.getVar("TEST_SUITES_MANIFEST", True)
|
|
if manifests is not None:
|
|
manifests = manifests.split()
|
|
for manifest in manifests:
|
|
testsuites.extend(read_testlist(d, manifest).split())
|
|
else:
|
|
testsuites = d.getVar("TEST_SUITES", True).split()
|
|
if type == "sdk":
|
|
testsuites = (d.getVar("TEST_SUITES_SDK", True) or "auto").split()
|
|
bbpath = d.getVar("BBPATH", True).split(':')
|
|
|
|
# This relies on lib/ under each directory in BBPATH being added to sys.path
|
|
# (as done by default in base.bbclass)
|
|
for testname in testsuites:
|
|
if testname != "auto":
|
|
if testname.startswith("oeqa."):
|
|
testslist.append(testname)
|
|
continue
|
|
found = False
|
|
for p in bbpath:
|
|
if os.path.exists(os.path.join(p, 'lib', 'oeqa', type, testname + '.py')):
|
|
testslist.append("oeqa." + type + "." + testname)
|
|
found = True
|
|
break
|
|
if not found:
|
|
bb.fatal('Test %s specified in TEST_SUITES could not be found in lib/oeqa/runtime under BBPATH' % testname)
|
|
|
|
if "auto" in testsuites:
|
|
def add_auto_list(path):
|
|
if not os.path.exists(os.path.join(path, '__init__.py')):
|
|
bb.fatal('Tests directory %s exists but is missing __init__.py' % path)
|
|
files = sorted([f for f in os.listdir(path) if f.endswith('.py') and not f.startswith('_')])
|
|
for f in files:
|
|
module = 'oeqa.' + type + '.' + f[:-3]
|
|
if module not in testslist:
|
|
testslist.append(module)
|
|
|
|
for p in bbpath:
|
|
testpath = os.path.join(p, 'lib', 'oeqa', type)
|
|
bb.debug(2, 'Searching for tests in %s' % testpath)
|
|
if os.path.exists(testpath):
|
|
add_auto_list(testpath)
|
|
|
|
return testslist
|
|
|
|
|
|
def exportTests(d,tc):
|
|
import json
|
|
import shutil
|
|
import pkgutil
|
|
|
|
exportpath = d.getVar("TEST_EXPORT_DIR", True)
|
|
|
|
savedata = {}
|
|
savedata["d"] = {}
|
|
savedata["target"] = {}
|
|
savedata["host_dumper"] = {}
|
|
for key in tc.__dict__:
|
|
# special cases
|
|
if key != "d" and key != "target" and key != "host_dumper":
|
|
savedata[key] = getattr(tc, key)
|
|
savedata["target"]["ip"] = tc.target.ip or d.getVar("TEST_TARGET_IP", True)
|
|
savedata["target"]["server_ip"] = tc.target.server_ip or d.getVar("TEST_SERVER_IP", True)
|
|
|
|
keys = [ key for key in d.keys() if not key.startswith("_") and not key.startswith("BB") \
|
|
and not key.startswith("B_pn") and not key.startswith("do_") and not d.getVarFlag(key, "func")]
|
|
for key in keys:
|
|
try:
|
|
savedata["d"][key] = d.getVar(key, True)
|
|
except bb.data_smart.ExpansionError:
|
|
# we don't care about those anyway
|
|
pass
|
|
|
|
savedata["host_dumper"]["parent_dir"] = tc.host_dumper.parent_dir
|
|
savedata["host_dumper"]["cmds"] = tc.host_dumper.cmds
|
|
|
|
with open(os.path.join(exportpath, "testdata.json"), "w") as f:
|
|
json.dump(savedata, f, skipkeys=True, indent=4, sort_keys=True)
|
|
|
|
# now start copying files
|
|
# we'll basically copy everything under meta/lib/oeqa, with these exceptions
|
|
# - oeqa/targetcontrol.py - not needed
|
|
# - oeqa/selftest - something else
|
|
# That means:
|
|
# - all tests from oeqa/runtime defined in TEST_SUITES (including from other layers)
|
|
# - the contents of oeqa/utils and oeqa/runtime/files
|
|
# - oeqa/oetest.py and oeqa/runexport.py (this will get copied to exportpath not exportpath/oeqa)
|
|
# - __init__.py files
|
|
bb.utils.mkdirhier(os.path.join(exportpath, "oeqa/runtime/files"))
|
|
bb.utils.mkdirhier(os.path.join(exportpath, "oeqa/utils"))
|
|
# copy test modules, this should cover tests in other layers too
|
|
for t in tc.testslist:
|
|
mod = pkgutil.get_loader(t)
|
|
shutil.copy2(mod.filename, os.path.join(exportpath, "oeqa/runtime"))
|
|
# copy __init__.py files
|
|
oeqadir = pkgutil.get_loader("oeqa").filename
|
|
shutil.copy2(os.path.join(oeqadir, "__init__.py"), os.path.join(exportpath, "oeqa"))
|
|
shutil.copy2(os.path.join(oeqadir, "runtime/__init__.py"), os.path.join(exportpath, "oeqa/runtime"))
|
|
# copy oeqa/oetest.py and oeqa/runexported.py
|
|
shutil.copy2(os.path.join(oeqadir, "oetest.py"), os.path.join(exportpath, "oeqa"))
|
|
shutil.copy2(os.path.join(oeqadir, "runexported.py"), exportpath)
|
|
# copy oeqa/utils/*.py
|
|
for root, dirs, files in os.walk(os.path.join(oeqadir, "utils")):
|
|
for f in files:
|
|
if f.endswith(".py"):
|
|
shutil.copy2(os.path.join(root, f), os.path.join(exportpath, "oeqa/utils"))
|
|
# copy oeqa/runtime/files/*
|
|
for root, dirs, files in os.walk(os.path.join(oeqadir, "runtime/files")):
|
|
for f in files:
|
|
shutil.copy2(os.path.join(root, f), os.path.join(exportpath, "oeqa/runtime/files"))
|
|
|
|
bb.plain("Exported tests to: %s" % exportpath)
|
|
|
|
|
|
def testimage_main(d):
|
|
import unittest
|
|
import os
|
|
import oeqa.runtime
|
|
import time
|
|
import signal
|
|
from oeqa.oetest import loadTests, runTests
|
|
from oeqa.targetcontrol import get_target_controller
|
|
from oeqa.utils.dump import get_host_dumper
|
|
|
|
pn = d.getVar("PN", True)
|
|
export = oe.utils.conditional("TEST_EXPORT_ONLY", "1", True, False, d)
|
|
bb.utils.mkdirhier(d.getVar("TEST_LOG_DIR", True))
|
|
if export:
|
|
bb.utils.remove(d.getVar("TEST_EXPORT_DIR", True), recurse=True)
|
|
bb.utils.mkdirhier(d.getVar("TEST_EXPORT_DIR", True))
|
|
|
|
# tests in TEST_SUITES become required tests
|
|
# they won't be skipped even if they aren't suitable for a image (like xorg for minimal)
|
|
# testslist is what we'll actually pass to the unittest loader
|
|
testslist = get_tests_list(d)
|
|
testsrequired = [t for t in d.getVar("TEST_SUITES", True).split() if t != "auto"]
|
|
|
|
tagexp = d.getVar("TEST_SUITES_TAGS", True)
|
|
|
|
# we need the host dumper in test context
|
|
host_dumper = get_host_dumper(d)
|
|
|
|
# the robot dance
|
|
target = get_target_controller(d)
|
|
|
|
class TestContext(object):
|
|
def __init__(self):
|
|
self.d = d
|
|
self.testslist = testslist
|
|
self.tagexp = tagexp
|
|
self.testsrequired = testsrequired
|
|
self.filesdir = os.path.join(os.path.dirname(os.path.abspath(oeqa.runtime.__file__)),"files")
|
|
self.target = target
|
|
self.host_dumper = host_dumper
|
|
self.imagefeatures = d.getVar("IMAGE_FEATURES", True).split()
|
|
self.distrofeatures = d.getVar("DISTRO_FEATURES", True).split()
|
|
manifest = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + ".manifest")
|
|
self.sigterm = False
|
|
self.origsigtermhandler = signal.getsignal(signal.SIGTERM)
|
|
signal.signal(signal.SIGTERM, self.sigterm_exception)
|
|
try:
|
|
with open(manifest) as f:
|
|
self.pkgmanifest = f.read()
|
|
except IOError as e:
|
|
bb.fatal("No package manifest file found. Did you build the image?\n%s" % e)
|
|
|
|
def sigterm_exception(self, signum, stackframe):
|
|
bb.warn("TestImage received SIGTERM, shutting down...")
|
|
self.sigterm = True
|
|
self.target.stop()
|
|
|
|
# test context
|
|
tc = TestContext()
|
|
|
|
# this is a dummy load of tests
|
|
# we are doing that to find compile errors in the tests themselves
|
|
# before booting the image
|
|
try:
|
|
loadTests(tc)
|
|
except Exception as e:
|
|
import traceback
|
|
bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
|
|
|
|
target.deploy()
|
|
|
|
try:
|
|
target.start()
|
|
if export:
|
|
exportTests(d,tc)
|
|
else:
|
|
starttime = time.time()
|
|
result = runTests(tc)
|
|
stoptime = time.time()
|
|
if result.wasSuccessful():
|
|
bb.plain("%s - Ran %d test%s in %.3fs" % (pn, result.testsRun, result.testsRun != 1 and "s" or "", stoptime - starttime))
|
|
msg = "%s - OK - All required tests passed" % pn
|
|
skipped = len(result.skipped)
|
|
if skipped:
|
|
msg += " (skipped=%d)" % skipped
|
|
bb.plain(msg)
|
|
else:
|
|
raise bb.build.FuncFailed("%s - FAILED - check the task log and the ssh log" % pn )
|
|
finally:
|
|
signal.signal(signal.SIGTERM, tc.origsigtermhandler)
|
|
target.stop()
|
|
|
|
testimage_main[vardepsexclude] =+ "BB_ORIGENV"
|
|
|
|
|
|
def testsdk_main(d):
|
|
import unittest
|
|
import os
|
|
import glob
|
|
import oeqa.runtime
|
|
import oeqa.sdk
|
|
import time
|
|
import subprocess
|
|
from oeqa.oetest import loadTests, runTests
|
|
|
|
pn = d.getVar("PN", True)
|
|
bb.utils.mkdirhier(d.getVar("TEST_LOG_DIR", True))
|
|
|
|
# tests in TEST_SUITES become required tests
|
|
# they won't be skipped even if they aren't suitable.
|
|
# testslist is what we'll actually pass to the unittest loader
|
|
testslist = get_tests_list(d, "sdk")
|
|
testsrequired = [t for t in (d.getVar("TEST_SUITES_SDK", True) or "auto").split() if t != "auto"]
|
|
|
|
tcname = d.expand("${SDK_DEPLOY}/${TOOLCHAIN_OUTPUTNAME}.sh")
|
|
if not os.path.exists(tcname):
|
|
bb.fatal("The toolchain is not built. Build it before running the tests: 'bitbake <image> -c populate_sdk' .")
|
|
|
|
class TestContext(object):
|
|
def __init__(self):
|
|
self.d = d
|
|
self.testslist = testslist
|
|
self.testsrequired = testsrequired
|
|
self.filesdir = os.path.join(os.path.dirname(os.path.abspath(oeqa.runtime.__file__)),"files")
|
|
self.sdktestdir = sdktestdir
|
|
self.sdkenv = sdkenv
|
|
self.imagefeatures = d.getVar("IMAGE_FEATURES", True).split()
|
|
self.distrofeatures = d.getVar("DISTRO_FEATURES", True).split()
|
|
manifest = d.getVar("SDK_TARGET_MANIFEST", True)
|
|
try:
|
|
with open(manifest) as f:
|
|
self.pkgmanifest = f.read()
|
|
except IOError as e:
|
|
bb.fatal("No package manifest file found. Did you build the sdk image?\n%s" % e)
|
|
hostmanifest = d.getVar("SDK_HOST_MANIFEST", True)
|
|
try:
|
|
with open(hostmanifest) as f:
|
|
self.hostpkgmanifest = f.read()
|
|
except IOError as e:
|
|
bb.fatal("No host package manifest file found. Did you build the sdk image?\n%s" % e)
|
|
|
|
sdktestdir = d.expand("${WORKDIR}/testimage-sdk/")
|
|
bb.utils.remove(sdktestdir, True)
|
|
bb.utils.mkdirhier(sdktestdir)
|
|
subprocess.call("cd %s; %s <<EOF\n./tc\nY\nEOF" % (sdktestdir, tcname), shell=True)
|
|
|
|
try:
|
|
targets = glob.glob(d.expand(sdktestdir + "/tc/environment-setup-*"))
|
|
bb.warn(str(targets))
|
|
for sdkenv in targets:
|
|
bb.plain("Testing %s" % sdkenv)
|
|
# test context
|
|
tc = TestContext()
|
|
|
|
# this is a dummy load of tests
|
|
# we are doing that to find compile errors in the tests themselves
|
|
# before booting the image
|
|
try:
|
|
loadTests(tc, "sdk")
|
|
except Exception as e:
|
|
import traceback
|
|
bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
|
|
|
|
|
|
starttime = time.time()
|
|
result = runTests(tc, "sdk")
|
|
stoptime = time.time()
|
|
if result.wasSuccessful():
|
|
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))
|
|
msg = "%s - OK - All required tests passed" % pn
|
|
skipped = len(result.skipped)
|
|
if skipped:
|
|
msg += " (skipped=%d)" % skipped
|
|
bb.plain(msg)
|
|
else:
|
|
raise bb.build.FuncFailed("%s - FAILED - check the task log and the commands log" % pn )
|
|
finally:
|
|
bb.utils.remove(sdktestdir, True)
|
|
|
|
testsdk_main[vardepsexclude] =+ "BB_ORIGENV"
|
|
|