blob: 505b4be837348efb3ad5dec4aacb1fff15aababf [file] [log] [blame]
Patrick Williams92b42cb2022-09-03 06:53:57 -05001#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6import os
7import socketserver
8import subprocess
Patrick Williamsac13d5f2023-11-24 18:59:46 -06009import time
10import urllib
11import pathlib
Patrick Williams92b42cb2022-09-03 06:53:57 -050012
Patrick Williamsac13d5f2023-11-24 18:59:46 -060013from oeqa.core.decorator import OETestTag
Patrick Williams92b42cb2022-09-03 06:53:57 -050014from oeqa.selftest.case import OESelftestTestCase
15from oeqa.utils.commands import bitbake, get_bb_var, runqemu
16
Andrew Geissler87f5cff2022-09-30 13:13:31 -050017
Patrick Williams92b42cb2022-09-03 06:53:57 -050018class Debuginfod(OESelftestTestCase):
Andrew Geissler517393d2023-01-13 08:55:19 -060019
20 def wait_for_debuginfod(self, port):
21 """
22 debuginfod takes time to scan the packages and requesting too early may
23 result in a test failure if the right packages haven't been scanned yet.
24
25 Request the metrics endpoint periodically and wait for there to be no
26 busy scanning threads.
27
Patrick Williamsac13d5f2023-11-24 18:59:46 -060028 Returns if debuginfod is ready, raises an exception if not within the
29 timeout.
Andrew Geissler517393d2023-01-13 08:55:19 -060030 """
Andrew Geissler517393d2023-01-13 08:55:19 -060031
Patrick Williamsac13d5f2023-11-24 18:59:46 -060032 # Wait two minutes
33 countdown = 24
34 delay = 5
35 latest = None
Andrew Geissler517393d2023-01-13 08:55:19 -060036
37 while countdown:
Patrick Williamsac13d5f2023-11-24 18:59:46 -060038 self.logger.info("waiting...")
Andrew Geissler517393d2023-01-13 08:55:19 -060039 time.sleep(delay)
Patrick Williamsac13d5f2023-11-24 18:59:46 -060040
41 self.logger.info("polling server")
42 if self.debuginfod.poll():
43 self.logger.info("server dead")
44 self.debuginfod.communicate()
45 self.fail("debuginfod terminated unexpectedly")
46 self.logger.info("server alive")
47
Andrew Geissler517393d2023-01-13 08:55:19 -060048 try:
Patrick Williamsac13d5f2023-11-24 18:59:46 -060049 with urllib.request.urlopen("http://localhost:%d/metrics" % port, timeout=10) as f:
50 for line in f.read().decode("ascii").splitlines():
51 key, value = line.rsplit(" ", 1)
52 if key == "thread_busy{role=\"scan\"}":
53 latest = int(value)
54 self.logger.info("Waiting for %d scan jobs to finish" % latest)
55 if latest == 0:
56 return
Andrew Geissler517393d2023-01-13 08:55:19 -060057 except urllib.error.URLError as e:
Patrick Williamsac13d5f2023-11-24 18:59:46 -060058 # TODO: how to catch just timeouts?
Andrew Geissler517393d2023-01-13 08:55:19 -060059 self.logger.error(e)
Patrick Williamsac13d5f2023-11-24 18:59:46 -060060
Andrew Geissler517393d2023-01-13 08:55:19 -060061 countdown -= 1
Andrew Geissler517393d2023-01-13 08:55:19 -060062
Patrick Williamsac13d5f2023-11-24 18:59:46 -060063 raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest)
Andrew Geissler517393d2023-01-13 08:55:19 -060064
Patrick Williamsac13d5f2023-11-24 18:59:46 -060065 def start_debuginfod(self):
66 # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot
Patrick Williams92b42cb2022-09-03 06:53:57 -050067
Patrick Williamsac13d5f2023-11-24 18:59:46 -060068 # Save some useful paths for later
69 native_sysroot = pathlib.Path(get_bb_var("RECIPE_SYSROOT_NATIVE", "elfutils-native"))
70 native_bindir = native_sysroot / "usr" / "bin"
71 self.debuginfod = native_bindir / "debuginfod"
72 self.debuginfod_find = native_bindir / "debuginfod-find"
73
Andrew Geissler87f5cff2022-09-30 13:13:31 -050074 cmd = [
Patrick Williamsac13d5f2023-11-24 18:59:46 -060075 self.debuginfod,
Andrew Geissler87f5cff2022-09-30 13:13:31 -050076 "--verbose",
Andrew Geissler517393d2023-01-13 08:55:19 -060077 # In-memory database, this is a one-shot test
Andrew Geissler87f5cff2022-09-30 13:13:31 -050078 "--database=:memory:",
Andrew Geissler517393d2023-01-13 08:55:19 -060079 # Don't use all the host cores
80 "--concurrency=8",
81 "--connection-pool=8",
82 # Disable rescanning, this is a one-shot test
83 "--rescan-time=0",
84 "--groom-time=0",
Andrew Geissler87f5cff2022-09-30 13:13:31 -050085 get_bb_var("DEPLOY_DIR"),
86 ]
Andrew Geissler517393d2023-01-13 08:55:19 -060087
88 format = get_bb_var("PACKAGE_CLASSES").split()[0]
89 if format == "package_deb":
90 cmd.append("--scan-deb-dir")
91 elif format == "package_ipk":
92 cmd.append("--scan-deb-dir")
93 elif format == "package_rpm":
94 cmd.append("--scan-rpm-dir")
95 else:
96 self.fail("Unknown package class %s" % format)
97
Patrick Williamsac13d5f2023-11-24 18:59:46 -060098 # Find a free port. Racey but the window is small.
Patrick Williams92b42cb2022-09-03 06:53:57 -050099 with socketserver.TCPServer(("localhost", 0), None) as s:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600100 self.port = s.server_address[1]
101 cmd.append("--port=%d" % self.port)
102
103 self.logger.info(f"Starting server {cmd}")
104 self.debuginfod = subprocess.Popen(cmd, env={})
105 self.wait_for_debuginfod(self.port)
106
107
108 def test_debuginfod_native(self):
109 """
110 Test debuginfod outside of qemu, by building a package and looking up a
111 binary's debuginfo using elfutils-native.
112 """
113
114 self.write_config("""
115TMPDIR = "${TOPDIR}/tmp-debuginfod"
116DISTRO_FEATURES:append = " debuginfod"
117""")
118 bitbake("elfutils-native:do_addto_recipe_sysroot xz xz:do_package")
Patrick Williams92b42cb2022-09-03 06:53:57 -0500119
120 try:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600121 self.start_debuginfod()
Andrew Geissler517393d2023-01-13 08:55:19 -0600122
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600123 env = os.environ.copy()
124 env["DEBUGINFOD_URLS"] = "http://localhost:%d/" % self.port
125
126 pkgs = pathlib.Path(get_bb_var("PKGDEST", "xz"))
127 cmd = (self.debuginfod_find, "debuginfo", pkgs / "xz" / "usr" / "bin" / "xz.xz")
128 self.logger.info(f"Starting client {cmd}")
129 output = subprocess.check_output(cmd, env=env, text=True)
130 # This should be more comprehensive
131 self.assertIn("/.cache/debuginfod_client/", output)
132 finally:
133 self.debuginfod.kill()
134
135 @OETestTag("runqemu")
136 def test_debuginfod_qemu(self):
137 """
138 Test debuginfod-find inside a qemu, talking to a debuginfod on the host.
139 """
140
141 self.write_config("""
142TMPDIR = "${TOPDIR}/tmp-debuginfod"
143DISTRO_FEATURES:append = " debuginfod"
144CORE_IMAGE_EXTRA_INSTALL += "elfutils xz"
145 """)
146 bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot")
147
148 try:
149 self.start_debuginfod()
Patrick Williams92b42cb2022-09-03 06:53:57 -0500150
151 with runqemu("core-image-minimal", runqemuparams="nographic") as qemu:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600152 cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port)
Andrew Geissler517393d2023-01-13 08:55:19 -0600153 self.logger.info(f"Starting client {cmd}")
Patrick Williams92b42cb2022-09-03 06:53:57 -0500154 status, output = qemu.run_serial(cmd)
155 # This should be more comprehensive
156 self.assertIn("/.cache/debuginfod_client/", output)
157 finally:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600158 self.debuginfod.kill()