summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/selftest/cases/debuginfod.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa/selftest/cases/debuginfod.py')
-rw-r--r--meta/lib/oeqa/selftest/cases/debuginfod.py158
1 files changed, 158 insertions, 0 deletions
diff --git a/meta/lib/oeqa/selftest/cases/debuginfod.py b/meta/lib/oeqa/selftest/cases/debuginfod.py
new file mode 100644
index 0000000000..505b4be837
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/debuginfod.py
@@ -0,0 +1,158 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6import os
7import socketserver
8import subprocess
9import time
10import urllib
11import pathlib
12
13from oeqa.core.decorator import OETestTag
14from oeqa.selftest.case import OESelftestTestCase
15from oeqa.utils.commands import bitbake, get_bb_var, runqemu
16
17
18class Debuginfod(OESelftestTestCase):
19
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
28 Returns if debuginfod is ready, raises an exception if not within the
29 timeout.
30 """
31
32 # Wait two minutes
33 countdown = 24
34 delay = 5
35 latest = None
36
37 while countdown:
38 self.logger.info("waiting...")
39 time.sleep(delay)
40
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
48 try:
49 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
57 except urllib.error.URLError as e:
58 # TODO: how to catch just timeouts?
59 self.logger.error(e)
60
61 countdown -= 1
62
63 raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest)
64
65 def start_debuginfod(self):
66 # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot
67
68 # 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
74 cmd = [
75 self.debuginfod,
76 "--verbose",
77 # In-memory database, this is a one-shot test
78 "--database=:memory:",
79 # 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",
85 get_bb_var("DEPLOY_DIR"),
86 ]
87
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
98 # Find a free port. Racey but the window is small.
99 with socketserver.TCPServer(("localhost", 0), None) as s:
100 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")
119
120 try:
121 self.start_debuginfod()
122
123 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()
150
151 with runqemu("core-image-minimal", runqemuparams="nographic") as qemu:
152 cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port)
153 self.logger.info(f"Starting client {cmd}")
154 status, output = qemu.run_serial(cmd)
155 # This should be more comprehensive
156 self.assertIn("/.cache/debuginfod_client/", output)
157 finally:
158 self.debuginfod.kill()