https://github.com/nageshnnazare updated https://github.com/llvm/llvm-project/pull/177160
>From 5b756054e45a6efc6792e031cd3d1ab33e416ac2 Mon Sep 17 00:00:00 2001 From: Nagesh Nazare <[email protected]> Date: Wed, 21 Jan 2026 01:30:23 +0530 Subject: [PATCH 1/3] Adding Console window to lldb gui This change implements the console window to output stdout / stderr messages. Native lldb gui masks the output printed by the application and it is difficult to debug if there are any required debug info messages in the app. --- lldb/source/Core/IOHandlerCursesGUI.cpp | 360 ++++++++++++++++++++++-- 1 file changed, 342 insertions(+), 18 deletions(-) diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp index 53d71db9b3b0c..437584f2361c2 100644 --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -6308,6 +6308,247 @@ HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, return eKeyHandled; } +class ConsoleOutputWindowDelegate : public WindowDelegate { +private: + void PollProcessOutput() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + + if (!process || !process->IsAlive()) return; + + // Buffer for reading output + char buffer[1024]; + Status error; + + // Read stdout + size_t stdout_bytes = process->GetSTDOUT(buffer, sizeof(buffer) - 1, error); + if (stdout_bytes > 0) { + buffer[stdout_bytes] = '\0'; + AppendOutput(buffer, false); + } + + // Read stderr + size_t stderr_bytes = process->GetSTDERR(buffer, sizeof(buffer) - 1, error); + if (stderr_bytes > 0) { + buffer[stderr_bytes] = '\0'; + AppendOutput(buffer, true); + } + } + + void AppendOutput(const char *text, bool is_stderr) { + if (!text || text[0] == '\0') return; + + std::lock_guard<std::mutex> lock(m_output_mutex); + + // Split text into lines and add to buffer + std::string remaining = m_partial_line; + remaining += text; + + size_t start = 0, pos = 0; + while ((pos = remaining.find('\n', start)) != std::string::npos) { + std::string line = remaining.substr(start, pos - start); + if (is_stderr) { + line = "[stderr] " + line; + } + m_output_lines.push_back(line); + + // Keep buffer size under limit + while (m_output_lines.size() > m_max_lines) { + m_output_lines.pop_front(); + if (m_first_visible_line > 0) { + --m_first_visible_line; + } + } + + start = pos + 1; + } + + // Save any remaining partial line + m_partial_line = remaining.substr(start); + + // Auto-scroll to bottom if enabled + if (m_auto_scroll && !m_output_lines.empty()) { + m_first_visible_line = + m_output_lines.size() > 0 ? m_output_lines.size() - 1 : 0; + } + } + +public: + ConsoleOutputWindowDelegate(Debugger &debugger) + : m_debugger(debugger), m_first_visible_line(0), + m_auto_scroll(true), m_max_lines(10000) {} + + ~ConsoleOutputWindowDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + // Poll for new output + PollProcessOutput(); + + std::lock_guard<std::mutex> lock(m_output_mutex); + + window.Erase(); + window.DrawTitleBox(window.GetName()); + + const int width = window.GetWidth(); + const int height = window.GetHeight(); + + // Calculate the visible range + size_t total_lines = m_output_lines.size(); + if (total_lines == 0) { + window.MoveCursor(2, 1); + window.PutCString("(no output yet)"); + return true; + } + + // Adjust scroll pos if needed + if (m_first_visible_line >= total_lines) { + m_first_visible_line = + total_lines > 0 ? total_lines - 1 : 0; + } + + // Draw visible line + int visible_height = height - 2; + size_t start_line = m_first_visible_line; + + // If we are at the end, display last N lines + if (m_auto_scroll || start_line + visible_height > total_lines) { + start_line = + total_lines > static_cast<size_t>(visible_height) ? + total_lines - visible_height : 0; + } + + for (int row = 1; row <= visible_height && + (start_line + row - 1) < total_lines; ++row) { + window.MoveCursor(2, row); + const std::string &line = m_output_lines[start_line + row - 1]; + + // Highlight stderr lines? + bool is_stderr = (line.find("[stderr]") == 0); + if (is_stderr) { + window.AttributeOn(COLOR_PAIR(2)); + } + + // Truncate line to fit window width + int available_width = width - 3; + if (static_cast<int>(line.length()) > available_width) { + window.PutCString(line.substr(0, available_width).c_str()); + } else { + window.PutCString(line.c_str()); + } + + if (is_stderr) { + window.AttributeOff(COLOR_PAIR(2)); + } + } + + return true; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + std::lock_guard<std::mutex> lock(m_output_mutex); + + size_t total_lines = m_output_lines.size(); + int visible_height = window.GetHeight() - 1; + + switch (key) { + case KEY_UP: + if (m_first_visible_line > 0) { + --m_first_visible_line; + m_auto_scroll = false; + } + return eKeyHandled; + + case KEY_DOWN: + if (m_first_visible_line + visible_height < total_lines) { + ++m_first_visible_line; + } + // Re-enable Auto-scroll at bottom + if (m_first_visible_line + visible_height >= total_lines) { + m_auto_scroll = true; + } + return eKeyHandled; + + case KEY_PPAGE: + if (m_first_visible_line > static_cast<size_t>(visible_height)) { + m_first_visible_line -= visible_height; + } else { + m_first_visible_line = 0; + } + m_auto_scroll = false; + return eKeyHandled; + + case KEY_NPAGE: + m_first_visible_line += visible_height; + if (m_first_visible_line + visible_height >= total_lines) { + m_first_visible_line = total_lines > static_cast<size_t>(visible_height) + ? total_lines - visible_height : 0; + m_auto_scroll = true; + } + return eKeyHandled; + + case 'a': + m_auto_scroll = !m_auto_scroll; + if (m_auto_scroll && total_lines > 0) { + m_first_visible_line = total_lines > static_cast<size_t>(visible_height) + ? total_lines - visible_height : 0; + } + return eKeyHandled; + + case 'c': + m_output_lines.clear(); + m_partial_line.clear(); + m_first_visible_line = 0; + return eKeyHandled; + + case KEY_HOME: + m_first_visible_line = 0; + m_auto_scroll = false; + return eKeyHandled; + + case KEY_END: + m_first_visible_line = total_lines > static_cast<size_t>(visible_height) + ? total_lines - visible_height : 0; + m_auto_scroll = true; + return eKeyHandled; + + default: + break; + } + + return eKeyNotHandled; + } + + const char *WindowDelegateGetHelpText() override { + return "Console Output view shows stdout and stderr from the process."; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_UP, "Scroll up"}, + {KEY_DOWN, "Scroll down"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {KEY_HOME, "Go to top"}, + {KEY_END, "Go to bottom"}, + {'h', "Show help dialog"}, + {'a', "Toggle auto-scroll"}, + {'c', "Clear output"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + +protected: + Debugger &m_debugger; + std::deque<std::string> m_output_lines; + std::string m_partial_line; + size_t m_first_visible_line = 0; + bool m_auto_scroll = true; + size_t m_max_lines = 10000; + std::mutex m_output_mutex; +}; + + class ApplicationDelegate : public WindowDelegate, public MenuDelegate { public: enum { @@ -6339,6 +6580,7 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { eMenuID_ViewSource, eMenuID_ViewVariables, eMenuID_ViewBreakpoints, + eMenuId_ViewConsole, eMenuID_Help, eMenuID_HelpGUIHelp @@ -6579,6 +6821,7 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { WindowSP main_window_sp = m_app.GetMainWindow(); WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP console_window_sp = main_window_sp->FindSubWindow("Console"); WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); const Rect source_bounds = source_window_sp->GetBounds(); @@ -6587,39 +6830,50 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { main_window_sp->RemoveSubWindow(variables_window_sp.get()); - if (registers_window_sp) { + if (console_window_sp) { + Rect console_bounds = console_window_sp->GetBounds(); + console_bounds.origin.x = variables_bounds.origin.x; + console_bounds.size.width = variables_bounds.size.width + console_bounds.size.width; + console_window_sp->SetBounds(console_bounds); + } else if (registers_window_sp) { // We have a registers window, so give all the area back to the // registers window Rect registers_bounds = variables_bounds; registers_bounds.size.width = source_bounds.size.width; registers_window_sp->SetBounds(registers_bounds); } else { - // We have no registers window showing so give the bottom area back + // We have no console or registers window showing so give the bottom area back // to the source view source_window_sp->Resize(source_bounds.size.width, source_bounds.size.height + variables_bounds.size.height); } } else { - Rect new_variables_rect; - if (registers_window_sp) { + Rect new_vars_rect; + if (console_window_sp) { + // Console exists, so split the area + const Rect console_bounds = console_window_sp->GetBounds(); + Rect new_console_rect; + console_bounds.VerticalSplitPercentage(0.50, new_vars_rect, + new_console_rect); + } else if (registers_window_sp) { // We have a registers window so split the area of the registers // window into two columns where the left hand side will be the // variables and the right hand side will be the registers - const Rect variables_bounds = registers_window_sp->GetBounds(); - Rect new_registers_rect; - variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, - new_registers_rect); - registers_window_sp->SetBounds(new_registers_rect); + const Rect registers_bounds = registers_window_sp->GetBounds(); + Rect new_regs_rect; + registers_bounds.VerticalSplitPercentage(0.50, new_vars_rect, + new_regs_rect); + registers_window_sp->SetBounds(new_regs_rect); } else { - // No registers window, grab the bottom part of the source window + // No registers or console window, grab the bottom part of the source window Rect new_source_rect; source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, - new_variables_rect); + new_vars_rect); source_window_sp->SetBounds(new_source_rect); } WindowSP new_window_sp = main_window_sp->CreateSubWindow( - "Variables", new_variables_rect, false); + "Variables", new_vars_rect, false); new_window_sp->SetDelegate( WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); } @@ -6678,6 +6932,67 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { } return MenuActionResult::Handled; + case eMenuId_ViewConsole: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP console_window_sp = main_window_sp->FindSubWindow("Console"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (console_window_sp) { + const Rect console_bounds = console_window_sp->GetBounds(); + main_window_sp->RemoveSubWindow(console_window_sp.get()); + + if (variables_window_sp) { + // Variables window exists, so give Console space to Variables + Rect variables_bounds = variables_window_sp->GetBounds(); + variables_bounds.size.width = variables_bounds.size.width + + console_bounds.size.width; + variables_window_sp->SetBounds(variables_bounds); + } else if (registers_window_sp) { + // Registers window exists, so give Console space to Registers + Rect registers_bounds = registers_window_sp->GetBounds(); + registers_bounds.size.width = source_bounds.size.width; + registers_window_sp->SetBounds(registers_bounds); + } else { + // No Variables or Registers window exists + source_window_sp->Resize(source_bounds.size.width, + source_bounds.size.height + + console_bounds.size.height); + } + } else { + Rect new_console_rect; + if (variables_window_sp) { + // Variable window exists, split area + const Rect variables_bounds = variables_window_sp->GetBounds(); + Rect new_vars_rect; + variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, + new_console_rect); + variables_window_sp->SetBounds(new_vars_rect); + } else if (registers_window_sp) { + // Registers window exists, split area + const Rect registers_bounds = registers_window_sp->GetBounds(); + Rect new_regs_rect; + registers_bounds.VerticalSplitPercentage(0.50, new_console_rect, + new_regs_rect); + registers_window_sp->SetBounds(new_regs_rect); + } else { + // No Registers or Variables window exists, split source area + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, + new_console_rect); + source_window_sp->SetBounds(new_source_rect); + } + WindowSP new_window_sp = + main_window_sp->CreateSubWindow("Console", new_console_rect, false); + new_window_sp->SetDelegate( + WindowDelegateSP(new ConsoleOutputWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + case eMenuID_ViewBreakpoints: { WindowSP main_window_sp = m_app.GetMainWindow(); WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); @@ -7629,9 +7944,10 @@ void IOHandlerCursesGUI::Activate() { "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)); view_menu_sp->AddSubmenu(std::make_shared<Menu>( "Variables", nullptr, 'v', ApplicationDelegate::eMenuID_ViewVariables)); - view_menu_sp->AddSubmenu( - std::make_shared<Menu>("Breakpoints", nullptr, 'b', - ApplicationDelegate::eMenuID_ViewBreakpoints)); + view_menu_sp->AddSubmenu(std::make_shared<Menu>( + "Breakpoints", nullptr, 'b', ApplicationDelegate::eMenuID_ViewBreakpoints)); + view_menu_sp->AddSubmenu(std::make_shared<Menu>( + "Console", nullptr, 'o', ApplicationDelegate::eMenuId_ViewConsole)); MenuSP help_menu_sp( new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); @@ -7655,12 +7971,16 @@ void IOHandlerCursesGUI::Activate() { Rect status_bounds = content_bounds.MakeStatusBar(); Rect source_bounds; Rect variables_bounds; + Rect console_bounds; Rect threads_bounds; Rect source_variables_bounds; + Rect variables_console_bounds; content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, threads_bounds); source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, - variables_bounds); + variables_console_bounds); + variables_console_bounds.VerticalSplitPercentage(0.50, variables_bounds, + console_bounds); WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); @@ -7672,10 +7992,12 @@ void IOHandlerCursesGUI::Activate() { WindowSP source_window_sp( main_window_sp->CreateSubWindow("Source", source_bounds, true)); - WindowSP variables_window_sp( - main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); WindowSP threads_window_sp( main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); + WindowSP variables_window_sp( + main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); + WindowSP console_window_sp( + main_window_sp->CreateSubWindow("Console", console_bounds, false)); WindowSP status_window_sp( main_window_sp->CreateSubWindow("Status", status_bounds, false)); status_window_sp->SetCanBeActive( @@ -7686,6 +8008,8 @@ void IOHandlerCursesGUI::Activate() { WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); variables_window_sp->SetDelegate( WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + console_window_sp->SetDelegate( + WindowDelegateSP(new ConsoleOutputWindowDelegate(m_debugger))); TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); threads_window_sp->SetDelegate(WindowDelegateSP( new TreeWindowDelegate(m_debugger, thread_delegate_sp))); >From 3db53482df85ad8478dccd7802ec3696eadd8cec Mon Sep 17 00:00:00 2001 From: Nagesh Nazare <[email protected]> Date: Wed, 21 Jan 2026 21:50:21 +0530 Subject: [PATCH 2/3] added a testcase --- .../API/commands/gui/console-output/Makefile | 2 + .../console-output/TestGuiConsoleOutput.py | 212 ++++++++++++++++++ .../API/commands/gui/console-output/main.cpp | 25 +++ 3 files changed, 239 insertions(+) create mode 100644 lldb/test/API/commands/gui/console-output/Makefile create mode 100644 lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py create mode 100644 lldb/test/API/commands/gui/console-output/main.cpp diff --git a/lldb/test/API/commands/gui/console-output/Makefile b/lldb/test/API/commands/gui/console-output/Makefile new file mode 100644 index 0000000000000..3d0b98f13f3d7 --- /dev/null +++ b/lldb/test/API/commands/gui/console-output/Makefile @@ -0,0 +1,2 @@ +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py new file mode 100644 index 0000000000000..d4445d825cca5 --- /dev/null +++ b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py @@ -0,0 +1,212 @@ +""" +Test that the 'gui' console output pane displays stdout / stderr from the debugged process +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + + +class TestGuiConsoleOutputTest(PExpectTest): + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + @skipIfCursesSupportMissing + @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) + def test_gui_console_output(self): + """Test that console pane prints messages""" + self.build() + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.expect( + 'br set -o true -f main.cpp -p "// break here begin"', + substrs=["Breakpoint 1", "address ="], + ) + self.expect( + 'br set -o true -f main.cpp -p "// break here end"', + substrs=["Breakpoint 2", "address ="], + ) + self.expect("run", substrs=["stop reason ="]) + + escape_key = chr(27).encode() + + # Start the GUI. + self.child.sendline("gui") + self.child.expect_exact("Sources") # wait for gui + + # Check for other gui elements in Menu bar + self.child.expect_exact("Target") + self.child.expect_exact("Process") + self.child.expect_exact("View") + + # Check Console window exists + self.child.expect_exact("Console") + + # The Console window show this message before continuing + self.child.expect_exact("(no output yet)") + + # Continue program execution + self.child.send('c') + + # Wait for Breakpoint 2 + self.child.expect_exact("stop reason") + + # Check console output for messages + self.child.expect_exact("Hello from stdout line 1") + self.child.expect_exact("Hello from stderr line 1") + self.child.expect_exact("Hello from stdout line 2") + self.child.expect_exact("Hello from stderr line 2") + self.child.expect_exact("Hello from stdout line 3") + self.child.expect_exact("Hello from stderr line 3") + + # Press escape to quit the gui + self.child.send(escape_key) + + self.expect_prompt() + self.quit() + + @skipIfAsan + @skipIfCursesSupportMissing + @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) + def test_gui_console_menu_toggle(self): + """Test that console pane can be toggled via View Menu""" + self.build() + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.expect( + 'br set -o true -f main.cpp -p "// break here begin"', + substrs=["Breakpoint 1", "address ="], + ) + self.expect("run", substrs=["stop reason ="]) + + escape_key = chr(27).encode() + + # Start the GUI. + self.child.sendline("gui") + self.child.expect_exact("Sources") # wait for gui + + # Check Console window exists by default + self.child.expect_exact("Console") + + # Open View Menu and toggle Console window off + self.child.send('v') + self.child.expect_exact("Console") # menu item should exist + self.child.send('o') + + # Wait for gui update + import time + time.sleep(0.5) + + # Open View Menu and toggle Console window on + self.child.send('v') + self.child.expect_exact("Console") # menu item should exist + self.child.send('o') + + # Console window show re-appear + self.child.expect_exact("Console") + + # Press escape to quit the gui + self.child.send(escape_key) + + self.expect_prompt() + self.quit() + + @skipIfAsan + @skipIfCursesSupportMissing + @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) + def test_gui_console_navigate(self): + """Test that console pane navigation works""" + self.build() + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.expect( + 'br set -o true -f main.cpp -p "// break here first"', + substrs=["Breakpoint 1", "address ="], + ) + self.expect( + 'br set -o true -f main.cpp -p "// break here end"', + substrs=["Breakpoint 2", "address ="], + ) + self.expect("run", substrs=["stop reason ="]) + + escape_key = chr(27).encode() + tab_key = chr(9).encode() + + # Start the GUI. + self.child.sendline("gui") + self.child.expect_exact("Sources") # wait for gui + + # Check Console window exists by default + self.child.expect_exact("Console") + + # The Console window show this message before continuing + self.child.expect_exact("(no output yet)") + + # Continue program execution + self.child.send('c') + + # Wait for Breakpoint 2 + self.child.expect_exact("stop reason") + + # Check console output for messages + self.child.expect_exact("Hello from stdout line 1") + + # Tab to console + self.child.send(tab_key) # Sources -> Threads + self.child.send(tab_key) # Threads -> Variables + self.child.send(tab_key) # Variables -> Console + + # Clear Console output + self.child.send('c') + + # The Console window show this message after clear + self.child.expect_exact("(no output yet)") + + # Press escape to quit the gui + self.child.send(escape_key) + + self.expect_prompt() + self.quit() + + @skipIfAsan + @skipIfCursesSupportMissing + @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) + def test_gui_console_interaction(self): + """Test that console pane doesn't interfere with other window layouts""" + self.build() + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.expect( + 'br set -o true -f main.cpp -p "// break here begin"', + substrs=["Breakpoint 1", "address ="], + ) + self.expect("run", substrs=["stop reason ="]) + + escape_key = chr(27).encode() + + # Start the GUI. + self.child.sendline("gui") + self.child.expect_exact("Sources") # wait for gui + + # Check Console window exists by default + self.child.expect_exact("Console") + + # Check other windows exists + self.child.expect_exact("Threads") + self.child.expect_exact("Variables") + + # Check test_var variable is listed in Variables window + self.child.expect_exact("test_var") + + # Check source code in shown Sources window + self.child.expect_exact("main.cpp") + + # Check main thread is shown in Threads window + self.child.expect_exact("thread #1") + + # Press escape to quit the gui + self.child.send(escape_key) + + self.expect_prompt() + self.quit() diff --git a/lldb/test/API/commands/gui/console-output/main.cpp b/lldb/test/API/commands/gui/console-output/main.cpp new file mode 100644 index 0000000000000..70aff9753ef43 --- /dev/null +++ b/lldb/test/API/commands/gui/console-output/main.cpp @@ -0,0 +1,25 @@ +#include <iostream> +#include <thread> +#include <chrono> + +void generate_output() { + for (unsigned i = 1; i < 4; ++i) { + std::cout << "Hello from stdout line " << i << std::endl; + std::cerr << "Hello from stderr line " << i << std::endl; + } +} + +int main (int argc, char *argv[]) { + int test_var = 42; + + // Break before output + int break_here = 0; // break here begin + + // Generate stdout/stderr output + generate_output(); + + // Wait to capture output + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + return 0; // break here end +} >From 322b54f4842e776dfec21dade4ac90a8fadd22ce Mon Sep 17 00:00:00 2001 From: Nagesh Nazare <[email protected]> Date: Thu, 22 Jan 2026 18:53:09 +0530 Subject: [PATCH 3/3] Syntax + test Modified syntax as per llvm guidelines. Tested the added testcase and fixed accordingly --- lldb/source/Core/IOHandlerCursesGUI.cpp | 37 ++--- .../console-output/TestGuiConsoleOutput.py | 136 +++++------------- 2 files changed, 47 insertions(+), 126 deletions(-) diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp index 437584f2361c2..7b46178d6eaed 100644 --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -6315,7 +6315,8 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { m_debugger.GetCommandInterpreter().GetExecutionContext(); Process *process = exe_ctx.GetProcessPtr(); - if (!process || !process->IsAlive()) return; + if (!process || !process->IsAlive()) + return; // Buffer for reading output char buffer[1024]; @@ -6337,7 +6338,8 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { } void AppendOutput(const char *text, bool is_stderr) { - if (!text || text[0] == '\0') return; + if (!text || text[0] == '\0') + return; std::lock_guard<std::mutex> lock(m_output_mutex); @@ -6348,17 +6350,15 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { size_t start = 0, pos = 0; while ((pos = remaining.find('\n', start)) != std::string::npos) { std::string line = remaining.substr(start, pos - start); - if (is_stderr) { + if (is_stderr) line = "[stderr] " + line; - } m_output_lines.push_back(line); // Keep buffer size under limit while (m_output_lines.size() > m_max_lines) { m_output_lines.pop_front(); - if (m_first_visible_line > 0) { + if (m_first_visible_line > 0) --m_first_visible_line; - } } start = pos + 1; @@ -6425,21 +6425,18 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { // Highlight stderr lines? bool is_stderr = (line.find("[stderr]") == 0); - if (is_stderr) { + if (is_stderr) window.AttributeOn(COLOR_PAIR(2)); - } // Truncate line to fit window width int available_width = width - 3; - if (static_cast<int>(line.length()) > available_width) { + if (static_cast<int>(line.length()) > available_width) window.PutCString(line.substr(0, available_width).c_str()); - } else { + else window.PutCString(line.c_str()); - } - if (is_stderr) { + if (is_stderr) window.AttributeOff(COLOR_PAIR(2)); - } } return true; @@ -6460,21 +6457,18 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { return eKeyHandled; case KEY_DOWN: - if (m_first_visible_line + visible_height < total_lines) { + if (m_first_visible_line + visible_height < total_lines) ++m_first_visible_line; - } // Re-enable Auto-scroll at bottom - if (m_first_visible_line + visible_height >= total_lines) { + if (m_first_visible_line + visible_height >= total_lines) m_auto_scroll = true; - } return eKeyHandled; case KEY_PPAGE: - if (m_first_visible_line > static_cast<size_t>(visible_height)) { + if (m_first_visible_line > static_cast<size_t>(visible_height)) m_first_visible_line -= visible_height; - } else { + else m_first_visible_line = 0; - } m_auto_scroll = false; return eKeyHandled; @@ -6489,10 +6483,9 @@ class ConsoleOutputWindowDelegate : public WindowDelegate { case 'a': m_auto_scroll = !m_auto_scroll; - if (m_auto_scroll && total_lines > 0) { + if (m_auto_scroll && total_lines > 0) m_first_visible_line = total_lines > static_cast<size_t>(visible_height) ? total_lines - visible_height : 0; - } return eKeyHandled; case 'c': diff --git a/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py index d4445d825cca5..1e4fedf313351 100644 --- a/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py +++ b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py @@ -18,29 +18,37 @@ def test_gui_console_output(self): """Test that console pane prints messages""" self.build() - self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.launch( + executable=self.getBuildArtifact("a.out"), + dimensions=(100, 500), + run_under=["env", "TERM=xterm"] + ) + self.expect( 'br set -o true -f main.cpp -p "// break here begin"', substrs=["Breakpoint 1", "address ="], ) + self.expect( 'br set -o true -f main.cpp -p "// break here end"', substrs=["Breakpoint 2", "address ="], ) + self.expect("run", substrs=["stop reason ="]) escape_key = chr(27).encode() # Start the GUI. self.child.sendline("gui") - self.child.expect_exact("Sources") # wait for gui - - # Check for other gui elements in Menu bar + + # Check for gui elements in Menu bar (top of screen) + # We expect these in the order they appear to avoid consumption issues self.child.expect_exact("Target") self.child.expect_exact("Process") self.child.expect_exact("View") - # Check Console window exists + # Check for window titles (middle of screen) + self.child.expect_exact("Sources") self.child.expect_exact("Console") # The Console window show this message before continuing @@ -49,9 +57,6 @@ def test_gui_console_output(self): # Continue program execution self.child.send('c') - # Wait for Breakpoint 2 - self.child.expect_exact("stop reason") - # Check console output for messages self.child.expect_exact("Hello from stdout line 1") self.child.expect_exact("Hello from stderr line 1") @@ -60,51 +65,8 @@ def test_gui_console_output(self): self.child.expect_exact("Hello from stdout line 3") self.child.expect_exact("Hello from stderr line 3") - # Press escape to quit the gui - self.child.send(escape_key) - - self.expect_prompt() - self.quit() - - @skipIfAsan - @skipIfCursesSupportMissing - @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) - def test_gui_console_menu_toggle(self): - """Test that console pane can be toggled via View Menu""" - self.build() - - self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) - self.expect( - 'br set -o true -f main.cpp -p "// break here begin"', - substrs=["Breakpoint 1", "address ="], - ) - self.expect("run", substrs=["stop reason ="]) - - escape_key = chr(27).encode() - - # Start the GUI. - self.child.sendline("gui") - self.child.expect_exact("Sources") # wait for gui - - # Check Console window exists by default - self.child.expect_exact("Console") - - # Open View Menu and toggle Console window off - self.child.send('v') - self.child.expect_exact("Console") # menu item should exist - self.child.send('o') - - # Wait for gui update - import time - time.sleep(0.5) - - # Open View Menu and toggle Console window on - self.child.send('v') - self.child.expect_exact("Console") # menu item should exist - self.child.send('o') - - # Console window show re-appear - self.child.expect_exact("Console") + # Wait for Breakpoint 2 + self.child.expect_exact("stop reason") # Press escape to quit the gui self.child.send(escape_key) @@ -119,15 +81,22 @@ def test_gui_console_navigate(self): """Test that console pane navigation works""" self.build() - self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) + self.launch( + executable=self.getBuildArtifact("a.out"), + dimensions=(100, 500), + run_under=["env", "TERM=xterm"] + ) + self.expect( - 'br set -o true -f main.cpp -p "// break here first"', + 'br set -o true -f main.cpp -p "// break here begin"', substrs=["Breakpoint 1", "address ="], ) + self.expect( 'br set -o true -f main.cpp -p "// break here end"', substrs=["Breakpoint 2", "address ="], ) + self.expect("run", substrs=["stop reason ="]) escape_key = chr(27).encode() @@ -135,9 +104,10 @@ def test_gui_console_navigate(self): # Start the GUI. self.child.sendline("gui") - self.child.expect_exact("Sources") # wait for gui - - # Check Console window exists by default + + # Match elements in top-to-bottom order + self.child.expect_exact("Target") + self.child.expect_exact("Sources") self.child.expect_exact("Console") # The Console window show this message before continuing @@ -146,12 +116,12 @@ def test_gui_console_navigate(self): # Continue program execution self.child.send('c') - # Wait for Breakpoint 2 - self.child.expect_exact("stop reason") - # Check console output for messages self.child.expect_exact("Hello from stdout line 1") + # Wait for Breakpoint 2 + self.child.expect_exact("stop reason") + # Tab to console self.child.send(tab_key) # Sources -> Threads self.child.send(tab_key) # Threads -> Variables @@ -167,46 +137,4 @@ def test_gui_console_navigate(self): self.child.send(escape_key) self.expect_prompt() - self.quit() - - @skipIfAsan - @skipIfCursesSupportMissing - @skipIf(oslist=["linux"], archs=["arm$", "aarch64"]) - def test_gui_console_interaction(self): - """Test that console pane doesn't interfere with other window layouts""" - self.build() - - self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100, 500)) - self.expect( - 'br set -o true -f main.cpp -p "// break here begin"', - substrs=["Breakpoint 1", "address ="], - ) - self.expect("run", substrs=["stop reason ="]) - - escape_key = chr(27).encode() - - # Start the GUI. - self.child.sendline("gui") - self.child.expect_exact("Sources") # wait for gui - - # Check Console window exists by default - self.child.expect_exact("Console") - - # Check other windows exists - self.child.expect_exact("Threads") - self.child.expect_exact("Variables") - - # Check test_var variable is listed in Variables window - self.child.expect_exact("test_var") - - # Check source code in shown Sources window - self.child.expect_exact("main.cpp") - - # Check main thread is shown in Threads window - self.child.expect_exact("thread #1") - - # Press escape to quit the gui - self.child.send(escape_key) - - self.expect_prompt() - self.quit() + self.quit() \ No newline at end of file _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
