testimage.bbclass 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. # Copyright (C) 2013 Intel Corporation
  2. #
  3. # Released under the MIT license (see COPYING.MIT)
  4. # testimage.bbclass enables testing of qemu images using python unittests.
  5. # Most of the tests are commands run on target image over ssh.
  6. # To use it add testimage to global inherit and call your target image with -c testimage
  7. # You can try it out like this:
  8. # - first add IMAGE_CLASSES += "testimage" in local.conf
  9. # - build a qemu core-image-sato
  10. # - then bitbake core-image-sato -c testimage. That will run a standard suite of tests.
  11. #
  12. # The tests can be run automatically each time an image is built if you set
  13. # TESTIMAGE_AUTO = "1"
  14. TESTIMAGE_AUTO ??= "0"
  15. # You can set (or append to) TEST_SUITES in local.conf to select the tests
  16. # which you want to run for your target.
  17. # The test names are the module names in meta/lib/oeqa/runtime/cases.
  18. # Each name in TEST_SUITES represents a required test for the image. (no skipping allowed)
  19. # 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).
  20. # Note that order in TEST_SUITES is relevant: tests are run in an order such that
  21. # tests mentioned in @skipUnlessPassed run before the tests that depend on them,
  22. # but without such dependencies, tests run in the order in which they are listed
  23. # in TEST_SUITES.
  24. #
  25. # A layer can add its own tests in lib/oeqa/runtime, provided it extends BBPATH as normal in its layer.conf.
  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. TEST_LOG_DIR ?= "${WORKDIR}/testimage"
  30. TEST_EXPORT_DIR ?= "${TMPDIR}/testimage/${PN}"
  31. TEST_INSTALL_TMP_DIR ?= "${WORKDIR}/testimage/install_tmp"
  32. TEST_NEEDED_PACKAGES_DIR ?= "${WORKDIR}/testimage/packages"
  33. TEST_EXTRACTED_DIR ?= "${TEST_NEEDED_PACKAGES_DIR}/extracted"
  34. TEST_PACKAGED_DIR ?= "${TEST_NEEDED_PACKAGES_DIR}/packaged"
  35. RPMTESTSUITE = "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'dnf rpm', '', d)}"
  36. SYSTEMDSUITE = "${@bb.utils.filter('DISTRO_FEATURES', 'systemd', d)}"
  37. MINTESTSUITE = "ping"
  38. NETTESTSUITE = "${MINTESTSUITE} ssh df date scp oe_syslog ${SYSTEMDSUITE}"
  39. DEVTESTSUITE = "gcc kernelmodule ldd"
  40. DEFAULT_TEST_SUITES = "${MINTESTSUITE} auto"
  41. DEFAULT_TEST_SUITES_pn-core-image-minimal = "${MINTESTSUITE}"
  42. DEFAULT_TEST_SUITES_pn-core-image-minimal-dev = "${MINTESTSUITE}"
  43. DEFAULT_TEST_SUITES_pn-core-image-full-cmdline = "${NETTESTSUITE} perl python logrotate ptest"
  44. DEFAULT_TEST_SUITES_pn-core-image-x11 = "${MINTESTSUITE}"
  45. DEFAULT_TEST_SUITES_pn-core-image-lsb = "${NETTESTSUITE} pam parselogs ${RPMTESTSUITE} ptest"
  46. DEFAULT_TEST_SUITES_pn-core-image-sato = "${NETTESTSUITE} connman xorg parselogs ${RPMTESTSUITE} \
  47. ${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'python', '', d)} ptest gi"
  48. DEFAULT_TEST_SUITES_pn-core-image-sato-sdk = "${NETTESTSUITE} buildcpio buildlzip buildgalculator \
  49. connman ${DEVTESTSUITE} logrotate perl parselogs python ${RPMTESTSUITE} xorg ptest gi stap"
  50. DEFAULT_TEST_SUITES_pn-core-image-lsb-dev = "${NETTESTSUITE} pam perl python parselogs ${RPMTESTSUITE} ptest gi"
  51. DEFAULT_TEST_SUITES_pn-core-image-lsb-sdk = "${NETTESTSUITE} buildcpio buildlzip buildgalculator \
  52. connman ${DEVTESTSUITE} logrotate pam parselogs perl python ${RPMTESTSUITE} ptest gi stap"
  53. DEFAULT_TEST_SUITES_pn-meta-toolchain = "auto"
  54. # aarch64 has no graphics
  55. DEFAULT_TEST_SUITES_remove_aarch64 = "xorg"
  56. # musl doesn't support systemtap
  57. DEFAULT_TEST_SUITES_remove_libc-musl = "stap"
  58. # qemumips is quite slow and has reached the timeout limit several times on the YP build cluster,
  59. # mitigate this by removing build tests for qemumips machines.
  60. MIPSREMOVE ??= "buildcpio buildlzip buildgalculator"
  61. DEFAULT_TEST_SUITES_remove_qemumips = "${MIPSREMOVE}"
  62. DEFAULT_TEST_SUITES_remove_qemumips64 = "${MIPSREMOVE}"
  63. TEST_SUITES ?= "${DEFAULT_TEST_SUITES}"
  64. TEST_QEMUBOOT_TIMEOUT ?= "1000"
  65. TEST_TARGET ?= "qemu"
  66. TESTIMAGEDEPENDS = ""
  67. TESTIMAGEDEPENDS_qemuall = "qemu-native:do_populate_sysroot qemu-helper-native:do_populate_sysroot qemu-helper-native:do_addto_recipe_sysroot"
  68. TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'cpio-native:do_populate_sysroot', '', d)}"
  69. TESTIMAGEDEPENDS_qemuall += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'cpio-native:do_populate_sysroot', '', d)}"
  70. TESTIMAGEDEPENDS_qemuall += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'createrepo-c-native:do_populate_sysroot', '', d)}"
  71. TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'dnf-native:do_populate_sysroot', '', d)}"
  72. TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'ipk', 'opkg-utils-native:do_populate_sysroot', '', d)}"
  73. TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'deb', 'apt-native:do_populate_sysroot', '', d)}"
  74. TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'createrepo-c-native:do_populate_sysroot', '', d)}"
  75. TESTIMAGELOCK = "${TMPDIR}/testimage.lock"
  76. TESTIMAGELOCK_qemuall = ""
  77. TESTIMAGE_DUMP_DIR ?= "/tmp/oe-saved-tests/"
  78. TESTIMAGE_UPDATE_VARS ?= "DL_DIR WORKDIR DEPLOY_DIR"
  79. testimage_dump_target () {
  80. top -bn1
  81. ps
  82. free
  83. df
  84. # The next command will export the default gateway IP
  85. export DEFAULT_GATEWAY=$(ip route | awk '/default/ { print $3}')
  86. ping -c3 $DEFAULT_GATEWAY
  87. dmesg
  88. netstat -an
  89. ip address
  90. # Next command will dump logs from /var/log/
  91. find /var/log/ -type f 2>/dev/null -exec echo "====================" \; -exec echo {} \; -exec echo "====================" \; -exec cat {} \; -exec echo "" \;
  92. }
  93. testimage_dump_host () {
  94. top -bn1
  95. iostat -x -z -N -d -p ALL 20 2
  96. ps -ef
  97. free
  98. df
  99. memstat
  100. dmesg
  101. ip -s link
  102. netstat -an
  103. }
  104. python do_testimage() {
  105. testimage_main(d)
  106. }
  107. addtask testimage
  108. do_testimage[nostamp] = "1"
  109. do_testimage[depends] += "${TESTIMAGEDEPENDS}"
  110. do_testimage[lockfiles] += "${TESTIMAGELOCK}"
  111. def testimage_sanity(d):
  112. if (d.getVar('TEST_TARGET') == 'simpleremote'
  113. and (not d.getVar('TEST_TARGET_IP')
  114. or not d.getVar('TEST_SERVER_IP'))):
  115. bb.fatal('When TEST_TARGET is set to "simpleremote" '
  116. 'TEST_TARGET_IP and TEST_SERVER_IP are needed too.')
  117. def testimage_main(d):
  118. import os
  119. import json
  120. import signal
  121. import logging
  122. from bb.utils import export_proxies
  123. from oeqa.core.utils.misc import updateTestData
  124. from oeqa.runtime.context import OERuntimeTestContext
  125. from oeqa.runtime.context import OERuntimeTestContextExecutor
  126. from oeqa.core.target.qemu import supported_fstypes
  127. from oeqa.core.utils.test import getSuiteCases
  128. from oeqa.utils import make_logger_bitbake_compatible
  129. def sigterm_exception(signum, stackframe):
  130. """
  131. Catch SIGTERM from worker in order to stop qemu.
  132. """
  133. raise RuntimeError
  134. testimage_sanity(d)
  135. if (d.getVar('IMAGE_PKGTYPE') == 'rpm'
  136. and ('dnf' in d.getVar('TEST_SUITES') or 'auto' in d.getVar('TEST_SUITES'))):
  137. create_rpm_index(d)
  138. logger = make_logger_bitbake_compatible(logging.getLogger("BitBake"))
  139. pn = d.getVar("PN")
  140. bb.utils.mkdirhier(d.getVar("TEST_LOG_DIR"))
  141. image_name = ("%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'),
  142. d.getVar('IMAGE_LINK_NAME')))
  143. tdname = "%s.testdata.json" % image_name
  144. try:
  145. td = json.load(open(tdname, "r"))
  146. except (FileNotFoundError) as err:
  147. bb.fatal('File %s Not Found. Have you built the image with INHERIT+="testimage" in the conf/local.conf?' % tdname)
  148. # Some variables need to be updates (mostly paths) with the
  149. # ones of the current environment because some tests require them.
  150. updateTestData(d, td, d.getVar('TESTIMAGE_UPDATE_VARS').split())
  151. image_manifest = "%s.manifest" % image_name
  152. image_packages = OERuntimeTestContextExecutor.readPackagesManifest(image_manifest)
  153. extract_dir = d.getVar("TEST_EXTRACTED_DIR")
  154. # Get machine
  155. machine = d.getVar("MACHINE")
  156. # Get rootfs
  157. fstypes = [fs for fs in d.getVar('IMAGE_FSTYPES').split(' ')
  158. if fs in supported_fstypes]
  159. if not fstypes:
  160. bb.fatal('Unsupported image type built. Add a comptible image to '
  161. 'IMAGE_FSTYPES. Supported types: %s' %
  162. ', '.join(supported_fstypes))
  163. rootfs = '%s.%s' % (image_name, fstypes[0])
  164. # Get tmpdir (not really used, just for compatibility)
  165. tmpdir = d.getVar("TMPDIR")
  166. # Get deploy_dir_image (not really used, just for compatibility)
  167. dir_image = d.getVar("DEPLOY_DIR_IMAGE")
  168. # Get bootlog
  169. bootlog = os.path.join(d.getVar("TEST_LOG_DIR"),
  170. 'qemu_boot_log.%s' % d.getVar('DATETIME'))
  171. # Get display
  172. display = d.getVar("BB_ORIGENV").getVar("DISPLAY")
  173. # Get kernel
  174. kernel_name = ('%s-%s.bin' % (d.getVar("KERNEL_IMAGETYPE"), machine))
  175. kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), kernel_name)
  176. # Get boottime
  177. boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT"))
  178. # Get use_kvm
  179. qemu_use_kvm = d.getVar("QEMU_USE_KVM")
  180. if qemu_use_kvm and \
  181. (oe.types.boolean(qemu_use_kvm) and 'x86' in machine or \
  182. d.getVar('MACHINE') in qemu_use_kvm.split()):
  183. kvm = True
  184. else:
  185. kvm = False
  186. # TODO: We use the current implementatin of qemu runner because of
  187. # time constrains, qemu runner really needs a refactor too.
  188. target_kwargs = { 'machine' : machine,
  189. 'rootfs' : rootfs,
  190. 'tmpdir' : tmpdir,
  191. 'dir_image' : dir_image,
  192. 'display' : display,
  193. 'kernel' : kernel,
  194. 'boottime' : boottime,
  195. 'bootlog' : bootlog,
  196. 'kvm' : kvm,
  197. }
  198. # TODO: Currently BBPATH is needed for custom loading of targets.
  199. # It would be better to find these modules using instrospection.
  200. target_kwargs['target_modules_path'] = d.getVar('BBPATH')
  201. # runtime use network for download projects for build
  202. export_proxies(d)
  203. # we need the host dumper in test context
  204. host_dumper = OERuntimeTestContextExecutor.getHostDumper(
  205. d.getVar("testimage_dump_host"),
  206. d.getVar("TESTIMAGE_DUMP_DIR"))
  207. # the robot dance
  208. target = OERuntimeTestContextExecutor.getTarget(
  209. d.getVar("TEST_TARGET"), logger, d.getVar("TEST_TARGET_IP"),
  210. d.getVar("TEST_SERVER_IP"), **target_kwargs)
  211. # test context
  212. tc = OERuntimeTestContext(td, logger, target, host_dumper,
  213. image_packages, extract_dir)
  214. # Load tests before starting the target
  215. test_paths = get_runtime_paths(d)
  216. test_modules = d.getVar('TEST_SUITES').split()
  217. if not test_modules:
  218. bb.fatal('Empty test suite, please verify TEST_SUITES variable')
  219. tc.loadTests(test_paths, modules=test_modules)
  220. suitecases = getSuiteCases(tc.suites)
  221. if not suitecases:
  222. bb.fatal('Empty test suite, please verify TEST_SUITES variable')
  223. else:
  224. bb.debug(2, 'test suites:\n\t%s' % '\n\t'.join([str(c) for c in suitecases]))
  225. package_extraction(d, tc.suites)
  226. bootparams = None
  227. if d.getVar('VIRTUAL-RUNTIME_init_manager', '') == 'systemd':
  228. # Add systemd.log_level=debug to enable systemd debug logging
  229. bootparams = 'systemd.log_target=console'
  230. results = None
  231. orig_sigterm_handler = signal.signal(signal.SIGTERM, sigterm_exception)
  232. try:
  233. # We need to check if runqemu ends unexpectedly
  234. # or if the worker send us a SIGTERM
  235. tc.target.start(extra_bootparams=bootparams)
  236. results = tc.runTests()
  237. except (RuntimeError, BlockingIOError) as err:
  238. if isinstance(err, RuntimeError):
  239. bb.error('testimage received SIGTERM, shutting down...')
  240. else:
  241. bb.error('runqemu failed, shutting down...')
  242. if results:
  243. results.stop()
  244. results = None
  245. finally:
  246. signal.signal(signal.SIGTERM, orig_sigterm_handler)
  247. tc.target.stop()
  248. # Show results (if we have them)
  249. if not results:
  250. bb.fatal('%s - FAILED - tests were interrupted during execution' % pn, forcelog=True)
  251. results.logDetails()
  252. results.logSummary(pn)
  253. if not results.wasSuccessful():
  254. bb.fatal('%s - FAILED - check the task log and the ssh log' % pn, forcelog=True)
  255. def get_runtime_paths(d):
  256. """
  257. Returns a list of paths where runtime test must reside.
  258. Runtime tests are expected in <LAYER_DIR>/lib/oeqa/runtime/cases/
  259. """
  260. paths = []
  261. for layer in d.getVar('BBLAYERS').split():
  262. path = os.path.join(layer, 'lib/oeqa/runtime/cases')
  263. if os.path.isdir(path):
  264. paths.append(path)
  265. return paths
  266. def create_index(arg):
  267. import subprocess
  268. index_cmd = arg
  269. try:
  270. bb.note("Executing '%s' ..." % index_cmd)
  271. result = subprocess.check_output(index_cmd,
  272. stderr=subprocess.STDOUT,
  273. shell=True)
  274. result = result.decode('utf-8')
  275. except subprocess.CalledProcessError as e:
  276. return("Index creation command '%s' failed with return code "
  277. '%d:\n%s' % (e.cmd, e.returncode, e.output.decode("utf-8")))
  278. if result:
  279. bb.note(result)
  280. return None
  281. def create_rpm_index(d):
  282. import glob
  283. # Index RPMs
  284. rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo_c")
  285. index_cmds = []
  286. archs = (d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or '').replace('-', '_')
  287. for arch in archs.split():
  288. rpm_dir = os.path.join(d.getVar('DEPLOY_DIR_RPM'), arch)
  289. idx_path = os.path.join(d.getVar('WORKDIR'), 'oe-testimage-repo', arch)
  290. if not os.path.isdir(rpm_dir):
  291. continue
  292. lockfilename = os.path.join(d.getVar('DEPLOY_DIR_RPM'), 'rpm.lock')
  293. lf = bb.utils.lockfile(lockfilename, False)
  294. oe.path.copyhardlinktree(rpm_dir, idx_path)
  295. # Full indexes overload a 256MB image so reduce the number of rpms
  296. # in the feed by filtering to specific packages needed by the tests.
  297. package_list = glob.glob(idx_path + "*/*.rpm")
  298. for pkg in package_list:
  299. if not os.path.basename(pkg).startswith(("rpm", "run-postinsts", "busybox", "bash", "update-alternatives", "libc6", "curl", "musl")):
  300. bb.utils.remove(pkg)
  301. bb.utils.unlockfile(lf)
  302. cmd = '%s --update -q %s' % (rpm_createrepo, idx_path)
  303. # Create repodata
  304. result = create_index(cmd)
  305. if result:
  306. bb.fatal('%s' % ('\n'.join(result)))
  307. def package_extraction(d, test_suites):
  308. from oeqa.utils.package_manager import find_packages_to_extract
  309. from oeqa.utils.package_manager import extract_packages
  310. bb.utils.remove(d.getVar("TEST_NEEDED_PACKAGES_DIR"), recurse=True)
  311. packages = find_packages_to_extract(test_suites)
  312. if packages:
  313. bb.utils.mkdirhier(d.getVar("TEST_INSTALL_TMP_DIR"))
  314. bb.utils.mkdirhier(d.getVar("TEST_PACKAGED_DIR"))
  315. bb.utils.mkdirhier(d.getVar("TEST_EXTRACTED_DIR"))
  316. extract_packages(d, packages)
  317. testimage_main[vardepsexclude] += "BB_ORIGENV DATETIME"
  318. python () {
  319. if oe.types.boolean(d.getVar("TESTIMAGE_AUTO") or "False"):
  320. bb.build.addtask("testimage", "do_build", "do_image_complete", d)
  321. }