diff mbox series

[14/14] oe-selftest: devtool ide-sdk: add test for ide=none LLDB/clang support

Message ID 20260802195324.64533-15-adrian.freihofer@siemens.com
State New
Headers show
Series devtool ide-sdk: clang and lldb support | expand

Commit Message

AdrianF Aug. 2, 2026, 7:53 p.m. UTC
From: Adrian Freihofer <adrian.freihofer@siemens.com>

Add test_devtool_ide_sdk_none_cmake_clang to DevtoolIdeSdkTests to
exercise the new LldbServerConfigNone code path in ide_none.py.

The test uses the cmake-example-clang recipe (TOOLCHAIN = "clang") and
runs devtool ide-sdk with --ide=none.  It verifies:

- lldb_server_<port>_<binary>_multi script is generated
- lldbinit/lldbinit_<port>_<binary> init file is generated
- lldb_<port>_<binary> wrapper script is generated
- No gdbserver_* script is generated for a clang recipe
- The install_and_deploy script exists
- The oe-scripts symlink inside the source tree is valid
- lldb-server can be started on the target via the generated script
- The pid file written by the start script references the live process
- An lldb --batch session using the generated wrapper reaches main and
  can evaluate CppExample::test_string at the breakpoint
- lldb-server stops cleanly via the generated stop script

A new helper _lldb_none_debugging_multi is added alongside the
existing _gdb_cross_debugging_multi, following the same structure.

Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
 meta/lib/oeqa/selftest/cases/devtool.py | 192 ++++++++++++++++++++++++
 1 file changed, 192 insertions(+)
diff mbox series

Patch

diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index e9b8f38449..7145755fed 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -4378,6 +4378,198 @@  class DevtoolIdeSdkTests(DevtoolBase):
                 lambda magic_string: self._lldb_server_debugging_once(
                     tempdir, qemu, magic_string))
 
+    def _lldb_none_debugging_multi(self, tempdir, qemu, recipe_name, example_exe, magic_string):
+        """Verify lldb-server scripts generated by ide=none work end-to-end.
+
+        Mirrors _gdb_cross_debugging_multi but for the LLDB/clang path:
+        - uses the generated lldb_server_* start/stop script
+        - uses the generated lldb_* wrapper script with lldb --batch
+
+        Covers the executable, the library and a header-only inline function
+        (see _lldb_debug_cpp_example_batch_commands), since a single call-site
+        breakpoint would not exercise (and thus not catch a regression in) the
+        lldbinit source map / debug-file-search-paths setup for the library's
+        own debug info specifically.
+        """
+        sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
+        scripts_dir = self._workspace_scripts_dir(recipe_name)
+        binary_pretty = 'usr-bin-' + example_exe
+        lldb_server_script = os.path.join(
+            scripts_dir, 'lldb_server_1234_%s_multi' % binary_pretty)
+        lldb_script = os.path.join(scripts_dir, 'lldb_1234_%s' % binary_pretty)
+
+        self.assertExists(lldb_server_script)
+        self.assertExists(lldb_script)
+
+        # Start lldb-server (script exits once the port is ready)
+        r = runCmd(lldb_server_script, output_log=self._cmd_logger)
+        self.assertEqual(r.status, 0)
+
+        # Verify lldb-server is running on the target
+        r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
+                   output_log=self._cmd_logger)
+        self.assertEqual(r.status, 0)
+        self.assertIn('lldb-server', r.output)
+
+        # Verify the pid file points at the running lldb-server process
+        pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty
+        test_cmd = "'cat /proc/$(cat %s)/cmdline'" % pid_file
+        r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd),
+                   output_log=self._cmd_logger)
+        self.assertEqual(r.status, 0)
+        self.assertIn('lldb-server', r.output)
+
+        # Run an lldb batch session covering the executable, library and
+        # header breakpoints, then continue to completion
+        lldb_batch_args = self._lldb_debug_cpp_example_batch_commands(tempdir)
+        r = runCmd([lldb_script, '--batch'] + lldb_batch_args,
+                   output_log=self._cmd_logger)
+        self.logger.debug("lldb batch output: %s", r.output)
+        self.assertEqual(r.status, 0)
+        self._lldb_debug_cpp_example_check(r.output, magic_string)
+
+        # Stop lldb-server
+        r = runCmd(lldb_server_script + ' stop', output_log=self._cmd_logger)
+        self.assertEqual(r.status, 0)
+
+        # Verify lldb-server is no longer running
+        r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
+                   output_log=self._cmd_logger)
+        self.assertEqual(r.status, 0)
+        self.assertNotIn('lldb-server', r.output)
+
+    @OETestTag("runqemu")
+    def test_devtool_ide_sdk_none_cmake_clang(self):
+        """Verify ide=none generates correct LLDB scripts for a clang cmake recipe.
+
+        Uses cmake-example-clang (TOOLCHAIN = "clang") which is built with the
+        clang toolchain.  devtool ide-sdk with --ide=none should produce:
+          - lldb_server_<port>_<binary>_multi  (start/stop script)
+          - lldbinit/lldbinit_<port>_<binary>  (platform connect + source maps)
+          - lldb_<port>_<binary>               (lldb wrapper)
+
+        The test verifies that lldb-server can be started via the generated
+        script, and that a basic lldb --batch debugging session reaches main.
+        """
+        recipe_name = 'cmake-example-clang'
+        example_exe = 'cmake-example-clang'
+        build_file = 'CMakeLists.txt'
+        testimage = 'oe-selftest-image'
+
+        self._check_workspace()
+        self._write_bb_config([recipe_name], extra_packages=['lldb-server'])
+        self._check_runqemu_prerequisites()
+        bitbake(testimage)
+        with runqemu(testimage, runqemuparams='nographic') as qemu:
+            tempdir = self._devtool_ide_sdk_recipe(recipe_name, build_file, testimage)
+            bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % (
+                recipe_name, testimage, qemu.ip)
+            runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger)
+
+            # Verify cmake preset still works (build system unchanged)
+            compile_cmd = self._verify_cmake_preset(tempdir)
+
+            # Verify install && deploy script exists
+            # (_verify_install_script_code opens .vscode/tasks.json which is not
+            # generated for ide=none; check the script path directly instead)
+            recipe_id, _ = self._get_recipe_ids(recipe_name)
+            scripts_dir = self._workspace_scripts_dir(recipe_name)
+            self.assertExists(os.path.join(
+                scripts_dir, 'install_and_deploy_' + recipe_id))
+
+            # Verify LLDB scripts were generated (not GDB scripts)
+            binary_pretty = 'usr-bin-' + example_exe
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldb_server_1234_%s_multi' % binary_pretty))
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldbinit', 'lldbinit_1234_%s' % binary_pretty))
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldb_1234_%s' % binary_pretty))
+            # No GDB scripts should have been generated for a clang recipe
+            self.assertFalse(os.path.exists(os.path.join(
+                scripts_dir, 'gdbserver_1234_%s_multi' % binary_pretty)),
+                'gdbserver script should not be generated for clang recipe')
+
+            # Verify the oe-scripts sym-link is valid
+            self.assertEqual(self._workspace_scripts_dir(recipe_name),
+                             self._sources_scripts_dir(tempdir))
+
+            # Verify end-to-end lldb debugging, before and after a code
+            # change/recompile/redeploy cycle (see _lldb_cross_debugging_multi)
+            self._lldb_cross_debugging_multi(
+                tempdir, recipe_name, compile_cmd,
+                lambda magic_string: self._lldb_none_debugging_multi(
+                    tempdir, qemu, recipe_name, example_exe, magic_string))
+
+    @OETestTag("runqemu")
+    def test_devtool_ide_sdk_none_meson_clang(self):
+        """Verify ide=none generates correct LLDB scripts for a clang meson recipe.
+
+        This is the meson/ninja counterpart of test_devtool_ide_sdk_none_cmake_clang.
+        It is required in addition to the cmake/clang test because meson/ninja
+        (unlike cmake here) invoke the compiler with source paths relative to
+        the build directory, which is what previously caused source-level
+        breakpoints to be left unresolved (pending) for devtool workspaces due
+        to -fdebug-prefix-map/-ffile-prefix-map underflow; see
+        _lldb_none_debugging_multi.
+
+        Uses meson-example-clang (TOOLCHAIN = "clang").
+        """
+        recipe_name = 'meson-example-clang'
+        example_exe = 'mesonex-clang'
+        build_file = 'meson.build'
+        testimage = 'oe-selftest-image'
+
+        self._check_workspace()
+        self._write_bb_config([recipe_name], extra_packages=['lldb-server'])
+        self._check_runqemu_prerequisites()
+
+        # Build image with debug settings (lldb-server for clang) before starting QEMU
+        tempdir = self._devtool_ide_sdk_recipe(recipe_name, build_file, testimage)
+        runCmd('devtool ide-sdk %s %s -c --ide=none' % (recipe_name, testimage),
+               output_log=self._cmd_logger)
+
+        with runqemu(testimage, runqemuparams='nographic') as qemu:
+            # Re-run with actual QEMU IP; image is already built
+            bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --skip-bitbake --ide=none' % (
+                recipe_name, testimage, qemu.ip)
+            runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger)
+
+            # Verify the meson build system still works (unchanged by clang/lldb support)
+            compile_cmd = self._verify_meson_build(tempdir, recipe_name)
+
+            # Verify install && deploy script exists
+            # (_verify_install_script_code opens .vscode/tasks.json which is not
+            # generated for ide=none; check the script path directly instead)
+            recipe_id, _ = self._get_recipe_ids(recipe_name)
+            scripts_dir = self._workspace_scripts_dir(recipe_name)
+            self.assertExists(os.path.join(
+                scripts_dir, 'install_and_deploy_' + recipe_id))
+
+            # Verify LLDB scripts were generated (not GDB scripts)
+            binary_pretty = 'usr-bin-' + example_exe
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldb_server_1234_%s_multi' % binary_pretty))
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldbinit', 'lldbinit_1234_%s' % binary_pretty))
+            self.assertExists(os.path.join(
+                scripts_dir, 'lldb_1234_%s' % binary_pretty))
+            # No GDB scripts should have been generated for a clang recipe
+            self.assertFalse(os.path.exists(os.path.join(
+                scripts_dir, 'gdbserver_1234_%s_multi' % binary_pretty)),
+                'gdbserver script should not be generated for clang recipe')
+
+            # Verify the oe-scripts sym-link is valid
+            self.assertEqual(self._workspace_scripts_dir(recipe_name),
+                             self._sources_scripts_dir(tempdir))
+
+            # Verify end-to-end lldb debugging, before and after a code
+            # change/recompile/redeploy cycle (see _lldb_cross_debugging_multi)
+            self._lldb_cross_debugging_multi(
+                tempdir, recipe_name, compile_cmd,
+                lambda magic_string: self._lldb_none_debugging_multi(
+                    tempdir, qemu, recipe_name, example_exe, magic_string))
+
     def test_devtool_ide_sdk_plugins(self):
         """Test that devtool ide-sdk can use plugins from other layers."""