https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/175048
>From e6988e729f084a2775d18c68bb4dbc749133db67 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Mon, 29 Dec 2025 01:51:47 +0000 Subject: [PATCH 1/5] [lldb-dap] fix redirection with program args --- .../tools/lldb-dap/Handler/RequestHandler.cpp | 24 +++++++++---------- lldb/tools/lldb-dap/JSONUtils.cpp | 20 +++++++++------- .../lldb-dap/Protocol/ProtocolRequests.cpp | 9 ++++++- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 4 ++-- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index b06a43d7421c1..8be83fac2c117 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -61,25 +61,25 @@ static uint32_t SetLaunchFlag(uint32_t flags, bool flag, static void SetupIORedirection(const std::vector<std::optional<std::string>> &stdio, lldb::SBLaunchInfo &launch_info) { - size_t n = std::max(stdio.size(), static_cast<size_t>(3)); - for (size_t i = 0; i < n; i++) { - std::optional<std::string> path; - if (stdio.size() <= i) - path = stdio.back(); - else - path = stdio[i]; - if (!path) + for (const auto &[idx, value_opt] : llvm::enumerate(stdio)) { + if (!value_opt) continue; - switch (i) { + const std::string &path = value_opt.value(); + assert(!path.empty() && "paths should not be empty"); + + const int fd = static_cast<int>(idx); + switch (fd) { case 0: - launch_info.AddOpenFileAction(i, path->c_str(), true, false); + launch_info.AddOpenFileAction(STDIN_FILENO, path.c_str(), true, false); break; case 1: + launch_info.AddOpenFileAction(STDOUT_FILENO, path.c_str(), false, true); + break; case 2: - launch_info.AddOpenFileAction(i, path->c_str(), false, true); + launch_info.AddOpenFileAction(STDERR_FILENO, path.c_str(), false, true); break; default: - launch_info.AddOpenFileAction(i, path->c_str(), true, true); + launch_info.AddOpenFileAction(fd, path.c_str(), true, true); break; } } diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 9a2142cd847ab..4bf9d6e19ad80 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -720,20 +720,24 @@ llvm::json::Object CreateRunInTerminalReverseRequest( req_args.push_back("--debugger-pid"); req_args.push_back(std::to_string(debugger_pid)); } - req_args.push_back("--launch-target"); - req_args.push_back(program.str()); + if (!stdio.empty()) { - req_args.push_back("--stdio"); + req_args.emplace_back("--stdio"); + std::stringstream ss; + std::string_view delimiter; for (const std::optional<std::string> &file : stdio) { + ss << std::exchange(delimiter, ":"); if (file) - ss << *file; - ss << ":"; + ss << file.value(); } - std::string files = ss.str(); - files.pop_back(); - req_args.push_back(std::move(files)); + req_args.push_back(ss.str()); } + + // WARNING: Any argument added after `launch-target` is passed to to the + // target. + req_args.emplace_back("--launch-target"); + req_args.push_back(program.str()); req_args.insert(req_args.end(), args.begin(), args.end()); run_in_terminal_args.try_emplace("args", req_args); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index c3225f6ba0e35..1f8283eba86fa 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -296,7 +296,7 @@ bool fromJSON(const json::Value &Params, Console &C, json::Path P) { bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, json::Path P) { json::ObjectMapper O(Params, P); - bool success = + const bool success = O && fromJSON(Params, LRA.configuration, P) && O.mapOptional("noDebug", LRA.noDebug) && O.mapOptional("launchCommands", LRA.launchCommands) && @@ -310,6 +310,13 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, O.mapOptional("stdio", LRA.stdio) && parseEnv(Params, LRA.env, P); if (!success) return false; + + for (std::optional<std::string> &io_path : LRA.stdio) { + // set empty paths to null. + if (io_path && llvm::StringRef(*io_path).trim().empty()) + io_path.reset(); + } + // Validate that we have a well formed launch request. if (!LRA.launchCommands.empty() && LRA.console != protocol::eConsoleInternal) { diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index e3b9d57e7d3a1..90168dec699b7 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -288,8 +288,8 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, if (!stdio.empty()) { llvm::SmallVector<llvm::StringRef, 3> files; stdio.split(files, ':'); - while (files.size() < 3) - files.push_back(files.back()); + constexpr size_t num_of_stdio = 3; + files.resize(std::max(num_of_stdio, files.size())); if (llvm::Error err = SetupIORedirection(files)) return err; } else if ((isatty(STDIN_FILENO) != 0) && >From c4e0021ae5077af21bce254a9ed0f0019727b5d7 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Tue, 30 Dec 2025 16:21:33 +0000 Subject: [PATCH 2/5] [lldb-dap] Update the documentation --- lldb/tools/lldb-dap/README.md | 2 +- lldb/tools/lldb-dap/extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index 9e4fb83cebed0..8c7066a3b551b 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -265,7 +265,7 @@ contain the following key/value pairs: | **stopOnEntry** | boolean | | Whether to stop program immediately after launching. | **runInTerminal** (deprecated) | boolean | | Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs. | **console** | string | | Specify where to launch the program: internal console (`internalConsole`), integrated terminal (`integratedTerminal`) or external terminal (`externalTerminal`). Supported from lldb-dap 21.0 version. -| **stdio** | [string] | | The stdio property specifies the redirection targets for the debuggee's stdio streams. A null value redirects a stream to the default debug terminal. String can be a path to file, named pipe or TTY device. If less than three values are provided, the list will be padded with the last value. Specifying more than three values will create additional file descriptors (4, 5, etc.). Supported from lldb-dap 22.0 version. +| **stdio** | [string] | | Redirects the debuggee's standard I/O (stdin, stdout, stderr) to a file path, named pipe, or TTY device.<br/>Use null to redirect a stream to the default debug terminal.<br/>The first three values map to stdin, stdout, and stderr respectively, Additional values create extra file descriptors (4, 5, etc.).<br/><br/>Example: `stdio: [null, "./output_file"]`, the debuggee uses the default terminal for `stdin` and `stderr`, but writes `stdout` to `./output_file`.<br/>Added in version 22.0. | **launchCommands** | [string] | | LLDB commands executed to launch the program. For JSON configurations of `"type": "attach"`, the JSON configuration can contain diff --git a/lldb/tools/lldb-dap/extension/package.json b/lldb/tools/lldb-dap/extension/package.json index bcc38aec260d6..bc1e1aca3751d 100644 --- a/lldb/tools/lldb-dap/extension/package.json +++ b/lldb/tools/lldb-dap/extension/package.json @@ -639,7 +639,7 @@ "null" ] }, - "description": "The stdio property specifies the redirection targets for the debuggee's stdio streams. A null value redirects a stream to the default debug terminal. String can be a path to file, named pipe or TTY device. If less than three values are provided, the list will be padded with the last value. Specifying more than three values will create additional file descriptors (4, 5, etc.).", + "description": "Redirects the debuggee's standard I/O (stdin, stdout, stderr) to a file path, named pipe, or TTY device.\nUse null to redirect a stream to the default debug terminal.\nThe first three values map to stdin, stdout, and stderr respectively.\nAdditional values create extra file descriptors (4, 5, etc.).\n\nExample: stdio: [null, \"./output_file\"], the debuggee uses the default terminal for stdin and stderr, but writes stdout to `./output_file`.\nAdded in version 22.0.", "default": [] }, "timeout": { >From 171e3fa703d92a29328186f5e1a4192b26f0745f Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Mon, 29 Dec 2025 18:22:18 +0000 Subject: [PATCH 3/5] [lldb] Add new launch redirection test --- .../Python/lldbsuite/test/lldbtest.py | 13 +- .../test/tools/lldb-dap/dap_server.py | 14 +- .../API/tools/lldb-dap/launch/io/Makefile | 3 + .../lldb-dap/launch/io/TestDAP_launch_io.py | 356 ++++++++++++++++++ .../API/tools/lldb-dap/launch/io/main.cpp | 24 ++ 5 files changed, 406 insertions(+), 4 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/launch/io/Makefile create mode 100644 lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py create mode 100644 lldb/test/API/tools/lldb-dap/launch/io/main.cpp diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 6409e1f742fd6..c98fb18262b56 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -406,14 +406,25 @@ def terminate(self): class _LocalProcess(_BaseProcess): def __init__(self, trace_on): - self._proc = None + self._proc: Popen | None = None self._trace_on = trace_on self._delayafterterminate = 0.1 @property def pid(self): + assert self._proc is not None, "No process" return self._proc.pid + @property + def stdout(self): + assert self._proc is not None, "No process" + return self._proc.stdout + + @property + def stderr(self): + assert self._proc is not None, "No process" + return self._proc.stderr + def launch(self, executable, args, extra_env, **kwargs): env = None if extra_env: diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 992e75244b67a..1349393b6caab 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -3,6 +3,7 @@ # FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8 from __future__ import annotations +from typing import Protocol import argparse import binascii import dataclasses @@ -42,7 +43,11 @@ # See lldbtest.Base.spawnSubprocess, which should help ensure any processes # created by the DAP client are terminated correctly when the test ends. -SpawnHelperCallback = Callable[[str, List[str], List[str]], subprocess.Popen] +class SpawnHelperCallback(Protocol): + def __call__( + self, executable: str, args: List[str], extra_env: List[str], **kwargs + ) -> subprocess.Popen: ... + ## DAP type references @@ -265,6 +270,7 @@ def __init__( self.module_events: List[Dict] = [] self.sequence: int = 1 self.output: Dict[str, str] = {} + self.reverse_process: Optional[subprocess.Popen] = None # debuggee state self.threads: Optional[dict] = None @@ -525,8 +531,10 @@ def _handle_reverse_request(self, request: Request) -> None: assert self.spawn_helper is not None, "Not configured to spawn subprocesses" [exe, *args] = arguments["args"] env = [f"{k}={v}" for k, v in arguments.get("env", {}).items()] - proc = self.spawn_helper(exe, args, env) - body = {"processId": proc.pid} + self.reverse_process = self.spawn_helper( + exe, args, env, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + body = {"processId": self.reverse_process.pid} self.send_packet( { "type": "response", diff --git a/lldb/test/API/tools/lldb-dap/launch/io/Makefile b/lldb/test/API/tools/lldb-dap/launch/io/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/launch/io/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py b/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py new file mode 100644 index 0000000000000..ef189b9595eac --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/launch/io/TestDAP_launch_io.py @@ -0,0 +1,356 @@ +""" +Test the redirection of stdio. +There are three ways to launch the debuggee +internalConsole, integratedTerminal and externalTerminal. + +For the three configurations, we test if we can read data +from environments, stdin and cli arguments. + +NOTE: The testcases do not include all possible configurations of +consoles, environments, stdin and cli arguments. +""" + +from abc import abstractmethod +from typing import IO +import lldbdap_testcase +from tempfile import NamedTemporaryFile + +from lldbsuite.test.decorators import ( + skip, + skipIfBuildType, + skipIfRemote, + skipIfWindows, +) + + +class DAP_launchIO(lldbdap_testcase.DAPTestCaseBase): + """The class holds the implementation different ways to redirect the debuggee I/O streams + which is configurable from the Derived classes. + + Depending on the console type the output will be in different places. + It also provides two abstract functions `_get_debuggee_stdout` and `_get_debuggee_stderr` + that provides the debuggee stdout and stderr. + """ + + def all_redirection(self, console: str, with_args: bool = False): + """Test all standard io redirection.""" + self.build_and_create_debug_adapter() + program = self.getBuildArtifact("a.out") + input_text = "from stdin with redirection" + args_text = "string from argv" + program_args = [args_text] if with_args else None + + with NamedTemporaryFile("wt") as stdin, NamedTemporaryFile( + "rt" + ) as stdout, NamedTemporaryFile("rt") as stderr: + stdin.write(input_text) + stdin.flush() + self.launch( + program, + stdio=[stdin.name, stdout.name, stderr.name], + console=console, + args=program_args, + ) + self.continue_to_exit() + + all_stdout = stdout.read() + all_stderr = stderr.read() + + if with_args: + self.assertEqual(f"[STDOUT][FROM_ARGV]: {args_text}", all_stdout) + self.assertEqual(f"[STDERR][FROM_ARGV]: {args_text}", all_stderr) + + self.assertNotIn(f"[STDOUT][FROM_ARGV]: {args_text}", all_stderr) + self.assertNotIn(f"[STDERR][FROM_ARGV]: {args_text}", all_stdout) + + else: + self.assertEqual(f"[STDOUT][FROM_STDIN]: {input_text}", all_stdout) + self.assertEqual(f"[STDERR][FROM_STDIN]: {input_text}", all_stderr) + + self.assertNotIn(f"[STDERR][FROM_STDIN]: {input_text}", all_stdout) + self.assertNotIn(f"[STDOUT][FROM_STDIN]: {input_text}", all_stderr) + + def stdin_redirection(self, console: str, with_args: bool = False): + """Test only stdin redirection.""" + self.build_and_create_debug_adapter() + program = self.getBuildArtifact("a.out") + input_text = "string from stdin" + args_text = "string from argv" + program_args = [args_text] if with_args else None + + with NamedTemporaryFile("w+t") as stdin: + stdin.write(input_text) + stdin.flush() + self.launch(program, stdio=[stdin.name], console=console, args=program_args) + self.continue_to_exit() + + stdout_text = self._get_debuggee_stdout() + stderr_text = self._get_debuggee_stderr() + + if with_args: + self.assertIn(f"[STDOUT][FROM_ARGV]: {args_text}", stdout_text) + self.assertIn(f"[STDERR][FROM_ARGV]: {args_text}", stderr_text) + else: + self.assertIn(f"[STDOUT][FROM_STDIN]: {input_text}", stdout_text) + self.assertIn(f"[STDERR][FROM_STDIN]: {input_text}", stderr_text) + + def stdout_redirection(self, console: str, with_env: bool = False): + """Test only stdout redirection.""" + self.build_and_create_debug_adapter() + program = self.getBuildArtifact("a.out") + + argv_text = "output with\n multiline" + # By default unix terminals the ONLCR flag is enabled. which replaces '\n' with '\r\n' + # see https://man7.org/linux/man-pages/man3/termios.3.html. + # This does not affect writing to normal files. + argv_replaced_text = argv_text.replace("\n", "\r\n") + + program_args = [argv_text] + env_text = "string from env" + env = {"FROM_ENV": env_text} if with_env else {} + + with NamedTemporaryFile("rt") as stdout: + self.launch( + program, + stdio=[None, stdout.name], + console=console, + args=program_args, + env=env, + ) + self.continue_to_exit() + + # check stdout + stdout_text = stdout.read() + stderr_text = self._get_debuggee_stderr() + if with_env: + self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text) + self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) + + self.assertNotIn(f"[STDERR][FROM_ENV]: {env_text}", stdout_text) + self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", stderr_text) + else: + self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_text}", stdout_text) + + self.assertNotIn( + f"[STDERR][FROM_ARGV]: {argv_replaced_text}", stdout_text + ) + self.assertNotIn(f"[STDOUT][FROM_ARGV]: {argv_text}", stderr_text) + + # check stderr + stderr_text = self._get_debuggee_stderr() + # FIXME: when using 'integrated' or 'external' terminal we do not correctly + # escape newlines that are sent to the terminal. + if console == "integratedConsole": + if with_env: + self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", stderr_text) + self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) + else: + self.assertNotIn( + f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", stderr_text + ) + self.assertIn( + f"[STDERR][FROM_ARGV]: {argv_replaced_text}", stderr_text + ) + + def stderr_redirection(self, console: str, with_env: bool = False): + """Test only stdout redirection.""" + self.build_and_create_debug_adapter() + program = self.getBuildArtifact("a.out") + + argv_text = "output with\n multiline" + # By default unix terminals the ONLCR flag is enabled. which replaces '\n' with '\r\n' + # see https://man7.org/linux/man-pages/man3/termios.3.html. + # This does not affect writing to normal files. + # Currently out test implementation for external and integrated Terminal does not run the + # program through a shell terminal. + argv_replaced_text = argv_text + if console == "internalConsole": + argv_replaced_text = argv_text.replace("\n", "\r\n") + program_args = [argv_text] + env_text = "string from env" + env = {"FROM_ENV": env_text} if with_env else {} + + with NamedTemporaryFile("rt") as stderr: + self.launch( + program, + stdio=[None, None, stderr.name], + console=console, + args=program_args, + env=env, + ) + self.continue_to_exit() + stdout_text = self._get_debuggee_stdout() + stderr_text = stderr.read() + if with_env: + self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text) + self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) + else: + self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", stdout_text) + self.assertIn(f"[STDERR][FROM_ARGV]: {argv_text}", stderr_text) + + @abstractmethod + def _get_debuggee_stdout(self) -> str: + """Retrieves the standard output (stdout) from the debuggee process. + + The default destination of the debuggee's stdout can vary based on how the debugger + was launched (either a debug console or a pseudo-terminal (pty)). + It requires subclasses to implement the specific mechanism for obtaining the stdout stream. + """ + raise RuntimeError(f"NotImplemented for {self}") + + @abstractmethod + def _get_debuggee_stderr(self) -> str: + """Retrieves the standard error (stderr) from the debuggee process. + + The default destination of the debuggee's stderr can vary based on how the debugger + was launched (either a debug console or a pseudo-terminal (pty)). + It requires subclasses to implement the specific mechanism for obtaining the stderr stream. + """ + raise RuntimeError(f"NotImplemented for {self}") + + +@skipIfWindows +class TestDAP_launchInternalConsole(DAP_launchIO): + console = "internalConsole" + __debuggee_stdout = None + + # all redirection + def test_all_redirection(self): + self.all_redirection(console=self.console) + + def test_all_redirection_with_args(self): + self.all_redirection(console=self.console, with_args=True) + + # stdin + def test_stdin_redirection(self): + self.stdin_redirection(console=self.console) + + def test_stdin_redirection_with_args(self): + self.stdin_redirection(console=self.console, with_args=True) + + # stdout + def test_stdout_redirection(self): + self.stdout_redirection(console=self.console) + + def test_stdout_redirection_with_env(self): + self.stdout_redirection(console=self.console, with_env=True) + + # stderr + def test_stderr_redirection(self): + self.stderr_redirection(console=self.console) + + def test_stderr_redirection_with_env(self): + self.stderr_redirection(console=self.console, with_env=True) + + def _get_debuggee_stdout(self) -> str: + # self.get_stdout is not idempotent. + if self.__debuggee_stdout is None: + self.__debuggee_stdout = self.get_stdout() + return self.__debuggee_stdout + + def _get_debuggee_stderr(self) -> str: + # NOTE: In internalConsole stderr writes to stdout. + return self._get_debuggee_stdout() + + +@skipIfRemote +@skipIfBuildType(["debug"]) +@skipIfWindows +class TestDAP_launchIntegratedTerminal(DAP_launchIO): + console = "integratedTerminal" + + # all redirection + def test_all_redirection(self): + self.all_redirection(console=self.console) + + def test_all_redirection_with_args(self): + self.all_redirection(console=self.console, with_args=True) + + # stdin + def test_stdin_redirection(self): + self.stdin_redirection(console=self.console) + + def test_stdin_redirection_with_args(self): + self.stdin_redirection(console=self.console, with_args=True) + + # stdout + def test_stdout_redirection(self): + self.stdout_redirection(console=self.console) + + def test_stdout_redirection_with_env(self): + self.stdout_redirection(console=self.console, with_env=True) + + # stderr + def test_stderr_redirection(self): + self.stderr_redirection(console=self.console) + + def test_stderr_redirection_with_env(self): + self.stderr_redirection(console=self.console, with_env=True) + + def _get_debuggee_stdout(self) -> str: + self.assertIsNotNone( + self.dap_server.reverse_process, "Expected a debuggee process." + ) + proc_stdout: IO = self.dap_server.reverse_process.stdout + self.assertIsNotNone(proc_stdout) + return proc_stdout.read().decode() + + def _get_debuggee_stderr(self) -> str: + self.assertIsNotNone( + self.dap_server.reverse_process, "Expected a debuggee process." + ) + proc_stderr = self.dap_server.reverse_process.stderr + self.assertIsNotNone(proc_stderr) + return proc_stderr.read().decode() + + +@skip # NOTE: Currently there is no difference between internal and externalTerminal. +@skipIfRemote +@skipIfBuildType(["debug"]) +@skipIfWindows +class TestDAP_launchExternalTerminal(DAP_launchIO): + console = "externalTerminal" + + # all redirection + def test_all_redirection(self): + self.all_redirection(console=self.console) + + def test_all_redirection_with_args(self): + self.all_redirection(console=self.console, with_args=True) + + # stdin + def test_stdin_redirection(self): + self.stdin_redirection(console=self.console) + + def test_stdin_redirection_with_args(self): + self.stdin_redirection(console=self.console, with_args=True) + + # stdout + def test_stdout_redirection(self): + self.stdout_redirection(console=self.console) + + def test_stdout_redirection_with_env(self): + self.stdout_redirection(console=self.console, with_env=True) + + # stderr + def test_stderr_redirection(self): + self.stderr_redirection(console=self.console) + + def test_stderr_redirection_with_env(self): + self.stderr_redirection(console=self.console, with_env=True) + + def _get_debuggee_stdout(self) -> str: + self.assertIsNotNone( + self.dap_server.reverse_process, "Expected a debuggee process." + ) + proc_stdout: IO = self.dap_server.reverse_process.stdout + self.assertIsNotNone(proc_stdout) + return proc_stdout.read().decode() + + def _get_debuggee_stderr(self) -> str: + self.assertIsNotNone( + self.dap_server.reverse_process, "Expected a debuggee process." + ) + proc_stderr = self.dap_server.reverse_process.stderr + self.assertIsNotNone(proc_stderr) + return proc_stderr.read().decode() diff --git a/lldb/test/API/tools/lldb-dap/launch/io/main.cpp b/lldb/test/API/tools/lldb-dap/launch/io/main.cpp new file mode 100644 index 0000000000000..9d0f1401e69c4 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/launch/io/main.cpp @@ -0,0 +1,24 @@ +#include <cstdlib> +#include <iostream> + +int main(int argc, char *argv[]) { + const bool use_stdin = argc <= 1; + const char *use_env = std::getenv("FROM_ENV"); + + if (use_env != nullptr) { // from environment variable + std::cout << "[STDOUT][FROM_ENV]: " << use_env; + std::cerr << "[STDERR][FROM_ENV]: " << use_env; + + } else if (use_stdin) { // from standard in + std::string line; + std::getline(std::cin, line); + std::cout << "[STDOUT][FROM_STDIN]: " << line; + std::cerr << "[STDERR][FROM_STDIN]: " << line; + + } else { // from argv + const char *first_arg = argv[1]; + std::cout << "[STDOUT][FROM_ARGV]: " << first_arg; + std::cerr << "[STDERR][FROM_ARGV]: " << first_arg; + } + return 0; +} \ No newline at end of file >From fc38a1d2e9de9d6ef4ea854c1e8325634c486889 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Thu, 8 Jan 2026 18:32:22 +0000 Subject: [PATCH 4/5] add windows include for STDFILENO --- lldb/tools/lldb-dap/Handler/RequestHandler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 8be83fac2c117..b660932713eb2 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -25,6 +25,9 @@ #if !defined(_WIN32) #include <unistd.h> #endif +#ifdef _WIN32 +#include "lldb/Host/windows/PosixApi.h" +#endif #ifndef LLDB_DAP_README_URL #define LLDB_DAP_README_URL \ >From 1d0ac550b155c96c8e0314bbf6b74d33cb25a647 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Thu, 8 Jan 2026 18:51:39 +0000 Subject: [PATCH 5/5] update file format --- .../Python/lldbsuite/test/tools/lldb-dap/dap_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 1349393b6caab..a88b345c12dcd 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -46,7 +46,8 @@ class SpawnHelperCallback(Protocol): def __call__( self, executable: str, args: List[str], extra_env: List[str], **kwargs - ) -> subprocess.Popen: ... + ) -> subprocess.Popen: + ... ## DAP type references _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
