testimage.bbclass 14 KB

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