diff options
| author | Ross Burton <ross.burton@arm.com> | 2023-11-21 13:19:24 +0000 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2023-11-24 05:01:37 -1000 |
| commit | 9c1fb1c9ef1f49330ce90ae09ece4548e54ccf2b (patch) | |
| tree | 60fd631e3f8d853c38a15876c2348319f9bdf5d2 | |
| parent | 6a35bdf5715de0018f2cec14efade52fdbc8eecb (diff) | |
| download | poky-9c1fb1c9ef1f49330ce90ae09ece4548e54ccf2b.tar.gz | |
oeqa/selftest/debuginfod: improve selftest
This test was occasionally failing for no obvious reason, so refactor
and improve:
- While waiting for the daemon, check that it is still running and
explicitly timeout after 10s when making the HTTP call.
- While waiting for the daemon to be ready, log the current state of the
daemon so we can tell if we're timing out as it is still scanning.
- This was in fact the cause of the intermittant failures, because the
TMPDIR is reused between tests and may contain a large number of
packages. Do the tests in an isolated TMPDIR to hopefully mitigate this
issue and increase the timeout to two minutes.
- Decorate the test using runqemu as such so that can be skipped in
environments without runqemu
- Add a second test that doesn't use runqemu or images, which is faster
but less realistic.
(From OE-Core rev: 99590fac1bfb5474f5bf0e02d3888b518af9fb3e)
Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
(cherry picked from commit 88b660aaae2527736b6eccec4c952eee969e20a2)
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/lib/oeqa/selftest/cases/debuginfod.py | 124 |
1 files changed, 88 insertions, 36 deletions
diff --git a/meta/lib/oeqa/selftest/cases/debuginfod.py b/meta/lib/oeqa/selftest/cases/debuginfod.py index 37f51760fb..505b4be837 100644 --- a/meta/lib/oeqa/selftest/cases/debuginfod.py +++ b/meta/lib/oeqa/selftest/cases/debuginfod.py | |||
| @@ -6,7 +6,11 @@ | |||
| 6 | import os | 6 | import os |
| 7 | import socketserver | 7 | import socketserver |
| 8 | import subprocess | 8 | import subprocess |
| 9 | import time | ||
| 10 | import urllib | ||
| 11 | import pathlib | ||
| 9 | 12 | ||
| 13 | from oeqa.core.decorator import OETestTag | ||
| 10 | from oeqa.selftest.case import OESelftestTestCase | 14 | from oeqa.selftest.case import OESelftestTestCase |
| 11 | from oeqa.utils.commands import bitbake, get_bb_var, runqemu | 15 | from oeqa.utils.commands import bitbake, get_bb_var, runqemu |
| 12 | 16 | ||
| @@ -21,39 +25,54 @@ class Debuginfod(OESelftestTestCase): | |||
| 21 | Request the metrics endpoint periodically and wait for there to be no | 25 | Request the metrics endpoint periodically and wait for there to be no |
| 22 | busy scanning threads. | 26 | busy scanning threads. |
| 23 | 27 | ||
| 24 | Returns True if debuginfod is ready, False if we timed out | 28 | Returns if debuginfod is ready, raises an exception if not within the |
| 29 | timeout. | ||
| 25 | """ | 30 | """ |
| 26 | import time, urllib | ||
| 27 | 31 | ||
| 28 | # Wait a minute | 32 | # Wait two minutes |
| 29 | countdown = 6 | 33 | countdown = 24 |
| 30 | delay = 10 | 34 | delay = 5 |
| 35 | latest = None | ||
| 31 | 36 | ||
| 32 | while countdown: | 37 | while countdown: |
| 38 | self.logger.info("waiting...") | ||
| 33 | time.sleep(delay) | 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 | |||
| 34 | try: | 48 | try: |
| 35 | with urllib.request.urlopen("http://localhost:%d/metrics" % port) as f: | 49 | with urllib.request.urlopen("http://localhost:%d/metrics" % port, timeout=10) as f: |
| 36 | lines = f.read().decode("ascii").splitlines() | 50 | for line in f.read().decode("ascii").splitlines(): |
| 37 | if "thread_busy{role=\"scan\"} 0" in lines: | 51 | key, value = line.rsplit(" ", 1) |
| 38 | return True | 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 | ||
| 39 | except urllib.error.URLError as e: | 57 | except urllib.error.URLError as e: |
| 58 | # TODO: how to catch just timeouts? | ||
| 40 | self.logger.error(e) | 59 | self.logger.error(e) |
| 60 | |||
| 41 | countdown -= 1 | 61 | countdown -= 1 |
| 42 | return False | ||
| 43 | 62 | ||
| 63 | raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest) | ||
| 44 | 64 | ||
| 45 | def test_debuginfod(self): | 65 | def start_debuginfod(self): |
| 46 | self.write_config( | 66 | # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot |
| 47 | """ | 67 | |
| 48 | DISTRO_FEATURES:append = " debuginfod" | 68 | # Save some useful paths for later |
| 49 | CORE_IMAGE_EXTRA_INSTALL += "elfutils" | 69 | native_sysroot = pathlib.Path(get_bb_var("RECIPE_SYSROOT_NATIVE", "elfutils-native")) |
| 50 | """ | 70 | native_bindir = native_sysroot / "usr" / "bin" |
| 51 | ) | 71 | self.debuginfod = native_bindir / "debuginfod" |
| 52 | bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot") | 72 | self.debuginfod_find = native_bindir / "debuginfod-find" |
| 53 | 73 | ||
| 54 | native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "elfutils-native") | ||
| 55 | cmd = [ | 74 | cmd = [ |
| 56 | os.path.join(native_sysroot, "usr", "bin", "debuginfod"), | 75 | self.debuginfod, |
| 57 | "--verbose", | 76 | "--verbose", |
| 58 | # In-memory database, this is a one-shot test | 77 | # In-memory database, this is a one-shot test |
| 59 | "--database=:memory:", | 78 | "--database=:memory:", |
| @@ -76,31 +95,64 @@ CORE_IMAGE_EXTRA_INSTALL += "elfutils" | |||
| 76 | else: | 95 | else: |
| 77 | self.fail("Unknown package class %s" % format) | 96 | self.fail("Unknown package class %s" % format) |
| 78 | 97 | ||
| 79 | # Find a free port | 98 | # Find a free port. Racey but the window is small. |
| 80 | with socketserver.TCPServer(("localhost", 0), None) as s: | 99 | with socketserver.TCPServer(("localhost", 0), None) as s: |
| 81 | port = s.server_address[1] | 100 | self.port = s.server_address[1] |
| 82 | cmd.append("--port=%d" % port) | 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(""" | ||
| 115 | TMPDIR = "${TOPDIR}/tmp-debuginfod" | ||
| 116 | DISTRO_FEATURES:append = " debuginfod" | ||
| 117 | """) | ||
| 118 | bitbake("elfutils-native:do_addto_recipe_sysroot xz xz:do_package") | ||
| 83 | 119 | ||
| 84 | try: | 120 | try: |
| 85 | # Remove DEBUGINFOD_URLS from the environment so we don't try | 121 | self.start_debuginfod() |
| 86 | # looking in the distro debuginfod | 122 | |
| 87 | env = os.environ.copy() | 123 | env = os.environ.copy() |
| 88 | if "DEBUGINFOD_URLS" in env: | 124 | env["DEBUGINFOD_URLS"] = "http://localhost:%d/" % self.port |
| 89 | del env["DEBUGINFOD_URLS"] | 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(""" | ||
| 142 | TMPDIR = "${TOPDIR}/tmp-debuginfod" | ||
| 143 | DISTRO_FEATURES:append = " debuginfod" | ||
| 144 | CORE_IMAGE_EXTRA_INSTALL += "elfutils xz" | ||
| 145 | """) | ||
| 146 | bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot") | ||
| 90 | 147 | ||
| 91 | self.logger.info(f"Starting server {cmd}") | 148 | try: |
| 92 | debuginfod = subprocess.Popen(cmd, env=env) | 149 | self.start_debuginfod() |
| 93 | 150 | ||
| 94 | with runqemu("core-image-minimal", runqemuparams="nographic") as qemu: | 151 | with runqemu("core-image-minimal", runqemuparams="nographic") as qemu: |
| 95 | self.assertTrue(self.wait_for_debuginfod(port)) | 152 | cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port) |
| 96 | |||
| 97 | cmd = ( | ||
| 98 | "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/debuginfod" | ||
| 99 | % (qemu.server_ip, port) | ||
| 100 | ) | ||
| 101 | self.logger.info(f"Starting client {cmd}") | 153 | self.logger.info(f"Starting client {cmd}") |
| 102 | status, output = qemu.run_serial(cmd) | 154 | status, output = qemu.run_serial(cmd) |
| 103 | # This should be more comprehensive | 155 | # This should be more comprehensive |
| 104 | self.assertIn("/.cache/debuginfod_client/", output) | 156 | self.assertIn("/.cache/debuginfod_client/", output) |
| 105 | finally: | 157 | finally: |
| 106 | debuginfod.kill() | 158 | self.debuginfod.kill() |
