diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 618e291..fce28c9 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -29,7 +29,8 @@ options = OpenStruct.new( 'value_as_nested_element' => false, 'attach_mode' => false, 'cli_debug' => false, - 'key_value_mode' => false + 'key_value_mode' => false, + 'socket_path' => nil ) opts = OptionParser.new do |opts| @@ -100,6 +101,9 @@ EOB opts.on("--value-as-nested-element", "Allow to pass variable's value as nested element instead of attribute") do options.value_as_nested_element = true end + opts.on("--socket-path PATH", "Listen for debugger on the given UNIX domain socket path") do |path| + options.socket_path = path + end opts.separator "" opts.separator "Common options:" opts.on_tail("-v", "--version", "Show version") do diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 97644df..bc61c90 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -74,16 +74,22 @@ def interrupt_last end def start_server(host = nil, port = 1234, notify_dispatcher = false) - return if started? - start - start_control(host, port, notify_dispatcher) + _start_server_common(host, port, nil, notify_dispatcher) + end + + def start_server_unix(socket_path, notify_dispatcher = false) + _start_server_common(nil, 0, socket_path, notify_dispatcher) end def prepare_debugger(options) @mutex = Mutex.new @proceed = ConditionVariable.new - start_server(options.host, options.port, options.notify_dispatcher) + if options.socket_path.nil? + start_server(options.host, options.port, options.notify_dispatcher) + else + start_server_unix(options.socket_path, options.notify_dispatcher) + end raise "Control thread did not start (#{@control_thread}}" unless @control_thread && @control_thread.alive? @@ -112,24 +118,53 @@ def run_prog_script end def start_control(host, port, notify_dispatcher) + _start_control_common(host, port, nil, notify_dispatcher) + end + + def start_control_unix(socket_path, notify_dispatcher) + _start_control_common(nil, 0, socket_path, notify_dispatcher) + end + + private + + def _start_server_common(host, port, socket_path, notify_dispatcher) + return if started? + start + _start_control_common(host, port, socket_path, notify_dispatcher) + end + + def _start_control_common(host, port, socket_path, notify_dispatcher) raise "Debugger is not started" unless started? return if @control_thread @control_thread = DebugThread.new do begin - # 127.0.0.1 seemingly works with all systems and with IPv6 as well. - # "localhost" and nil have problems on some systems. - host ||= '127.0.0.1' - - server = notify_dispatcher_if_needed(host, port, notify_dispatcher) do |real_port, port_changed| - s = TCPServer.new(host, real_port) - print_greeting_msg $stderr, host, real_port, port_changed ? "Subprocess" : "Fast" if defined? IDE_VERSION - s + if socket_path.nil? + # 127.0.0.1 seemingly works with all systems and with IPv6 as well. + # "localhost" and nil have problems on some systems. + host ||= '127.0.0.1' + + server = notify_dispatcher_if_needed(host, port, notify_dispatcher) do |real_port, port_changed| + s = TCPServer.new(host, real_port) + print_greeting_msg $stderr, host, real_port, port_changed ? "Subprocess" : "Fast" if defined? IDE_VERSION + s + end + else + raise "Cannot specify host and socket_file at the same time" if host + File.delete(socket_path) if File.exist?(socket_path) + server = UNIXServer.new(socket_path) + print_greeting_msg $stderr, nil, nil, "Fast", socket_path if defined? IDE_VERSION end return unless server while (session = server.accept) - $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug + if Debugger.cli_debug + if session.peeraddr == 'AF_INET' + $stderr.puts "Connected from #{session.peeraddr[2]}" + else + $stderr.puts "Connected from local client" + end + end dispatcher = ENV['IDE_PROCESS_DISPATCHER'] if dispatcher ENV['IDE_PROCESS_DISPATCHER'] = "#{session.peeraddr[2]}:#{dispatcher}" unless dispatcher.include?(":") @@ -153,8 +188,6 @@ def start_control(host, port, notify_dispatcher) end end - private - def notify_dispatcher_if_needed(host, port, need_notify) return yield port unless need_notify diff --git a/lib/ruby-debug-ide/greeter.rb b/lib/ruby-debug-ide/greeter.rb index 7e3dd93..f47bde3 100644 --- a/lib/ruby-debug-ide/greeter.rb +++ b/lib/ruby-debug-ide/greeter.rb @@ -10,7 +10,7 @@ module Debugger class << self - def print_greeting_msg(stream, host, port, debugger_name = "Fast") + def print_greeting_msg(stream, host, port, debugger_name = "Fast", socket_path = nil) base_gem_name = if defined?(JRUBY_VERSION) || RUBY_VERSION < '1.9.0' 'ruby-debug-base' elsif RUBY_VERSION < '2.0.0' @@ -27,6 +27,8 @@ def print_greeting_msg(stream, host, port, debugger_name = "Fast") if host && port listens_on = " listens on #{host}:#{port}\n" + elsif socket_path + listens_on = " listens on #{socket_path}\n" else listens_on = "\n" end @@ -37,4 +39,4 @@ def print_greeting_msg(stream, host, port, debugger_name = "Fast") end end -end \ No newline at end of file +end diff --git a/test-base/catchpoint_test.rb b/test-base/catchpoint_test.rb new file mode 100644 index 0000000..f8b18a1 --- /dev/null +++ b/test-base/catchpoint_test.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +$:.unshift File.join(File.dirname(__FILE__), "..", "lib") + +require 'test_base' + +module CatchpointTest + + def test_catchpoint_basics + create_socket ['sleep 0.01', '5/0', 'sleep 0.01'] + run_to_line(1) + send_next + assert_suspension(@test_path, 2, 1) + send_ruby('catch ZeroDivisionError') + assert_catchpoint_set('ZeroDivisionError') + send_next + assert_exception(@test_path, 2, 'ZeroDivisionError') + send_next + end + +end diff --git a/test-base/enable_disable_test.rb b/test-base/enable_disable_test.rb new file mode 100644 index 0000000..a6239d3 --- /dev/null +++ b/test-base/enable_disable_test.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby + +$:.unshift File.join(File.dirname(__FILE__), "..", "lib") + +require 'test_base' + +module EnableDisableTest + + def test_enable_disable_basics + create_socket ['1.upto(10) do', 'sleep 0.01', 'sleep 0.01', 'end'] + + send_test_breakpoint(2) + assert_breakpoint_added_no(1) + send_test_breakpoint(3) + assert_breakpoint_added_no(2) + + start_debugger + assert_test_breakpoint(2) + send_cont + assert_test_breakpoint(3) + send_cont + assert_test_breakpoint(2) + send_ruby('disable 2') + assert_breakpoint_disabled(2) + send_cont + assert_test_breakpoint(2) + send_cont + assert_test_breakpoint(2) + send_ruby('enable 2') + assert_breakpoint_enabled(2) + send_cont + assert_test_breakpoint(3) + send_cont + assert_test_breakpoint(2) + send_cont + assert_test_breakpoint(3) + send_ruby('disable 1') + assert_breakpoint_disabled(1) + send_ruby('disable 2') + assert_breakpoint_disabled(2) + send_cont + end + +end diff --git a/test-base/test_base.rb b/test-base/test_base.rb index c026750..a9719b4 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -7,6 +7,7 @@ require 'socket' require 'readers' require 'test/unit' +require 'thread' require 'tmpdir' require 'open3' require 'yaml' @@ -24,6 +25,14 @@ def initialize(name) @port = nil @parser = nil @fast_fail = nil + @socket_path = nil + end + + module UseUNIXDomainSocket + def setup + super + @use_unix_socket = true + end end def setup @@ -36,6 +45,9 @@ def setup # XXX: tmpdir unique to test, probably parse out from self.name FileUtils.mkdir_p(TMP_DIR) + + @use_unix_socket = false + @socket_path = nil end # Loads key from the _config_._yaml_ file. @@ -83,8 +95,12 @@ def debug_jruby? end def start_ruby_process(script, additional_opts = '') - @port = TestBase.find_free_port - cmd = debug_command(script, @port, additional_opts) + if !@use_unix_socket + @port = TestBase.find_free_port + else + @socket_path = TestBase.next_socket_path + end + cmd = debug_command(script, @port, @socket_path, additional_opts) debug "Starting: #{cmd}\n" Thread.new do @@ -131,6 +147,17 @@ def TestBase.find_free_port(port = 1098) end end + @@socket_path_seq_mutex = Mutex.new + @@socket_path_sequence_num = 0 + def self.next_socket_path + seq_num = @@socket_path_seq_mutex.synchronize do + i = @@socket_path_sequence_num + @@socket_path_sequence_num += 1 + i + end + File.join(TMP_DIR, "d#{seq_num}.sock") + end + def create_file(script_name, lines) file = File.join(TMP_DIR, script_name) script_path = RUBY_VERSION >= "1.9" ? File.realdirpath(file) : file.to_s @@ -161,11 +188,18 @@ def socket debug "Trying to connect to the debugger..." (config_load('server_start_up_timeout')*4).downto(1) do |i| begin - @socket = TCPSocket.new("127.0.0.1", @port) + if @socket_path + @socket = UNIXSocket.new(@socket_path) + else + @socket = TCPSocket.new("127.0.0.1", @port) + end break rescue Errno::ECONNREFUSED debug '.' sleep 0.5 + rescue Errno::ENOENT + debug '.' + sleep 0.5 end end debug "\n" diff --git a/test/rd_basic_test.rb b/test/rd_basic_test.rb index 5bafdcc..83b684a 100644 --- a/test/rd_basic_test.rb +++ b/test/rd_basic_test.rb @@ -7,4 +7,9 @@ class RDSteppingAndBreakpointsTest < RDTestBase include BasicTest +end + +class RDUNIXSteppingAndBreakpointsTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include BasicTest end \ No newline at end of file diff --git a/test/rd_catchpoint_test.rb b/test/rd_catchpoint_test.rb index 1eea686..c55d2a3 100644 --- a/test/rd_catchpoint_test.rb +++ b/test/rd_catchpoint_test.rb @@ -1,20 +1,15 @@ #!/usr/bin/env ruby require 'rd_test_base' +require 'catchpoint_test' class RDCatchpointTest < RDTestBase - def test_catchpoint_basics - create_socket ['sleep 0.01', '5/0', 'sleep 0.01'] - run_to_line(1) - send_next - assert_suspension(@test_path, 2, 1) - send_ruby('catch ZeroDivisionError') - assert_catchpoint_set('ZeroDivisionError') - send_next - assert_exception(@test_path, 2, 'ZeroDivisionError') - send_next - end - + include CatchpointTest + end +class RDUNIXCatchpointTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include CatchpointTest +end diff --git a/test/rd_condition_test.rb b/test/rd_condition_test.rb index 6fa2c4f..aec1f40 100644 --- a/test/rd_condition_test.rb +++ b/test/rd_condition_test.rb @@ -9,3 +9,7 @@ class RDConditionTest < RDTestBase end +class RDUNIXConditionTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include ConditionTest +end diff --git a/test/rd_enable_disable_test.rb b/test/rd_enable_disable_test.rb index e7d9364..8a82960 100644 --- a/test/rd_enable_disable_test.rb +++ b/test/rd_enable_disable_test.rb @@ -1,43 +1,15 @@ #!/usr/bin/env ruby require 'rd_test_base' +require 'enable_disable_test' class RDEnableDisableTest < RDTestBase - def test_enable_disable_basics - create_socket ['1.upto(10) do', 'sleep 0.01', 'sleep 0.01', 'end'] - - send_test_breakpoint(2) - assert_breakpoint_added_no(1) - send_test_breakpoint(3) - assert_breakpoint_added_no(2) - - start_debugger - assert_test_breakpoint(2) - send_cont - assert_test_breakpoint(3) - send_cont - assert_test_breakpoint(2) - send_ruby('disable 2') - assert_breakpoint_disabled(2) - send_cont - assert_test_breakpoint(2) - send_cont - assert_test_breakpoint(2) - send_ruby('enable 2') - assert_breakpoint_enabled(2) - send_cont - assert_test_breakpoint(3) - send_cont - assert_test_breakpoint(2) - send_cont - assert_test_breakpoint(3) - send_ruby('disable 1') - assert_breakpoint_disabled(1) - send_ruby('disable 2') - assert_breakpoint_disabled(2) - send_cont - end + include EnableDisableTest end +class RDUNIXEnableDisableTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include EnableDisableTest +end diff --git a/test/rd_expression_info_test.rb b/test/rd_expression_info_test.rb index b5d823d..4ed7071 100644 --- a/test/rd_expression_info_test.rb +++ b/test/rd_expression_info_test.rb @@ -9,3 +9,7 @@ class RDExpressionInfoTest < RDTestBase end +class RDUNIXExpressionInfoTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include ExpressionInfoTest +end diff --git a/test/rd_inspect_test.rb b/test/rd_inspect_test.rb index 90f99c9..5cd71e2 100644 --- a/test/rd_inspect_test.rb +++ b/test/rd_inspect_test.rb @@ -9,3 +9,7 @@ class RDInspectTest < RDTestBase end +class RDUNIXInspectTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include InspectTest +end diff --git a/test/rd_stepping_breakpoints_test.rb b/test/rd_stepping_breakpoints_test.rb index 88e7aa1..7ca885c 100644 --- a/test/rd_stepping_breakpoints_test.rb +++ b/test/rd_stepping_breakpoints_test.rb @@ -34,3 +34,7 @@ def test_breakpoint_and_continue_from_other_file end +class RDUNIXSteppingAndBreakpointsTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include SteppingAndBreakpointsTest +end \ No newline at end of file diff --git a/test/rd_test_base.rb b/test/rd_test_base.rb index d5b7e4f..d5bc369 100644 --- a/test/rd_test_base.rb +++ b/test/rd_test_base.rb @@ -18,13 +18,20 @@ def setup end end - def debug_command(script, port, additional_opts='') + def debug_command(script, port, socket_path, additional_opts='') + location_arg = if !port.nil? + "-p #{port}" + elsif !socket_path.nil? + "--socket-path #{socket_path}" + else + raise StandardError, "One of port and socket_path must be set" + end cmd = "#{interpreter}" cmd << " --debug" if jruby? cmd << " -J-Xdebug -J-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y" if jruby? and debug_jruby? cmd << " -I 'lib:#{File.dirname(script)}' #{@rdebug_ide}" + (@verbose_server ? " -d" : "") + - " -p #{port} #{additional_opts} -- '#{script}'" + " #{location_arg} #{additional_opts} -- '#{script}'" end def start_debugger diff --git a/test/rd_threads_and_frames_test.rb b/test/rd_threads_and_frames_test.rb index 89a957b..616266b 100644 --- a/test/rd_threads_and_frames_test.rb +++ b/test/rd_threads_and_frames_test.rb @@ -9,3 +9,7 @@ class RDThreadsAndFrames < RDTestBase end +class RDUNIXThreadsAndFrames < RDTestBase + include TestBase::UseUNIXDomainSocket + include ThreadsAndFrames +end \ No newline at end of file diff --git a/test/rd_variables_test.rb b/test/rd_variables_test.rb index 403640c..0f866b4 100644 --- a/test/rd_variables_test.rb +++ b/test/rd_variables_test.rb @@ -9,3 +9,7 @@ class RDVariablesTest < RDTestBase end +class RDUNIXVariablesTest < RDTestBase + include TestBase::UseUNIXDomainSocket + include VariablesTest +end