diff --git a/lib/debug/server_dap.rb b/lib/debug/server_dap.rb index 7502182a4..71133e4a7 100644 --- a/lib/debug/server_dap.rb +++ b/lib/debug/server_dap.rb @@ -298,6 +298,7 @@ def process process_request(req) end ensure + restore_debuggee_stdio send_event :terminated unless @sock.closed? end @@ -314,6 +315,7 @@ def process_request req UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') || true @nonstop = true + capture_debuggee_stdio load_extensions req when 'attach' @@ -326,6 +328,7 @@ def process_request req @nonstop = false end + capture_debuggee_stdio load_extensions req when 'configurationDone' @@ -397,6 +400,7 @@ def process_request req terminate = args.fetch("terminateDebuggee", false) SESSION.clear_all_breakpoints + restore_debuggee_stdio send_response req if SESSION.in_subsession? @@ -506,6 +510,56 @@ def puts result = "" send_event 'output', category: 'console', output: "#{result&.chomp}\n" end + def capture_debuggee_stdio + return if @stdio_captured + + @original_stdout = $stdout + @original_stderr = $stderr + + @stdout_reader, @stdout_writer = IO.pipe + @stderr_reader, @stderr_writer = IO.pipe + + @stdout_writer.sync = true + @stderr_writer.sync = true + + $stdout = @stdout_writer + $stderr = @stderr_writer + @stdio_captured = true + + @stdout_monitor = start_monitor_thread(@stdout_reader, 'stdout') + @stderr_monitor = start_monitor_thread(@stderr_reader, 'stderr') + end + + def restore_debuggee_stdio + return unless @stdio_captured + + $stdout = @original_stdout if @original_stdout + $stderr = @original_stderr if @original_stderr + + [@stdout_writer, @stderr_writer] + .filter { |writer| !writer&.closed? } + .each(&:close) + + [@stdout_monitor, @stderr_monitor].each { |monitor| monitor&.join } + [@stdout_reader, @stderr_reader].each { |reader| reader&.close unless reader&.closed? } + + @stdio_captured = false + end + + private + + def start_monitor_thread(reader, category) + Thread.new do + reader.each_line do |line| + send_event 'output', category: category, output: line + end + rescue IOError, Errno::EBADF + # Pipe closed, exit gracefully + end + end + + public + def ignore_output_on_suspend? true end diff --git a/test/protocol/stdio_capture_dap_test.rb b/test/protocol/stdio_capture_dap_test.rb new file mode 100644 index 000000000..b0dd60cc8 --- /dev/null +++ b/test/protocol/stdio_capture_dap_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative '../support/protocol_test_case' + +module DEBUGGER__ + class StdioCaptureDAPTest < ProtocolTestCase + PROGRAM = <<~RUBY + 1| $stdout.puts "stdout message" + 2| $stderr.puts "stderr message" + 3| a = 1 + RUBY + + def test_stdout_captured_as_output_event + run_protocol_scenario PROGRAM, cdp: false do + req_add_breakpoint 3 + req_continue + + stdout_event = find_response :event, 'output', 'V