================
@@ -0,0 +1,1699 @@
+# FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8
+from __future__ import annotations
+
+import base64
+import dataclasses
+import logging
+import os
+import unittest
+from dataclasses import dataclass
+from pathlib import Path
+from typing import (
+    Callable,
+    Iterable,
+    Iterator,
+    Literal,
+    Optional,
+    Sequence,
+    TypeVar,
+    cast,
+)
+
+from .dap_types import (
+    AttachArgs,
+    Breakpoint,
+    BreakpointEvent,
+    BreakpointLocationsArgs,
+    CompletionsArgs,
+    ConfigurationDoneArgs,
+    ContinueArgs,
+    DataBreakpoint,
+    DataBreakpointInfoArgs,
+    DisassembleArgs,
+    DisconnectArgs,
+    EmptyBodyResponse,
+    ErrorResponse,
+    EvaluateArgs,
+    EvaluateContext,
+    EvaluateResponse,
+    Event,
+    EventName,
+    ExceptionFilterOptions,
+    ExceptionInfoArgs,
+    ExceptionOptions,
+    ExitedEvent,
+    FunctionBreakpoint,
+    InitializeArgs,
+    InitializedEvent,
+    InstructionBreakpoint,
+    InvalidatedEvent,
+    LaunchArgs,
+    LocationsArgs,
+    MemoryEvent,
+    ModuleEvent,
+    ModuleReason,
+    ModulesArgs,
+    NextArgs,
+    OutputCategory,
+    OutputEvent,
+    ProcessEvent,
+    ReadMemoryArgs,
+    ReadMemoryResponse,
+    Response,
+    RestartArgs,
+    Scope,
+    ScopesArgs,
+    SetBreakpointsArgs,
+    SetDataBreakpointsArgs,
+    SetExceptionBreakpointsArgs,
+    SetFunctionBreakpointsArgs,
+    SetInstructionBreakpointsArgs,
+    SetVariableArgs,
+    SetVariableResponse,
+    Source,
+    SourceBreakpoint,
+    StackFrame,
+    StackFrameFormat,
+    StackTraceArgs,
+    StepInArgs,
+    StepOutArgs,
+    SteppingGranularity,
+    StoppedEvent,
+    StoppedReason,
+    TerminatedEvent,
+    ThreadsArgs,
+    ValueFormat,
+    Variable,
+    VariablePresentationHint,
+    VariablesArgs,
+    WriteMemoryArgs,
+)
+from .session import PendingResponse, Session
+from .utils import DebugAdapter, SubProcessSpawner
+
+T = TypeVar("T")
+
+
+class ThreadContext:
+    """Lazy view of a debug adapter thread.
+
+    Thread ids do not have a limited lifetime, so this context is long-lived.
+    It can be reused after continue and stepXXX requests.
+    """
+
+    def __init__(self, thread_id: int, session: DAPTestSession):
+        self._thread_id: int = thread_id
+        self._session: DAPTestSession = session
+
+    @property
+    def thread_id(self) -> int:
+        return self._thread_id
+
+    def step_in(
+        self,
+        *,
+        targetId: Optional[int] = None,
+        granularity: SteppingGranularity = "statement",
+    ):
+        return self._session.step_in(
+            threadId=self.thread_id, targetId=targetId, granularity=granularity
+        )
+
+    def step_over(self, *, granularity: SteppingGranularity = "statement"):
+        return self._session.step_over(threadId=self.thread_id, 
granularity=granularity)
+
+    def step_out(self, *, granularity: SteppingGranularity = "statement"):
+        return self._session.step_out(threadId=self.thread_id, 
granularity=granularity)
+
+    def top_frame(
+        self,
+        *,
+        format: Optional[StackFrameFormat] = None,
+    ) -> FrameContext:
+        return self.frames(levels=1, format=format)[0]
+
+    def frames(
+        self,
+        *,
+        startFrame: Optional[int] = None,
+        levels: Optional[int] = None,
+        format: Optional[StackFrameFormat] = None,
+    ) -> list[FrameContext]:
+        args = StackTraceArgs(
+            self._thread_id, startFrame=startFrame, levels=levels, 
format=format
+        )
+        response = self._session.send_request(args).result()
+        generation = self._session._current_stop_generation()
+        return [
+            FrameContext(frame, self._session, generation)
+            for frame in response.body.stackFrames
+        ]
+
+
+class FrameContext:
+    """Lazy view of a stack frame. Valid only within its stop generation."""
+
+    def __init__(self, frame: StackFrame, session: DAPTestSession, generation: 
int):
+        self._frame = frame
+        self._session = session
+        self._generation = generation
+        self._scopes: Optional[list[ScopeContext]] = None
+
+    @property
+    def frame(self) -> StackFrame:
+        self._session._check_stop_generation(self._generation, self)
+        return self._frame
+
+    @property
+    def id(self) -> int:
+        return self.frame.id
+
+    @property
+    def name(self) -> str:
+        return self.frame.name
+
+    def __dir__(self):
+        # Hide the property fields that may call 'ScopesRequest' from the 
debugger.
+        # The python debugger will hang because it is waiting for a response
+        # when viewing the FrameContext.
+        hidden = {"locals", "globals", "registers", "scopes"}
+        return (attr for attr in super().__dir__() if attr not in hidden)
+
+    def source_and_line(self) -> tuple[str, int]:
+        frame = self.frame
+        assert frame.source is not None
+        assert frame.source.path is not None
+        assert frame.line is not None
+        return frame.source.path, frame.line
+
+    def scopes(self) -> list[ScopeContext]:
+        self._session._check_stop_generation(self._generation, self)
+        if self._scopes is None:
+            scope_args = ScopesArgs(frameId=self._frame.id)
+            response = self._session.send_request(scope_args).result()
+            self._scopes = [
+                ScopeContext(scope, self._session, self._generation)
+                for scope in response.body.scopes
+            ]
+        return self._scopes
+
+    def scope(self, name: str) -> ScopeContext:
+        scopes = self.scopes()
+        for scope in scopes:
+            if scope.scope.name == name:
+                return scope
+        scope_names = [scope.scope.name for scope in scopes]
+        self._session.test_case.fail(
+            f"scope '{name}' not in frame scopes: {scope_names}"
+        )
+
+    @property
+    def locals(self) -> ScopeContext:
+        return self.scope("Locals")
+
+    @property
+    def globals(self) -> ScopeContext:
+        return self.scope("Globals")
+
+    @property
+    def registers(self) -> ScopeContext:
+        return self.scope("Registers")
+
+    def evaluate(
+        self,
+        expression: str,
+        *,
+        context: Optional[EvaluateContext] = None,
+        format: Optional[ValueFormat] = None,
+    ):
+        """Evaluates `expression` in this frame's context."""
+        self._session._check_stop_generation(self._generation, self)
+        return self._session.evaluate(
+            expression, frameId=self._frame.id, context=context, format=format
+        )
+
+    def disassemble(self):
+        self._session._check_stop_generation(self._generation, self)
+
+        mem_ref = self._frame.instructionPointerReference
+        if mem_ref is None:
+            self._session.test_case.fail(
+                f"expects 'instructionPointerReference' for frame {self.frame}"
+            )
+        return self._session.disassemble(
+            mem_ref, instructionOffset=0, instructionCount=100
+        )
+
+
+class _VariableContainer:
+    """Shared dict-like behaviour for contexts that hold a variablesReference.
+
+    The optional `_value_format` is passed into every child-fetching
+    `variables` request in the container.
+    A child `VariableContext` inherits its parent's format,
+    so walking `locals.with_format(hex)["pt"]["x"]` keeps hex formatting all
+    the way down without the caller repeating it at each step.
+    """
+
+    _session: DAPTestSession
+    _generation: int
+    _value_format: Optional[ValueFormat] = None
+
+    def _fetch_variables(
+        self,
+        variables_reference: int,
+        *,
+        filter: Optional[Literal["indexed", "named"]] = None,
+        start: Optional[int] = None,
+        count: Optional[int] = None,
+    ) -> list[VariableContext]:
+        self._session._check_stop_generation(self._generation, self)
+        variables = self._session.get_variables(
+            variables_reference,
+            filter=filter,
+            start=start,
+            count=count,
+            format=self._value_format,
+        )
+        return [
+            VariableContext(var, self._session, self._generation, 
self._value_format)
+            for var in variables
+        ]
+
+    def page(
+        self,
+        *,
+        filter: Optional[Literal["indexed", "named"]] = None,
+        start: Optional[int] = None,
+        count: Optional[int] = None,
+    ) -> list[VariableContext]:
+        """Fetch a subset of children with paging/filter arguments.
+
+        Inherits the container's value format.
+        """
+        return self._fetch_variables(
+            self._container_reference(),
+            filter=filter,
+            start=start,
+            count=count,
+        )
+
+    def set(
+        self, name: str, value, *, is_hex: bool = False
+    ) -> SetVariableResponse | ErrorResponse:
+        """Sends a `setVariable` request for a named child."""
+        self._session._check_stop_generation(self._generation, self)
+        return self._session.set_variable(
+            name, value, variablesReference=self._container_reference(), 
is_hex=is_hex
+        )
+
+    def _container_reference(self) -> int:
+        raise NotImplementedError
+
+    def _by_name(self) -> dict[str, VariableContext]:
+        return {child.name: child for child in self._children()}
+
+    def _children(self) -> list[VariableContext]:
+        raise NotImplementedError
+
+    def __getitem__(self, name: str) -> VariableContext:
+        by_name = self._by_name()
+        try:
+            return by_name[name]
+        except KeyError:
+            self._session.test_case.fail(
+                f"'{name}' not found in {self}, has: {list(by_name)}"
+            )
+
+    def __contains__(self, name: object) -> bool:
+        return name in self._by_name()
+
+    def __iter__(self) -> Iterator[VariableContext]:
+        return iter(self._children())
+
+    def __len__(self) -> int:
+        return len(self._children())
+
+    def __str__(self) -> str:
+        return type(self).__name__
+
+
+class ScopeContext(_VariableContainer):
+    """Lazy view of a scope's variables. Valid only within its stop 
generation."""
+
+    def __init__(
+        self,
+        scope: Scope,
+        session: DAPTestSession,
+        generation: int,
+        value_format: Optional[ValueFormat] = None,
+    ):
+        self._scope = scope
+        self._session = session
+        self._generation = generation
+        self._value_format = value_format
+
+    @property
+    def scope(self) -> Scope:
+        self._session._check_stop_generation(self._generation, self)
+        return self._scope
+
+    @property
+    def name(self) -> str:
+        return self.scope.name
+
+    @property
+    def variablesReference(self) -> int:
+        return self.scope.variablesReference
+
+    def variables(self) -> list[VariableContext]:
+        return self._fetch_variables(self._scope.variablesReference)
+
+    def with_format(self, *, is_hex: bool = False) -> ScopeContext:
+        """Return a new ScopeContext that applies the ValueFormat."""
+        value_format = ValueFormat(hex=True) if is_hex else None
+        return ScopeContext(self._scope, self._session, self._generation, 
value_format)
+
+    def _container_reference(self) -> int:
+        return self._scope.variablesReference
+
+    def _children(self) -> list[VariableContext]:
+        return self.variables()
+
+    def __str__(self) -> str:
+        return f"scope '{self._scope.name}'"
+
+
+class VariableContext(_VariableContainer):
+    """Lazy view of a variable and (optionally) its children.
+
+    Valid only within its' stop generation.
+    """
+
+    def __init__(
+        self,
+        variable: Variable,
+        session: DAPTestSession,
+        generation: int,
+        value_format: Optional[ValueFormat] = None,
+    ):
+        self._variable = variable
+        self._session = session
+        self._generation = generation
+        self._value_format = value_format
+
+    @property
+    def variable(self) -> Variable:
+        self._session._check_stop_generation(self._generation, self)
+        return self._variable
+
+    @property
+    def name(self) -> str:
+        return self._variable.name
+
+    @property
+    def value(self) -> str:
+        return self._variable.value
+
+    @property
+    def value_as_int(self) -> int:
+        return self._variable.value_as_int
+
+    @property
+    def type(self) -> Optional[str]:
+        return self._variable.type
+
+    @property
+    def variablesReference(self) -> int:
+        return self._variable.variablesReference
+
+    @property
+    def memoryReference(self) -> Optional[str]:
+        return self._variable.memoryReference
+
+    @property
+    def indexedVariables(self) -> Optional[int]:
+        return self._variable.indexedVariables
+
+    @property
+    def namedVariables(self) -> Optional[int]:
+        return self._variable.namedVariables
+
+    @property
+    def has_children(self) -> bool:
+        return self._variable.variablesReference > 0
+
+    def children(self) -> list[VariableContext]:
+        if not self.has_children:
+            self._session.test_case.fail(
+                f"variable '{self._variable.name}' has no children"
+            )
+        return self._fetch_variables(self._variable.variablesReference)
+
+    def with_format(self, *, is_hex: bool = False) -> VariableContext:
+        """Return a new VariableContext that applies the ValueFormat"""
+        value_format = ValueFormat(hex=True) if is_hex else None
+        return VariableContext(
+            self._variable, self._session, self._generation, value_format
+        )
+
+    def _container_reference(self) -> int:
+        return self._variable.variablesReference
+
+    def _children(self) -> list[VariableContext]:
+        return self.children()
+
+    def __str__(self) -> str:
+        return f"variable '{self._variable.name}'"
+
+
+@dataclass(frozen=True)
+class CapturedOutput:
+    seen_texts: str
+    """The accumulated text until the terminator (included if it was an 
OutputEvent)."""
+    event: Event
+    """The event that terminated the collection"""
+
+
+class _ConfigureContext:
+    """Handles the initial launch sequence handshake.
+
+    Orchestrates the full DAP initialization sequence:
+    On enter:
+        - Request and respond to the `Initialize` command.
+        - Send launch/attach request.
+        - Wait for InitializedEvent.
+
+    In between:
+       The test can set breakpoints or perform any check it needs do.
+
+    On exit:
+        4. Set and verify the pending source and function breakpoints.
+        5. Request and response to configurationDone.
+        6. Wait for ProcessEvent and launch/attach response.
+
+    Example:
+
+      >>> session.configure(LaunchArgs(program="a.out")) as ctx:
+      ...     session.resolve_function_breakpoints(["do_foo"])
+      >>> session.wait_for_breakpoint(after=ctx.process_event)
+    """
+
+    def __init__(
+        self,
+        session: "DAPTestSession",
+        config: LaunchArgs | AttachArgs,
+    ):
+        self._session = session
+        self._config = config
+        self._pending_request: Optional[PendingResponse[EmptyBodyResponse]] = 
None
+
+    def __enter__(self) -> "_ConfigureContext":
+        session = self._session
+        session.test_case.assertFalse(
+            session._state.is_initialized, "session already started."
+        )
+        self.init_response = 
session.initialize_sequence(session.initialize_args)
+        self._pending_request = session.send_request(self._config)
+        session.wait_for_event(InitializedEvent, after=self.init_response)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if exc_type is not None:
+            return False
+
+        session = self._session
+        assert self.init_response is not None
+        assert self._pending_request is not None
+
+        session.verify_configuration_done()
+        self.process_event = session.wait_for_event(
+            ProcessEvent, after=self.init_response
+        )
+
+        self.launch_or_attach_response = self._pending_request.result()
+        return False
+
+
+@dataclass
+class _ExpectCommon:
+    """Shared fields used by both `ExpectVar` and `ExpectEval`.
+    Any attribute set to `None` will be skipped when checking.
+    """
+
+    type: Optional[str] = None
+    variables_reference: Optional[int] = None
+    named_variables: Optional[int] = None
+    indexed_variables: Optional[int] = None
+    read_only: bool = False
+
+    has_var_ref: Optional[bool] = None
+    has_mem_ref: Optional[bool] = None
+    has_loc_ref: Optional[bool] = None
----------------
DrSergei wrote:

I think we can use bool here

https://github.com/llvm/llvm-project/pull/203978
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to