Module: Breakpoint

Extended by:
Breakpoint
Included in:
Breakpoint
Defined in:
lib/breakpoint.rb

Defined Under Namespace

Modules: CommandBundle Classes: DRbService, FailedAssertError

Constant Summary collapse

Version =
id.split(" ")[2].to_i

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#asserts_cause_exceptionsObject

Whether an Exception should be raised on failed asserts in non-$DEBUG code or not. By default this is disabled.



303
304
305
# File 'lib/breakpoint.rb', line 303

def asserts_cause_exceptions
  @asserts_cause_exceptions
end

#drb_serviceObject (readonly)

:nodoc:



307
308
309
# File 'lib/breakpoint.rb', line 307

def drb_service
  @drb_service
end

#optimize_assertsObject

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.



298
299
300
# File 'lib/breakpoint.rb', line 298

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



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/breakpoint.rb', line 384

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.



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/breakpoint.rb', line 270

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

  message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."

  if Breakpoint.asserts_cause_exceptions and not $DEBUG then
    raise(Breakpoint::FailedAssertError, message)
  end

  message += " Executing implicit breakpoint."

  if context then
    return handle_breakpoint(context, message, file, line)
  end

  Binding.of_caller do |context|
    handle_breakpoint(context, message, 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.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/breakpoint.rb', line 109

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

  message = "Executing break point " + (id ? "#{id.inspect} " : "") +
            "at #{file}:#{line}" + (method ? " in `#{method}'" : "")

  if context then
    return handle_breakpoint(context, message, file, line, &block)
  end

  Binding.of_caller do |binding_context|
    handle_breakpoint(binding_context, message, file, line, &block)
  end
end

#deactivate_drbObject

Deactivates a running Breakpoint service.



428
429
430
431
432
433
# File 'lib/breakpoint.rb', line 428

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:



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/breakpoint.rb', line 228

def handle_breakpoint(context, message, 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 message
      IRB.start(nil, IRB::WorkSpace.new(context))
    else
      @drb_service.add_breakpoint(context, message)
    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.

Returns:

  • (Boolean)


437
438
439
# File 'lib/breakpoint.rb', line 437

def use_drb?
  @use_drb == true
end