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

Reply via email to