[Lldb-commits] [lldb] cf7a612 - [lldb-dap] Migrate the Completions tests (#210829)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 22 01:58:26 PDT 2026
Author: Ebuka Ezike
Date: 2026-07-22T09:58:20+01:00
New Revision: cf7a6122a1247353ba29f5384885de9e42bd1e14
URL: https://github.com/llvm/llvm-project/commit/cf7a6122a1247353ba29f5384885de9e42bd1e14
DIFF: https://github.com/llvm/llvm-project/commit/cf7a6122a1247353ba29f5384885de9e42bd1e14.diff
LOG: [lldb-dap] Migrate the Completions tests (#210829)
Drop the Scenario dataclass as it is now clear what we are testing for.
Added:
Modified:
lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
Removed:
################################################################################
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
-
-
- at 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)
-
-
- at 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
diff erent 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
More information about the lldb-commits
mailing list