Module: Breakpoint
Defined Under Namespace
Modules: CommandBundle Classes: DRbService, FailedAssertError
Constant Summary collapse
- Version =
id.split(" ")[2].to_i
Instance Attribute Summary collapse
-
#asserts_cause_exceptions ⇒ Object
Whether an Exception should be raised on failed asserts in non-$DEBUG code or not.
-
#drb_service ⇒ Object
readonly
:nodoc:.
-
#optimize_asserts ⇒ Object
Whether asserts should be ignored if not in debug mode.
Instance Method Summary collapse
-
#activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) ⇒ Object
Will run Breakpoint in DRb mode.
-
#assert(context = nil, &condition) ⇒ Object
This asserts that the block evaluates to true.
-
#breakpoint(id = nil, context = nil, &block) ⇒ Object
This will pop up an interactive ruby session at a pre-defined break point in a Ruby application.
-
#deactivate_drb ⇒ Object
Deactivates a running Breakpoint service.
-
#handle_breakpoint(context, message, file = "", line = "", &block) ⇒ Object
:nodoc:.
-
#use_drb? ⇒ Boolean
Returns true when Breakpoints are used over DRb.
Instance Attribute Details
#asserts_cause_exceptions ⇒ Object
Whether an Exception should be raised on failed asserts in non-$DEBUG code or not. By default this is disabled.
273 274 275 |
# File 'lib/breakpoint.rb', line 273 def asserts_cause_exceptions @asserts_cause_exceptions end |
#drb_service ⇒ Object (readonly)
:nodoc:
277 278 279 |
# File 'lib/breakpoint.rb', line 277 def drb_service @drb_service end |
#optimize_asserts ⇒ Object
Whether asserts should be ignored if not in debug mode. Debug mode can be enabled by running ruby with the -d switch or by setting $DEBUG to true.
268 269 270 |
# File 'lib/breakpoint.rb', line 268 def optimize_asserts @optimize_asserts end |
Instance Method Details
#activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) ⇒ Object
Will run Breakpoint in DRb mode. This will spawn a server that can be attached to via the breakpoint-client command whenever a breakpoint is executed. This is useful when you are debugging CGI applications or other applications where you can’t access debug sessions via the standard input and output of your application.
You can specify an URI where the DRb server will run at. This way you can specify the port the server runs on. The default URI is druby://localhost:42531.
Please note that breakpoints will be skipped silently in case the DRb server can not spawned. (This can happen if the port is already used by another instance of your application on CGI or another application.)
Also note that by default this will only allow access from localhost. You can however specify a list of allowed hosts or nil (to allow access from everywhere). But that will still not protect you from somebody reading the data as it goes through the net.
A good approach for getting security and remote access is setting up an SSH tunnel between the DRb service and the client. This is usually done like this:
$ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com (This will connect port 20000 at the client side to port 20000 at the server side, and port 10000 at the server side to port 10000 at the client side.)
After that do this on the server side: (the code being debugged) Breakpoint.activate_drb(“druby://127.0.0.1:20000”, “localhost”)
And at the client side: ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
Running through such a SSH proxy will also let you use breakpoint.rb in case you are behind a firewall.
Detailed information about running DRb through firewalls is available at www.rubygarden.org/ruby?DrbTutorial
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/breakpoint.rb', line 354 def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) return false if @use_drb uri ||= 'druby://localhost:42531' if allowed_hosts then acl = ["deny", "all"] Array(allowed_hosts).each do |host| acl += ["allow", host] end DRb.install_acl(ACL.new(acl)) end @use_drb = true @drb_service = DRbService.new did_collision = false begin @service = DRb.start_service(uri, @drb_service) rescue Errno::EADDRINUSE if ignore_collisions then nil else # The port is already occupied by another # Breakpoint service. We will try to tell # the old service that we want its port. # It will then forward that request to the # user and retry. unless did_collision then DRbObject.new(nil, uri).collision did_collision = true end sleep(10) retry end end return true end |
#assert(context = nil, &condition) ⇒ Object
This asserts that the block evaluates to true. If it doesn’t evaluate to true a breakpoint will automatically be created at that execution point.
You can disable assert checking in production code by setting Breakpoint.optimize_asserts to true. (It will still be enabled when Ruby is run via the -d argument.)
Example:
person_name = "Foobar"
assert { not person_name.nil? }
Note: If you want to use this method from an unit test, you will have to call it by its full name, Breakpoint.assert.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/breakpoint.rb', line 240 def assert(context = nil, &condition) return if Breakpoint.optimize_asserts and not $DEBUG return if yield callstack = caller callstack.slice!(0, 3) if callstack.first["assert"] file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}." if Breakpoint.asserts_cause_exceptions and not $DEBUG then raise(Breakpoint::FailedAssertError, ) end += " Executing implicit breakpoint." if context then return handle_breakpoint(context, , file, line) end Binding.of_caller do |context| handle_breakpoint(context, , file, line) end end |
#breakpoint(id = nil, context = nil, &block) ⇒ Object
This will pop up an interactive ruby session at a pre-defined break point in a Ruby application. In this session you can examine the environment of the break point.
You can get a list of variables in the context using local_variables via local_variables
. You can then examine their values by typing their names.
You can have a look at the call stack via caller
.
The source code around the location where the breakpoint was executed can be examined via source_lines
. Its argument specifies how much lines of context to display. The default amount of context is 5 lines. Note that the call to source_lines
can raise an exception when it isn’t able to read in the source code.
breakpoints can also return a value. They will execute a supplied block for getting a default return value. A custom value can be returned from the session by doing throw(:debug_return, value).
You can also give names to break points which will be used in the message that is displayed upon execution of them.
Here’s a sample of how breakpoints should be placed:
class Person
def initialize(name, age)
@name, @age = name, age
breakpoint("Person#initialize")
end
attr_reader :age
def name
breakpoint("Person#name") { @name }
end
end
person = Person.new("Random Person", 23)
puts "Name: #{person.name}"
And here is a sample debug session:
Executing break point "Person#initialize" at file.rb:4 in `initialize'
irb(#<Person:0x292fbe8>):001:0> local_variables
=> ["name", "age", "_", "__"]
irb(#<Person:0x292fbe8>):002:0> [name, age]
=> ["Random Person", 23]
irb(#<Person:0x292fbe8>):003:0> [@name, @age]
=> ["Random Person", 23]
irb(#<Person:0x292fbe8>):004:0> self
=> #<Person:0x292fbe8 @age=23, @name="Random Person">
irb(#<Person:0x292fbe8>):005:0> @age += 1; self
=> #<Person:0x292fbe8 @age=24, @name="Random Person">
irb(#<Person:0x292fbe8>):006:0> exit
Executing break point "Person#name" at file.rb:9 in `name'
irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
Name: Overriden name
Breakpoint sessions will automatically have a few convenience methods available. See Breakpoint::CommandBundle for a list of them.
Breakpoints can also be used remotely over sockets. This is implemented by running part of the IRB session in the application and part of it in a special client. You have to call Breakpoint.activate_drb to enable support for remote breakpoints and then run breakpoint_client.rb which is distributed with this library. See the documentation of Breakpoint.activate_drb for details.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/breakpoint.rb', line 103 def breakpoint(id = nil, context = nil, &block) callstack = caller callstack.slice!(0, 3) if callstack.first["breakpoint"] file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures = "Executing break point " + (id ? "#{id.inspect} " : "") + "at #{file}:#{line}" + (method ? " in `#{method}'" : "") if context then return handle_breakpoint(context, , file, line, &block) end Binding.of_caller do |binding_context| handle_breakpoint(binding_context, , file, line, &block) end end |
#deactivate_drb ⇒ Object
Deactivates a running Breakpoint service.
398 399 400 401 402 403 |
# File 'lib/breakpoint.rb', line 398 def deactivate_drb @service.stop_service unless @service.nil? @service = nil @use_drb = false @drb_service = nil end |
#handle_breakpoint(context, message, file = "", line = "", &block) ⇒ Object
:nodoc:
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/breakpoint.rb', line 198 def handle_breakpoint(context, , file = "", line = "", &block) # :nodoc: catch(:debug_return) do |value| eval(%{ @__bp_file = #{file.inspect} @__bp_line = #{line} extend Breakpoint::CommandBundle extend DRbUndumped if self }, context) rescue nil if not use_drb? then puts IRB.start(nil, IRB::WorkSpace.new(context)) else @drb_service.add_breakpoint(context, ) end block.call if block end end |
#use_drb? ⇒ Boolean
Returns true when Breakpoints are used over DRb. Breakpoint.activate_drb causes this to be true.
407 408 409 |
# File 'lib/breakpoint.rb', line 407 def use_drb? @use_drb == true end |