diff --git a/.travis.yml b/.travis.yml index 31827b8..15a86ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,15 @@ os: - osx rvm: + - 1.8.7 - 1.9.3 - 2.0.0 - 2.1.10 - 2.2.8 - - 2.3.5 - - 2.4.2 - - 2.5.0 + - 2.3.8 + - 2.4.6 + - 2.5.5 + - 2.6.3 - ruby-head matrix: @@ -25,10 +27,13 @@ matrix: - os: osx rvm: 2.0.0 + - os: osx + rvm: 1.8.7 + # include: # - os: osx # rvm: 1.9.3 # before_script: rvm install ruby-1.9.3 # not binary # - os: osx # rvm: 2.0.0 -# before_script: rvm install ruby-2.0.0 # not binary \ No newline at end of file +# before_script: rvm install ruby-2.0.0 # not binary diff --git a/Gemfile b/Gemfile index 9d8626c..c36df53 100644 --- a/Gemfile +++ b/Gemfile @@ -3,17 +3,23 @@ source "http://rubygems.org/" # @param [Array] versions compatible ruby versions # @return [Array] an array with mri platforms of given versions def mries(*versions) - versions.flat_map do |v| - %w(ruby mingw x64_mingw) - .map { |platform| "#{platform}_#{v}".to_sym unless platform == "x64_mingw" && v < "2.0" } - .delete_if &:nil? - end + versions.map do |v| + %w(ruby mingw x64_mingw).map do |platform| + "#{platform}_#{v}".to_sym unless platform == "x64_mingw" && v < "2.0" + end.delete_if &:nil? + end.flatten +end + +if RUBY_VERSION < '1.9' || defined?(JRUBY_VERSION) + gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] +end + +if RUBY_VERSION && RUBY_VERSION >= "1.9" + gem "ruby-debug-base19x", ">= 0.11.32", :platforms => mries('19') end -gem "ruby-debug-base", :platforms => [:jruby, *mries('18')] -gem "ruby-debug-base19x", ">= 0.11.32", :platforms => mries('19') if RUBY_VERSION && RUBY_VERSION >= "2.0" - gem "debase", "~> 0.2.2", :platforms => mries('20', '21', '22', '23', '24', '25') + gem "debase", "~> 0.2", ">= 0.2.2", :platforms => mries('20', '21', '22', '23', '24', '25') end gemspec @@ -23,6 +29,10 @@ group :development do end group :test do - gem "test-unit" + if RUBY_VERSION < "1.9" + gem "test-unit", "~> 2.1.2" + else + gem "test-unit" + end end diff --git a/bin/rdebug-ide b/bin/rdebug-ide index a72dfd1..3054353 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -27,7 +27,8 @@ options = OpenStruct.new( 'catchpoint_deleted_event' => false, 'value_as_nested_element' => false, 'attach_mode' => false, - 'cli_debug' => false + 'cli_debug' => false, + 'key_value_mode' => false ) opts = OptionParser.new do |opts| @@ -80,6 +81,9 @@ EOB opts.on("--attach-mode", "Tells that rdebug-ide is working in attach mode") do options.attach_mode = true end + opts.on("--key-value", "Key/Value presentation of hash items") do + options.key_value_mode = true + end opts.on("--ignore-port", "Generate another port") do options.ignore_port = true end @@ -166,6 +170,7 @@ Debugger.debugger_memory_limit = options.debugger_memory_limit Debugger.inspect_time_limit = options.inspect_time_limit Debugger.catchpoint_deleted_event = options.catchpoint_deleted_event || options.rm_protocol_extensions Debugger.value_as_nested_element = options.value_as_nested_element || options.rm_protocol_extensions +Debugger.key_value_mode = options.key_value_mode if options.attach_mode if Debugger::FRONT_END == "debase" diff --git a/ext/mkrf_conf.rb b/ext/mkrf_conf.rb index c8170ac..478dca6 100644 --- a/ext/mkrf_conf.rb +++ b/ext/mkrf_conf.rb @@ -1,3 +1,4 @@ +install_dir = File.expand_path("../../../..", __FILE__) jruby = defined?(JRUBY_VERSION) || (defined?(RUBY_ENGINE) && 'jruby' == RUBY_ENGINE) rbx = defined?(RUBY_ENGINE) && 'rbx' == RUBY_ENGINE @@ -27,11 +28,11 @@ def already_installed(dep) begin puts "Installing base gem" - inst = Gem::DependencyInstaller.new :prerelease => dep.prerelease? + inst = Gem::DependencyInstaller.new(:prerelease => dep.prerelease?, :install_dir => install_dir) inst.install dep rescue begin - inst = Gem::DependencyInstaller.new(:prerelease => true) + inst = Gem::DependencyInstaller.new(:prerelease => true, :install_dir => install_dir) inst.install dep rescue Exception => e puts e diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 62c0671..10f70f4 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -18,6 +18,13 @@ module Debugger class << self + def find_free_port(host) + server = TCPServer.open(host, 0) + port = server.addr[1] + server.close + port + end + # Prints to the stderr using printf(*args) if debug logging flag (-d) is on. def print_debug(*args) if Debugger.cli_debug @@ -44,6 +51,7 @@ def cleanup_backtrace(backtrace) end attr_accessor :attached + attr_accessor :key_value_mode attr_accessor :cli_debug, :xml_debug, :evaluation_timeout attr_accessor :trace_to_s, :debugger_memory_limit, :inspect_time_limit attr_accessor :control_thread @@ -111,9 +119,15 @@ def start_control(host, port, notify_dispatcher) # 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 = TCPServer.new(host, port) - print_greeting_msg($stderr, host, port) if defined? IDE_VERSION - notify_dispatcher(port) if notify_dispatcher + + 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 + + return unless server + while (session = server.accept) $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug dispatcher = ENV['IDE_PROCESS_DISPATCHER'] @@ -141,8 +155,9 @@ def start_control(host, port, notify_dispatcher) private + def notify_dispatcher_if_needed(host, port, need_notify) + return yield port unless need_notify - def notify_dispatcher(port) return unless ENV['IDE_PROCESS_DISPATCHER'] acceptor_host, acceptor_port = ENV['IDE_PROCESS_DISPATCHER'].split(":") acceptor_host, acceptor_port = '127.0.0.1', acceptor_host unless acceptor_port @@ -151,11 +166,19 @@ def notify_dispatcher(port) 3.times do |i| begin s = TCPSocket.open(acceptor_host, acceptor_port) + dispatcher_answer = s.gets.chomp + + if dispatcher_answer == "true" + port = Debugger.find_free_port(host) + end + + server = yield port, dispatcher_answer == "true" + s.print(port) s.close connected = true print_debug "Ide process dispatcher notified about sub-debugger which listens on #{port}\n" - return + return server rescue => bt $stderr.puts "#{Process.pid}: connection failed(#{i+1})" $stderr.puts "Exception: #{bt}" @@ -164,7 +187,6 @@ def notify_dispatcher(port) end unless connected end end - end class Exception # :nodoc: diff --git a/lib/ruby-debug-ide/command.rb b/lib/ruby-debug-ide/command.rb index e6ee3e1..ecbd2ba 100644 --- a/lib/ruby-debug-ide/command.rb +++ b/lib/ruby-debug-ide/command.rb @@ -4,6 +4,7 @@ require 'debase' end +require 'ruby-debug-ide/thread_alias' require 'ruby-debug-ide/helper' require 'delegate' @@ -128,9 +129,18 @@ def debug_eval(str, b = get_binding) to_inspect = Command.unescape_incoming(str) max_time = Debugger.evaluation_timeout @printer.print_debug("Evaluating %s with timeout after %i sec", str, max_time) + + Debugger::TimeoutHandler.do_thread_alias + + eval_result = nil + timeout(max_time) do - eval(to_inspect, b) + eval_result = eval(to_inspect, b) end + + Debugger::TimeoutHandler.undo_thread_alias + + return eval_result rescue StandardError, ScriptError => e @printer.print_exception(e, @state.binding) throw :debug_error diff --git a/lib/ruby-debug-ide/greeter.rb b/lib/ruby-debug-ide/greeter.rb index df9b042..7e3dd93 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) + def print_greeting_msg(stream, host, port, debugger_name = "Fast") base_gem_name = if defined?(JRUBY_VERSION) || RUBY_VERSION < '1.9.0' 'ruby-debug-base' elsif RUBY_VERSION < '2.0.0' @@ -31,7 +31,7 @@ def print_greeting_msg(stream, host, port) listens_on = "\n" end - msg = "Fast Debugger (ruby-debug-ide #{IDE_VERSION}, #{base_gem_name} #{VERSION}, file filtering is #{file_filtering_support})" + listens_on + msg = "#{debugger_name} Debugger (ruby-debug-ide #{IDE_VERSION}, #{base_gem_name} #{VERSION}, file filtering is #{file_filtering_support})" + listens_on stream.printf msg end diff --git a/lib/ruby-debug-ide/multiprocess/pre_child.rb b/lib/ruby-debug-ide/multiprocess/pre_child.rb index 4a349fd..b707fc4 100644 --- a/lib/ruby-debug-ide/multiprocess/pre_child.rb +++ b/lib/ruby-debug-ide/multiprocess/pre_child.rb @@ -11,7 +11,7 @@ def pre_child(options = nil) 'frame_bind' => false, 'host' => host, 'load_mode' => false, - 'port' => find_free_port(host), + 'port' => Debugger.find_free_port(host), 'stop' => false, 'tracing' => false, 'int_handler' => true, @@ -24,7 +24,7 @@ def pre_child(options = nil) ) if(options.ignore_port) - options.port = find_free_port(options.host) + options.port = Debugger.find_free_port(options.host) options.notify_dispatcher = true end @@ -54,14 +54,6 @@ def start_debugger(options) Debugger.cli_debug = options.cli_debug Debugger.prepare_debugger(options) end - - - def find_free_port(host) - server = TCPServer.open(host, 0) - port = server.addr[1] - server.close - port - end end end end \ No newline at end of file diff --git a/lib/ruby-debug-ide/thread_alias.rb b/lib/ruby-debug-ide/thread_alias.rb new file mode 100644 index 0000000..4e10a7b --- /dev/null +++ b/lib/ruby-debug-ide/thread_alias.rb @@ -0,0 +1,27 @@ +module Debugger + module TimeoutHandler + class << self + def do_thread_alias + if defined? ::OldThread + Debugger.print_debug 'Tried to re-alias thread for eval' + return + end + + Object.const_set :OldThread, ::Thread + Object.send :remove_const, :Thread + Object.const_set :Thread, ::Debugger::DebugThread + end + + def undo_thread_alias + unless defined? ::OldThread + Debugger.print_debug 'Tried to de-alias thread twice' + return + end + + Object.send :remove_const, :Thread + Object.const_set :Thread, ::OldThread + Object.send :remove_const, :OldThread + end + end + end +end \ No newline at end of file diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 41f8b9f..43d918b 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.6.1' + IDE_VERSION='0.7.0' end diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 49b0b2e..416be25 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -144,19 +144,36 @@ def print_array(array) end end - def print_hash(hash) + def do_print_hash_key_value(hash) + print_element("variables", {:type => 'hashItem'}) do + hash.each {|(k, v)| + print_variable('key', k, 'instance') + print_variable('value', v, 'instance') + } + end + end + + def do_print_hash(hash) print_element("variables") do - hash.keys.each {|k| + hash.each {|(k, v)| if k.class.name == "String" name = '\'' + k + '\'' else name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) end - print_variable(name, hash[k], 'instance') + print_variable(name, v, 'instance') } end end + def print_hash(hash) + if Debugger.key_value_mode + do_print_hash_key_value(hash) + else + do_print_hash(hash) + end + end + def print_string(string) print_element("variables") do if string.respond_to?('bytes') @@ -243,6 +260,7 @@ def exec_with_allocation_control(value, exec_method, overflow_message_type) def print_variable(name, value, kind) name = name.to_s + if value.nil? print("", CGI.escapeHTML(name), kind) return @@ -280,6 +298,7 @@ def print_variable(name, value, kind) CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind, build_value_attr(escaped_value_str), value.class, has_children, value.object_id) + print("", escaped_value_str) if Debugger.value_as_nested_element print('') rescue StandardError => e @@ -439,10 +458,12 @@ def print_load_result(file, exception = nil) end end - def print_element(name) - print("<#{name}>") + def print_element(name, additional_tags = nil) + additional_tags_presentation = additional_tags.nil? ? '' : additional_tags.map {|tag, value| " #{tag}=\"#{value}\""}.reduce(:+) + + print("<#{name}#{additional_tags_presentation}>") begin - yield + yield if block_given? ensure print("") end diff --git a/test-base/inspect_test.rb b/test-base/inspect_test.rb index ca57de8..c7ff66b 100644 --- a/test-base/inspect_test.rb +++ b/test-base/inspect_test.rb @@ -58,6 +58,30 @@ def test_inspect_error_2 send_cont end + def test_inspect_expr_with_timeout + create_socket ["require 'timeout'", "puts 'test'"] + run_to_line(2) + send_ruby("v inspect (Timeout::timeout(10) { nil })") + variables = read_variables + assert_equal(1, variables.length, "There is one variable returned which is nil.") + assert_equal(nil, variables[0].value) + send_cont + end + + def test_inspect_failing_expr_with_timeout + if RUBY_VERSION < "1.9" + @process_finished = true + return + end + create_socket ["require 'timeout'", "puts 'test'"] + run_to_line(2) + send_ruby("v inspect (Timeout::timeout(0.1) { sleep 0.2 })") + variables = read_variables + assert_equal(1, variables.length, "There is one variable returned which is nil.") + assert_equal("Timeout::Error", variables[0].type) + send_cont + end + def test_inspect_multiline_expression create_socket ["sleep 0.1"] run_to_line(1) diff --git a/test-base/test_base.rb b/test-base/test_base.rb index 01bd85d..c026750 100644 --- a/test-base/test_base.rb +++ b/test-base/test_base.rb @@ -82,9 +82,9 @@ def debug_jruby? config_load('debug_jruby') end - def start_ruby_process(script) + def start_ruby_process(script, additional_opts = '') @port = TestBase.find_free_port - cmd = debug_command(script, @port) + cmd = debug_command(script, @port, additional_opts) debug "Starting: #{cmd}\n" Thread.new do @@ -132,8 +132,9 @@ def TestBase.find_free_port(port = 1098) end def create_file(script_name, lines) - script_path = File.realdirpath(File.join(TMP_DIR, script_name)) - + file = File.join(TMP_DIR, script_name) + script_path = RUBY_VERSION >= "1.9" ? File.realdirpath(file) : file.to_s + File.open(script_path, "w") do |script| script.printf(lines.join("\n")) end @@ -142,15 +143,17 @@ def create_file(script_name, lines) def create_test2(lines) @test2_name = "test2.rb" - @test2_path = create_file(@test2_name, lines).force_encoding(Encoding::UTF_8) + @test2_path = create_file(@test2_name, lines) + + @test2_path = @test2_path.force_encoding(Encoding::UTF_8) if RUBY_VERSION >= "1.9" end # Creates test.rb with the given lines, set up @test_name and @test_path # variables and then start the process. - def create_socket(lines) + def create_socket(lines, additional_opts = '') @test_name = "test.rb" @test_path = create_file(@test_name, lines) - start_ruby_process(@test_path) + start_ruby_process(@test_path, additional_opts) end def socket diff --git a/test-base/variables_test.rb b/test-base/variables_test.rb index 67f9b48..85a44b3 100644 --- a/test-base/variables_test.rb +++ b/test-base/variables_test.rb @@ -100,7 +100,9 @@ def test_variable_local end def test_variable_instance - create_socket ["require_relative 'test2.rb'", "custom_object=Test2.new", "puts custom_object"] + require_string = RUBY_VERSION < "1.9" ? "require" : "require_relative" + + create_socket ["#{require_string} 'test2.rb'", "custom_object=Test2.new", "puts custom_object"] create_test2 ["class Test2", "def initialize", "@y=5", "end", "def to_s", "'test'", "end", "end"] run_to("test2.rb", 6) frame_number = 3 @@ -130,8 +132,9 @@ def test_variable_hash_with_string_keys assert_variables(read_variables, 1, {:name => "hash", :hasChildren => true}) send_ruby("v i hash") + expected_name = CGI::escapeHTML("'a'") assert_variables(read_variables, 2, - {:name => CGI.escape_html("'a'"), :value => "z", :type => "String"}) + {:name => expected_name, :value => "z", :type => "String"}) send_cont end @@ -199,22 +202,75 @@ def test_to_s_raises_exception send_cont end + def test_new_hash_presentation + create_socket ['class A', + ' def to_s', + ' "A instance"', + ' end', + 'end', + + 'class C', + ' def to_s', + ' "C instance"', + ' end', + 'end', + + 'b = Hash.new', + 'c = C.new', + 'a = A.new', + 'b[1] = a', + 'b[a] = "1"', + 'b[c] = a', + 'puts b #bp here'], '--key-value' + run_to_line(17) + send_ruby('v l') + assert_variables(read_variables, 3, + {:name => "a", :value => "A instance",:type => "A"}, + {:name => "b", :value => "Hash (3 elements)", :type => "Hash"}, + {:name => "c", :value => "C instance", :type => "C"}) + + send_ruby("v i b") + + variables = [] + + read_variables.each_slice(2) do |var| + variables << var + end + + assert_variables(variables.sort_by{|a| a[0].value}.flatten, 6, + {:name => "key", :value => "1"}, + {:name => "value", :value => "A instance", :type => "A"}, + + {:name => "key", :value => "A instance", :type => "A"}, + {:name => "value", :value => "1", :type => "String"}, + + {:name => "key", :value => "C instance", :type => "C"}, + {:name => "value", :value => "A instance", :type => "A"}) + send_cont + end + def test_to_s_timelimit + #no TracePointApi for old versions + if RUBY_VERSION <= "1.9" + @process_finished = true + return + end create_socket ['class A', - 'def to_s', - 'a = 1', - 'loop do', - 'a = a + 1', - 'sleep 1', - 'break if (a > 2)', - 'end', - 'a.to_s', - 'end', - 'end', - 'b = Hash.new', - 'b[A.new] = A.new', - 'b[1] = A.new', - 'puts b #bp here'] + 'def to_s', + 'a = 1', + 'loop do', + 'a = a + 1', + 'sleep 1', + 'break if (a > 2)', + 'end', + 'a.to_s', + 'end', + 'end', + + 'b = Hash.new', + 'b[A.new] = A.new', + 'b[1] = A.new', + 'puts b #bp here'], '--evaluation-control --time-limit 100 --memory-limit 0' run_to_line(15) send_ruby('v l') assert_variables(read_variables, 1, diff --git a/test/rd_test_base.rb b/test/rd_test_base.rb index 7d29edc..d5b7e4f 100644 --- a/test/rd_test_base.rb +++ b/test/rd_test_base.rb @@ -18,13 +18,13 @@ def setup end end - def debug_command(script, port) + def debug_command(script, port, additional_opts='') 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} --evaluation-control --time-limit 100 --memory-limit 0 -- '#{script}'" + " -p #{port} #{additional_opts} -- '#{script}'" end def start_debugger