diff options
Diffstat (limited to 'railties')
-rw-r--r-- | railties/CHANGELOG | 2 | ||||
-rw-r--r-- | railties/lib/binding_of_caller.rb | 168 | ||||
-rw-r--r-- | railties/lib/breakpoint.rb | 89 | ||||
-rw-r--r-- | railties/lib/breakpoint_client.rb | 207 |
4 files changed, 240 insertions, 226 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG index fb4fa31707..baa5494f41 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross] + * Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik] * Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net] diff --git a/railties/lib/binding_of_caller.rb b/railties/lib/binding_of_caller.rb index 759e760016..d18fbdef3d 100644 --- a/railties/lib/binding_of_caller.rb +++ b/railties/lib/binding_of_caller.rb @@ -1,85 +1,83 @@ -begin - require 'simplecc' -rescue LoadError - class Continuation #:nodoc: - def self.create(*args, &block) - cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} - result ||= args - return *[cc, *result] - end - end -end - -class Binding #:nodoc: - # This method returns the binding of the method that called your - # method. It will raise an Exception when you're not inside a method. - # - # It's used like this: - # def inc_counter(amount = 1) - # Binding.of_caller do |binding| - # # Create a lambda that will increase the variable 'counter' - # # in the caller of this method when called. - # inc = eval("lambda { |arg| counter += arg }", binding) - # # We can refer to amount from inside this block safely. - # inc.call(amount) - # end - # # No other statements can go here. Put them inside the block. - # end - # counter = 0 - # 2.times { inc_counter } - # counter # => 2 - # - # Binding.of_caller must be the last statement in the method. - # This means that you will have to put everything you want to - # do after the call to Binding.of_caller into the block of it. - # This should be no problem however, because Ruby has closures. - # If you don't do this an Exception will be raised. Because of - # the way that Binding.of_caller is implemented it has to be - # done this way. - def self.of_caller(&block) - old_critical = Thread.critical - Thread.critical = true - count = 0 - cc, result, error, extra_data = Continuation.create(nil, nil) - error.call if error - - tracer = lambda do |*args| - type, context, extra_data = args[0], args[4], args - if type == "return" - count += 1 - # First this method and then calling one will return -- - # the trace event of the second event gets the context - # of the method which called the method that called this - # method. - if count == 2 - # It would be nice if we could restore the trace_func - # that was set before we swapped in our own one, but - # this is impossible without overloading set_trace_func - # in current Ruby. - set_trace_func(nil) - cc.call(eval("binding", context), nil, extra_data) - end - elsif type == "line" then - nil - elsif type == "c-return" and extra_data[3] == :set_trace_func then - nil - else - set_trace_func(nil) - error_msg = "Binding.of_caller used in non-method context or " + - "trailing statements of method using it aren't in the block." - cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) - end - end - - unless result - set_trace_func(tracer) - return nil - else - Thread.critical = old_critical - case block.arity - when 1 then yield(result) - else yield(result, extra_data) - end - end - end -end
\ No newline at end of file +begin
+ require 'simplecc'
+rescue LoadError
+ class Continuation; end # :nodoc: # for RDoc
+ def Continuation.create(*args, &block) # :nodoc:
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
+ result ||= args
+ return *[cc, *result]
+ end
+end
+
+class Binding; end # for RDoc
+# This method returns the binding of the method that called your
+# method. It will raise an Exception when you're not inside a method.
+#
+# It's used like this:
+# def inc_counter(amount = 1)
+# Binding.of_caller do |binding|
+# # Create a lambda that will increase the variable 'counter'
+# # in the caller of this method when called.
+# inc = eval("lambda { |arg| counter += arg }", binding)
+# # We can refer to amount from inside this block safely.
+# inc.call(amount)
+# end
+# # No other statements can go here. Put them inside the block.
+# end
+# counter = 0
+# 2.times { inc_counter }
+# counter # => 2
+#
+# Binding.of_caller must be the last statement in the method.
+# This means that you will have to put everything you want to
+# do after the call to Binding.of_caller into the block of it.
+# This should be no problem however, because Ruby has closures.
+# If you don't do this an Exception will be raised. Because of
+# the way that Binding.of_caller is implemented it has to be
+# done this way.
+def Binding.of_caller(&block)
+ old_critical = Thread.critical
+ Thread.critical = true
+ count = 0
+ cc, result, error, extra_data = Continuation.create(nil, nil)
+ error.call if error
+
+ tracer = lambda do |*args|
+ type, context, extra_data = args[0], args[4], args
+ if type == "return"
+ count += 1
+ # First this method and then calling one will return --
+ # the trace event of the second event gets the context
+ # of the method which called the method that called this
+ # method.
+ if count == 2
+ # It would be nice if we could restore the trace_func
+ # that was set before we swapped in our own one, but
+ # this is impossible without overloading set_trace_func
+ # in current Ruby.
+ set_trace_func(nil)
+ cc.call(eval("binding", context), nil, extra_data)
+ end
+ elsif type == "line" then
+ nil
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
+ nil
+ else
+ set_trace_func(nil)
+ error_msg = "Binding.of_caller used in non-method context or " +
+ "trailing statements of method using it aren't in the block."
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
+ end
+ end
+
+ unless result
+ set_trace_func(tracer)
+ return nil
+ else
+ Thread.critical = old_critical
+ case block.arity
+ when 1 then yield(result)
+ else yield(result, extra_data)
+ end
+ end
+end
diff --git a/railties/lib/breakpoint.rb b/railties/lib/breakpoint.rb index 6de2d64a7e..1923d49733 100644 --- a/railties/lib/breakpoint.rb +++ b/railties/lib/breakpoint.rb @@ -21,6 +21,9 @@ require 'drb' require 'drb/acl'
module Breakpoint
+ id = %q$Id: breakpoint.rb 41 2005-01-22 20:22:10Z flgr $
+ Version = id.split(" ")[2].to_i
+
extend self
# This will pop up an interactive ruby session at a
@@ -114,10 +117,10 @@ module Breakpoint end
end
- module CommandBundle #:nodoc:
+ module CommandBundle
# Proxy to a Breakpoint client. Lets you directly execute code
# in the context of the client.
- class Client#:nodoc:
+ class Client
def initialize(eval_handler) # :nodoc:
@eval_handler = eval_handler
end
@@ -133,15 +136,23 @@ module Breakpoint end
# Will execute the specified statement at the client.
- def method_missing(method, *args)
- if args.empty?
- result = eval("#{method}")
+ def method_missing(method, *args, &block)
+ if args.empty? and not block
+ result = eval "#{method}"
else
- result = eval("#{method}(*Marshal.load(#{Marshal.dump(args).inspect}))")
- end
-
- unless [true, false, nil].include?(result)
- result.extend(DRbUndumped) if result
+ # This is a bit ugly. The alternative would be using an
+ # eval context instead of an eval handler for executing
+ # the code at the client. The problem with that approach
+ # is that we would have to handle special expressions
+ # like "self", "nil" or constants ourself which is hard.
+ remote = eval %{
+ result = lambda { |block, *args| #{method}(*args, &block) }
+ def result.call_with_block(*args, &block)
+ call(block, *args)
+ end
+ result
+ }
+ remote.call_with_block(*args, &block)
end
return result
@@ -175,6 +186,7 @@ module Breakpoint # client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
def client()
if Breakpoint.use_drb? then
+ sleep(0.5) until Breakpoint.drb_service.eval_handler
Client.new(Breakpoint.drb_service.eval_handler)
else
Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
@@ -205,7 +217,7 @@ module Breakpoint # These exceptions will be raised on failed asserts
# if Breakpoint.asserts_cause_exceptions is set to
# true.
- class FailedAssertError < RuntimeError#:nodoc:
+ class FailedAssertError < RuntimeError
end
# This asserts that the block evaluates to true.
@@ -279,7 +291,7 @@ module Breakpoint @collision_handler.call
end
- def ping; end
+ def ping() end
def add_breakpoint(context, message)
workspace = IRB::WorkSpace.new(context)
@@ -290,31 +302,7 @@ module Breakpoint @handler.call(workspace, message)
end
- def register_handler(&block)
- @handler = block
- end
-
- def unregister_handler
- @handler = nil
- end
-
- attr_reader :eval_handler
-
- def register_eval_handler(&block)
- @eval_handler = block
- end
-
- def unregister_eval_handler
- @eval_handler = lambda { }
- end
-
- def register_collision_handler(&block)
- @collision_handler = block
- end
-
- def unregister_collision_handler
- @collision_handler = lambda { }
- end
+ attr_accessor :handler, :eval_handler, :collision_handler
end
# Will run Breakpoint in DRb mode. This will spawn a server
@@ -359,7 +347,8 @@ module Breakpoint #
# Detailed information about running DRb through firewalls is
# available at http://www.rubygarden.org/ruby?DrbTutorial
- def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) #:nodoc:
+ def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
+ ignore_collisions = false)
return false if @use_drb
@@ -402,7 +391,7 @@ module Breakpoint end
# Deactivates a running Breakpoint service.
- def deactivate_drb #:nodoc:
+ def deactivate_drb
@service.stop_service unless @service.nil?
@service = nil
@use_drb = false
@@ -411,7 +400,7 @@ module Breakpoint # Returns true when Breakpoints are used over DRb.
# Breakpoint.activate_drb causes this to be true.
- def use_drb? #:nodoc:
+ def use_drb?
@use_drb == true
end
end
@@ -440,7 +429,11 @@ module IRB # :nodoc: @CONF[:MAIN_CONTEXT] = irb.context
old_sigint = trap("SIGINT") do
- irb.signal_handle
+ begin
+ irb.signal_handle
+ rescue RubyLex::TerminateLineInput
+ # ignored
+ end
end
catch(:IRB_EXIT) do
@@ -464,7 +457,7 @@ module IRB # :nodoc: end
end
- class Context#:nodoc:
+ class Context
alias :old_evaluate :evaluate
def evaluate(line, line_no)
if line.chomp == "exit" then
@@ -475,7 +468,7 @@ module IRB # :nodoc: end
end
- class WorkSpace#:nodoc:
+ class WorkSpace
alias :old_evaluate :evaluate
def evaluate(*args)
@@ -493,7 +486,7 @@ module IRB # :nodoc: end
end
- module InputCompletor#:nodoc:
+ module InputCompletor
def self.eval(code, context, *more)
# Big hack, this assumes that InputCompletor
# will only call eval() when it wants code
@@ -504,9 +497,9 @@ module IRB # :nodoc: end
module DRb # :nodoc:
- class DRbObject#:nodoc:
- undef :inspect
- undef :clone
+ class DRbObject
+ undef :inspect if method_defined?(:inspect)
+ undef :clone if method_defined?(:clone)
end
end
@@ -522,4 +515,4 @@ def assert(&block) Binding.of_caller do |context|
Breakpoint.assert(context, &block)
end
-end
\ No newline at end of file +end
diff --git a/railties/lib/breakpoint_client.rb b/railties/lib/breakpoint_client.rb index fa93c11f3e..0abcb6824e 100644 --- a/railties/lib/breakpoint_client.rb +++ b/railties/lib/breakpoint_client.rb @@ -2,17 +2,18 @@ require 'breakpoint' require 'optparse' require 'timeout' -options = { +Options = { :ClientURI => nil, :ServerURI => "druby://localhost:42531", - :RetryDelay => 1, + :RetryDelay => 3, + :Permanent => true, :Verbose => false } ARGV.options do |opts| script_name = File.basename($0) opts.banner = [ - "Usage: ruby #{script_name} [options] [server uri]", + "Usage: ruby #{script_name} [Options] [server uri]", "", "This tool lets you connect to a breakpoint service ", "which was started via Breakpoint.activate_drb.", @@ -29,18 +30,13 @@ ARGV.options do |opts| "connections from the server.", "Default: Find a good URI automatically.", "Example: -c druby://localhost:12345" - ) { |options[:ClientURI]| } + ) { |Options[:ClientURI]| } opts.on("-s", "--server-uri=uri", "Connect to the server specified at the", "specified uri.", "Default: druby://localhost:42531" - ) { |options[:ServerURI]| } - - opts.on("-v", "--verbose", - "Report all connections and disconnections", - "Default: false" - ) { |options[:Verbose]| } + ) { |Options[:ServerURI]| } opts.on("-R", "--retry-delay=delay", Integer, "Automatically try to reconnect to the", @@ -49,124 +45,149 @@ ARGV.options do |opts| "A value of 0 disables automatical", "reconnecting completely.", "Default: 10" - ) { |options[:RetryDelay]| } + ) { |Options[:RetryDelay]| } + + opts.on("-P", "--[no-]permanent", + "Run the breakpoint client in permanent mode.", + "This means that the client will keep continue", + "running even after the server has closed the", + "connection. Useful for example in Rails." + ) { |Options[:Permanent]| } + + opts.on("-V", "--[no-]verbose", + "Run the breakpoint client in verbose mode.", + "Will produce more messages, for example between", + "individual breakpoints. This might help in seeing", + "that the breakpoint client is still alive, but adds", + "quite a bit of clutter." + ) { |Options[:Verbose]| } opts.separator "" opts.on("-h", "--help", "Show this help message." ) { puts opts; exit } + opts.on("-v", "--version", + "Display the version information." + ) do + id = %q$Id: breakpoint_client.rb 40 2005-01-22 20:05:00Z flgr $ + puts id.sub("Id: ", "") + puts "(Breakpoint::Version = #{Breakpoint::Version})" + exit + end opts.parse! end -options[:ServerURI] = ARGV[0] if ARGV[0] +Options[:ServerURI] = ARGV[0] if ARGV[0] -$running = true +module Handlers + extend self -trap("INT"){$running = false} + def breakpoint_handler(workspace, message) + puts message + IRB.start(nil, nil, workspace) + + puts "" + if Options[:Verbose] then + puts "Resumed execution. Waiting for next breakpoint...", "" + end + end + + def eval_handler(code) + result = eval(code, TOPLEVEL_BINDING) + if result then + DRbObject.new(result) + else + result + end + end -puts "Waiting for initial breakpoint..." + def collision_handler() + msg = [ + " *** Breakpoint service collision ***", + " Another Breakpoint service tried to use the", + " port already occupied by this one. It will", + " keep waiting until this Breakpoint service", + " is shut down.", + " ", + " If you are using the Breakpoint library for", + " debugging a Rails or other CGI application", + " this likely means that this Breakpoint", + " session belongs to an earlier, outdated", + " request and should be shut down via 'exit'." + ].join("\n") + + if RUBY_PLATFORM["win"] then + # This sucks. Sorry, I'm not doing this because + # I like funky message boxes -- I need to do this + # because on Windows I have no way of displaying + # my notification via puts() when gets() is still + # being performed on STDIN. I have not found a + # better solution. + begin + require 'tk' + root = TkRoot.new { withdraw } + Tk.messageBox('message' => msg, 'type' => 'ok') + root.destroy + rescue Exception + puts "", msg, "" + end + else + puts "", msg, "" + end + end +end + +# Used for checking whether we are currently in the reconnecting loop. +reconnecting = false loop do - DRb.start_service(options[:ClientURI]) + DRb.start_service(Options[:ClientURI]) begin - service = DRbObject.new(nil, options[:ServerURI]) + service = DRbObject.new(nil, Options[:ServerURI]) begin - timeout(10) { service.ping } - rescue Timeout::Error, DRb::DRbConnError - if options[:Verbose] - puts "", - " *** Breakpoint service didn't respond to ping request ***", - " This likely happened because of a misconfigured ACL (see the", - " documentation of Breakpoint.activate_drb, note that by default", - " you can only connect to a remote Breakpoint service via a SSH", - " tunnel), but might also be caused by an extremely slow connection.", - "" - end - raise - end + service.eval_handler = Handlers.method(:eval_handler) + service.collision_handler = Handlers.method(:collision_handler) + service.handler = Handlers.method(:breakpoint_handler) - begin - service.register_eval_handler do |code| - result = eval(code, TOPLEVEL_BINDING) - if result - DRbObject.new(result) - else - result - end - end - - service.register_collision_handler do - msg = [ - " *** Breakpoint service collision ***", - " Another Breakpoint service tried to use the", - " port already occupied by this one. It will", - " keep waiting until this Breakpoint service", - " is shut down.", - " ", - " If you are using the Breakpoint library for", - " debugging a Rails or other CGI application", - " this likely means that this Breakpoint", - " session belongs to an earlier, outdated", - " request and should be shut down via 'exit'." - ].join("\n") - - if RUBY_PLATFORM["win"] then - # This sucks. Sorry, I'm not doing this because - # I like funky message boxes -- I need to do this - # because on Windows I have no way of displaying - # my notification via puts() when gets() is still - # being performed on STDIN. I have not found a - # better solution. - begin - require 'tk' - root = TkRoot.new { withdraw } - Tk.messageBox('message' => msg, 'type' => 'ok') - root.destroy - rescue Exception - puts "", msg, "" - end - else - puts "", msg, "" - end + reconnecting = false + if Options[:Verbose] then + puts "Connection established. Waiting for breakpoint...", "" end - service.register_handler do |workspace, message| - puts message - IRB.start(nil, nil, workspace) - puts "", "Resumed execution. Waiting for next breakpoint...", "" - end - - puts "Connection established. Waiting for breakpoint...", "" if options[:Verbose] - - while $running + loop do begin service.ping rescue DRb::DRbConnError => error - puts "Server exited. Closing connection..." if options[:Verbose] + puts "Server exited. Closing connection...", "" + exit! unless Options[:Permanent] break end sleep(0.5) end ensure - service.unregister_handler + service.eval_handler = nil + service.collision_handler = nil + service.handler = nil end rescue Exception => error - break unless $running - if options[:RetryDelay] > 0 then - puts "No connection to breakpoint service at #{options[:ServerURI]}:", " (#{error.inspect})" if options[:Verbose] - error.backtrace if $DEBUG - - puts " Reconnecting in #{options[:RetryDelay]} seconds..." if options[:Verbose] - - sleep options[:RetryDelay] + if Options[:RetryDelay] > 0 then + if not reconnecting then + reconnecting = true + puts "No connection to breakpoint service at #{Options[:ServerURI]} " + + "(#{error.class})" + puts error.backtrace if $DEBUG + puts "Tries to connect will be made every #{Options[:RetryDelay]} seconds..." + end + + sleep Options[:RetryDelay] retry else raise end end -end
\ No newline at end of file +end |