https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/210829
>From 082ed9812ad47685a1d110b2c542724fb100f0bb Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Mon, 20 Jul 2026 23:11:58 +0100 Subject: [PATCH] [lldb-dap] Migrate the completion request Drop the Scenario dataclass. as it seems already clear what we are testing for. --- .../completions/TestDAP_completions.py | 466 ++++++++---------- 1 file changed, 205 insertions(+), 261 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py index dd5fdcc4bbad4..73023e8e3cd01 100644 --- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py +++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py @@ -5,36 +5,16 @@ # FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8 from __future__ import annotations -import json -from typing import Optional -import lldbdap_testcase -from dataclasses import dataclass, replace, asdict +from typing import Optional, Union + from lldbsuite.test.decorators import skipIf from lldbsuite.test.lldbtest import line_number - - -@dataclass(frozen=True) -class CompletionItem: - label: str - text: Optional[str] = None - detail: Optional[str] = None - start: Optional[int] = None - length: int = 0 - - def __repr__(self): - # use json as it easier to see the diff on failure. - return json.dumps(asdict(self), indent=4) - - def clone(self, **kwargs) -> CompletionItem: - """Creates a copy of this CompletionItem with specified fields modified.""" - return replace(self, **kwargs) - - -@dataclass(frozen=True) -class Scenario: - input: str - expected: set[CompletionItem] - not_expected: Optional[set[CompletionItem]] = None +from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase, DAPTestSession +from lldbsuite.test.tools.lldb_dap.types import ( + CompletionItem, + LaunchArgs, + StoppedReason, +) session_completion = CompletionItem( @@ -67,46 +47,52 @@ class Scenario: # Older version of libcxx produce slightly different typename strings for # templates like vector. @skipIf(compiler="clang", compiler_version=["<", "16.0"]) -class TestDAP_completions(lldbdap_testcase.DAPTestCaseBase): - def verify_completions(self, case: Scenario): - completions = { - CompletionItem(**comp) - for comp in self.dap_server.get_completions(case.input) - } - - # handle expected completions - for exp_comp in case.expected: - self.assertIn( - exp_comp, completions, f"\nCompletion for input: {case.input}" - ) - - # unexpected completions - for not_exp_comp in case.not_expected or set(): - with self.subTest(f"Not expected completion : {not_exp_comp}"): - self.assertNotIn(not_exp_comp, completions) +class TestDAP_completions(DAPTestCaseBase): + def verify_completions( + self, + input: str, + *, + expected: Union[CompletionItem, set[CompletionItem]], + not_expected: Optional[Union[CompletionItem, set[CompletionItem]]] = None, + frame_id: Optional[int] = None, + ): + if isinstance(expected, CompletionItem): + expected = {expected} + if isinstance(not_expected, CompletionItem): + not_expected = {not_expected} + + completions = set(self._session.get_completions(input, frameId=frame_id)) + + for item in expected: + self.assertIn(item, completions, f"\nCompletion for input: {input}") + + for item in not_expected or set(): + with self.subTest(f"Not expected completion : {item}"): + self.assertNotIn(item, completions) def setup_debuggee(self): + """Creates and returns the session and breakpoint stop event.""" program = self.getBuildArtifact("a.out") source = "main.cpp" - self.build_and_launch(program) - self.set_source_breakpoints( - source, - [ - line_number(source, "// breakpoint 1"), - line_number(source, "// breakpoint 2"), - ], - ) + session = self.build_and_create_session() + self._session = session + with session.configure(LaunchArgs(program)) as ctx: + session.resolve_source_breakpoints( + source, + [ + line_number(source, "// breakpoint 1"), + line_number(source, "// breakpoint 2"), + ], + ) + return session, session.verify_stopped_on_breakpoint(after=ctx.process_event) - def verify_non_ascii_completion(self, alias_cmd: str): + def verify_non_ascii_completion(self, session: DAPTestSession, alias_cmd: str): """Creates an command alias for the `next` command and verify if it has completion for the command and its help. It assumes we are in command mode in the repl. """ - res = self.dap_server.request_evaluate( - f"command alias {alias_cmd} next", context="repl" - ) - self.assertTrue(res["success"]) + session.evaluate(f"command alias {alias_cmd} next", context="repl") part = alias_cmd[:2] # first two characters part_codeunits = len(part.encode("utf-16-le")) // 2 @@ -116,313 +102,271 @@ def verify_non_ascii_completion(self, alias_cmd: str): label=alias_cmd, detail=next_detail, length=part_codeunits ) - # complete the command - self.verify_completions(Scenario(input=part, expected={expected_item})) - # complete the help - self.verify_completions( - Scenario(input=f"help {part}", expected={expected_item}) - ) + # Verify the command and it's help. + self.verify_completions(part, expected=expected_item) + self.verify_completions(f"help {part}", expected=expected_item) - # remove the alias - res = self.dap_server.request_evaluate( - f"command unalias {alias_cmd}", context="repl" - ) - self.assertTrue(res["success"]) + session.evaluate(f"command unalias {alias_cmd}", context="repl") def test_command_completions(self): - """ - Tests completion requests for lldb commands, within "repl-mode=command" - """ - self.setup_debuggee() - self.continue_to_next_stop() + """Tests completion requests for lldb commands, within "repl-mode=command".""" + session, _ = self.setup_debuggee() + stop_event = session.continue_to_next_stop(exp_reason=StoppedReason.BREAKPOINT) - res = self.dap_server.request_evaluate( - "`lldb-dap repl-mode command", context="repl" - ) - self.assertTrue(res["success"]) + session.evaluate("`lldb-dap repl-mode command", context="repl") + top_frame_id = session.top_frame_from(stop_event).frame.id - # Provides completion for top-level commands + # Provides completion for top-level commands. self.verify_completions( - Scenario( - input="se", - expected={ - session_completion.clone(length=2), - settings_completion.clone(length=2), - }, - ) - ) - # Provides completions for sub-commands + "se", + expected={ + session_completion.clone(length=2), + settings_completion.clone(length=2), + }, + frame_id=top_frame_id, + ) + # Provides completions for sub-commands. self.verify_completions( - Scenario( - input="memory ", - expected={ - CompletionItem( - label="read", - detail="Read from the memory of the current target process.", - ), - CompletionItem( - label="region", - detail="Get information on the memory region containing an address " - "in the current target process.\nIf this command is given an " - "<address-expression> once and then repeated without options, " - "it will try to print the memory region that follows the " - "previously printed region. The command can be repeated " - "until the end of the address range is reached.", - ), - }, - ), + "memory ", + expected={ + CompletionItem( + label="read", + detail="Read from the memory of the current target process.", + ), + CompletionItem( + label="region", + detail="Get information on the memory region containing an address " + "in the current target process.\nIf this command is given an " + "<address-expression> once and then repeated without options, " + "it will try to print the memory region that follows the " + "previously printed region. The command can be repeated " + "until the end of the address range is reached.", + ), + }, + frame_id=top_frame_id, ) # Provides completions for parameter values of commands self.verify_completions( - Scenario( - input="`log enable ", expected={CompletionItem(label="gdb-remote")} - ) + "`log enable ", + expected=CompletionItem(label="gdb-remote"), + frame_id=top_frame_id, ) # Also works if the escape prefix is used self.verify_completions( - Scenario(input="`mem", expected={memory_completion.clone(length=3)}) + "`mem", + expected=memory_completion.clone(length=3), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="`", - expected={session_completion, settings_completion, memory_completion}, - ) + "`", + expected={session_completion, settings_completion, memory_completion}, + frame_id=top_frame_id, ) # Completes an incomplete quoted token self.verify_completions( - Scenario( - input='setting "se', - expected={ - CompletionItem( - label="set", - detail="Set the value of the specified debugger setting.", - length=3, - ) - }, - ) + 'setting "se', + expected=CompletionItem( + label="set", + detail="Set the value of the specified debugger setting.", + length=3, + ), + frame_id=top_frame_id, ) # Completes an incomplete quoted token self.verify_completions( - Scenario(input="'mem", expected={memory_completion.clone(length=4)}) + "'mem", + expected=memory_completion.clone(length=4), + frame_id=top_frame_id, ) # Completes expressions with quotes inside self.verify_completions( - Scenario( - input='expr " "; typed', - expected={CompletionItem(label="typedef", length=5)}, - ) + 'expr " "; typed', + expected=CompletionItem(label="typedef", length=5), + frame_id=top_frame_id, ) # Provides completions for commands, but not variables self.verify_completions( - Scenario( - input="var", - expected={command_var_completion}, - not_expected={variable_var_completion}, - ) + "var", + expected=command_var_completion, + not_expected=variable_var_completion, + frame_id=top_frame_id, ) # Completes partial completion self.verify_completions( - Scenario( - input="plugin list ar", - expected={CompletionItem(label="architecture", length=2)}, - ) + "plugin list ar", + expected=CompletionItem(label="architecture", length=2), + frame_id=top_frame_id, ) - # Complete custom command with non ascii character. - self.verify_non_ascii_completion("n€xt") # 2 bytes £ - self.verify_non_ascii_completion("n£xt") # 3 bytes € - self.verify_non_ascii_completion("n💩xt") # 4 bytes 💩 - self.verify_non_ascii_completion("√∂xt") # start with non ascii - self.verify_non_ascii_completion("one_seç") # ends with non ascii + # Complete custom command with non-ASCII character. + self.verify_non_ascii_completion(session, "n€xt") # 3 bytes € + self.verify_non_ascii_completion(session, "n£xt") # 2 bytes £ + self.verify_non_ascii_completion(session, "n💩xt") # 4 bytes 💩 + self.verify_non_ascii_completion(session, "√∂xt") # starts with non-ASCII + self.verify_non_ascii_completion(session, "one_seç") # ends with non-ASCII def test_variable_completions(self): - """ - Tests completion requests in "repl-mode=variable" - """ - self.setup_debuggee() - self.continue_to_next_stop() + """Tests completion requests in "repl-mode=variable" """ - res = self.dap_server.request_evaluate( - "`lldb-dap repl-mode variable", context="repl" + session, stop_event = self.setup_debuggee() + top_frame_id = session.top_frame_from(stop_event).frame.id + session.evaluate( + "`lldb-dap repl-mode variable", context="repl", frameId=top_frame_id ) - self.assertTrue(res["success"]) - # Provides completions for varibles, but not command + # Provides completions for variables, but not command. self.verify_completions( - Scenario( - input="var", - expected={variable_var_completion}, - not_expected={command_var_completion}, - ) + "var", + expected=variable_var_completion, + not_expected=command_var_completion, + frame_id=top_frame_id, ) - # We stopped inside `fun`, so we shouldn't see variables from main + # We stopped inside `fun`, so we shouldn't see variables from main. self.verify_completions( - Scenario( - input="var", - expected={variable_var_completion}, - not_expected={ - variable_var1_completion.clone(length=3), - variable_var2_completion.clone(length=3), - }, - ) + "var", + expected=variable_var_completion, + not_expected={ + variable_var1_completion.clone(length=3), + variable_var2_completion.clone(length=3), + }, + frame_id=top_frame_id, ) - # We should see global keywords but not variables inside main + # We should see global keywords but not variables inside main. self.verify_completions( - Scenario( - input="str", - expected={CompletionItem(label="struct", length=3)}, - not_expected={str1_completion.clone(length=3)}, - ) + "str", + expected=CompletionItem(label="struct", length=3), + not_expected=str1_completion.clone(length=3), + frame_id=top_frame_id, ) - self.continue_to_next_stop() + stop_event = session.continue_to_next_stop() + top_frame_id = session.top_frame_from(stop_event).frame.id # We stopped in `main`, so we should see variables from main but - # not from the other function + # not from the other function. self.verify_completions( - Scenario( - input="var", - expected={ - variable_var1_completion.clone(length=3), - variable_var2_completion.clone(length=3), - }, - not_expected={ - variable_var_completion.clone(length=3), - }, - ) + "var", + expected={ + variable_var1_completion.clone(length=3), + variable_var2_completion.clone(length=3), + }, + not_expected=variable_var_completion.clone(length=3), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="str", - expected={ - CompletionItem(label="struct", length=3), - str1_completion.clone(length=3), - }, - ) + "str", + expected={ + CompletionItem(label="struct", length=3), + str1_completion.clone(length=3), + }, + frame_id=top_frame_id, ) - self.assertIsNotNone(self.dap_server.get_completions("ƒ")) + self.assertIsNotNone(session.get_completions("ƒ", top_frame_id)) # Test utf8 after ascii. - # TODO - self.dap_server.get_completions("mƒ") + session.get_completions("mƒ", top_frame_id) # Completion also works for more complex expressions self.verify_completions( - Scenario( - input="foo1.v", - expected={CompletionItem(label="foo1.var1", detail="int", length=6)}, - ) + "foo1.v", + expected=CompletionItem(label="foo1.var1", detail="int", length=6), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="foo1.my_bar_object.v", - expected={ - CompletionItem( - label="foo1.my_bar_object.var1", detail="int", length=20 - ) - }, - ) + "foo1.my_bar_object.v", + expected=CompletionItem( + label="foo1.my_bar_object.var1", detail="int", length=20 + ), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="foo1.var1 + foo1.v", - expected={CompletionItem(label="foo1.var1", detail="int", length=6)}, - ) + "foo1.var1 + foo1.v", + expected=CompletionItem(label="foo1.var1", detail="int", length=6), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="foo1.var1 + v", - expected={CompletionItem(label="var1", detail="int &", length=1)}, - ) + "foo1.var1 + v", + expected=CompletionItem(label="var1", detail="int &", length=1), + frame_id=top_frame_id, ) # should correctly handle spaces between objects and member operators self.verify_completions( - Scenario( - input="foo1 .v", - expected={CompletionItem(label=".var1", detail="int", length=2)}, - not_expected={CompletionItem(label=".var2", detail="int", length=2)}, - ) + "foo1 .v", + expected=CompletionItem(label=".var1", detail="int", length=2), + not_expected=CompletionItem(label=".var2", detail="int", length=2), + frame_id=top_frame_id, ) self.verify_completions( - Scenario( - input="foo1 . v", - expected={CompletionItem(label="var1", detail="int", length=1)}, - not_expected={CompletionItem(label="var2", detail="int", length=1)}, - ) + "foo1 . v", + expected=CompletionItem(label="var1", detail="int", length=1), + not_expected=CompletionItem(label="var2", detail="int", length=1), + frame_id=top_frame_id, ) # Even in variable mode, we can still use the escape prefix self.verify_completions( - Scenario(input="`mem", expected={memory_completion.clone(length=3)}) + "`mem", + expected=memory_completion.clone(length=3), + frame_id=top_frame_id, ) def test_auto_completions(self): - """ - Tests completion requests in "repl-mode=auto" - """ - self.setup_debuggee() - - res = self.dap_server.request_evaluate( - "`lldb-dap repl-mode auto", context="repl" - ) - self.assertTrue(res["success"]) - - self.continue_to_next_stop() + """Tests completion requests in "repl-mode=auto".""" + session, stop_event = self.setup_debuggee() + session.evaluate("`lldb-dap repl-mode auto", context="repl") + top_frame_id = session.top_frame_from(stop_event).frame.id # Stopped at breakpoint 1 # 'var' variable is in scope, completions should not show any warning. - self.dap_server.get_completions("var ") - self.continue_to_next_stop() + # We check this at the end of the test. + session.get_completions("var ", top_frame_id) + stop_event = session.continue_to_next_stop(exp_reason=StoppedReason.BREAKPOINT) - # We are stopped inside `main`. Variables `var1` and `var2` are in scope. + # We stopped in `main` function. Variables `var1` and `var2` are in scope. # Make sure, we offer all completions self.verify_completions( - Scenario( - input="va", - expected={ - command_var_completion.clone(length=2), - variable_var1_completion.clone(length=2), - variable_var2_completion.clone(length=2), - }, - ) + "va", + expected={ + command_var_completion.clone(length=2), + variable_var1_completion.clone(length=2), + variable_var2_completion.clone(length=2), + }, + frame_id=top_frame_id, ) # If we are using the escape prefix, only commands are suggested, but no variables self.verify_completions( - Scenario( - input="`va", - expected={ - command_var_completion.clone(length=2), - }, - not_expected={ - variable_var1_completion.clone(length=2), - variable_var2_completion.clone(length=2), - }, - ) + "`va", + expected=command_var_completion.clone(length=2), + not_expected={ + variable_var1_completion.clone(length=2), + variable_var2_completion.clone(length=2), + }, + frame_id=top_frame_id, ) # TODO: Note we are not checking the result because the `expression --` command adds an extra character # for non ascii variables. - self.assertIsNotNone(self.dap_server.get_completions("ƒ")) + self.assertTrue(session.get_completions("ƒ", top_frame_id)) - self.continue_to_exit() - console_str = self.get_console() + session.continue_to_exit() + console_str = session.get_console() # we check in console to avoid waiting for output event. self.assertNotIn( "Expression 'var' is both an LLDB command and variable", console_str _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
