pry_debug 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ *.rbc
@@ -0,0 +1,253 @@
1
+ # PryDebug
2
+
3
+ PryDebug is a pure-ruby debugger, It simply relies on ``set_trace_func`` and
4
+ uses Pry (an alternative to IRB) to evaluate code on breakpoints. This means you
5
+ have complete access to local variables (you can even change them!), and can get
6
+ any information you want using methods and Pry's commands (find out the value of
7
+ an instance variable, for example).
8
+
9
+ If you wonder why using a debugger: how often have you written things like this?
10
+
11
+ ```ruby
12
+ puts "HERE!!!"
13
+
14
+ # add as many variables as needed until you find the source of the issue.
15
+ p :foo => foo, :bar => bar, :baz => baz, :self => self
16
+ ```
17
+
18
+ Adding a breakpoint on that line would tell you whether it was executed and let
19
+ you see the value of all those variables and all the other without running your
20
+ code again.
21
+
22
+ ## Installation
23
+
24
+ ```
25
+ gem install pry_debug
26
+ ```
27
+
28
+ PryDebug works has been tested with CRuby 1.9.2 and 1.8.7, and works with some
29
+ glitches in JRuby.
30
+
31
+ ## Example
32
+
33
+ Just use the ``pry_debug`` executable, configure the debugger (add breakpoints,
34
+ ...) and type ``run``.
35
+
36
+ ```
37
+ $ pry_debug file.rb
38
+ debugged file set to test_debug.rb
39
+ pry(main)> b Foo.foo
40
+ addded breakpoint 0 at Foo.foo
41
+ pry(main)> r
42
+ reached breakpoint 0 at Foo.foo
43
+
44
+ From: /home/kilian/code/pry_debug/test_debug.rb @ line 2 in Class#foo:
45
+
46
+ 1: class Foo
47
+ => 2: def self.foo
48
+ 3: a, b, c = 1, 2, 3
49
+ 4: @foo = a + b + c
50
+ 5: end
51
+ 6:
52
+ 7: def initialize(name)
53
+ pry(Foo):1> n
54
+ stepped at /home/kilian/code/pry_debug/test_debug.rb:3
55
+
56
+ From: /home/kilian/code/pry_debug/test_debug.rb @ line 3 in Class#foo:
57
+
58
+ 1: class Foo
59
+ 2: def self.foo
60
+ => 3: a, b, c = 1, 2, 3
61
+ 4: @foo = a + b + c
62
+ 5: end
63
+ 6:
64
+ 7: def initialize(name)
65
+ 8: @name = name
66
+ pry(Foo):2> n
67
+ stepped at /home/kilian/code/pry_debug/test_debug.rb:4
68
+
69
+ From: /home/kilian/code/pry_debug/test_debug.rb @ line 4 in Class#foo:
70
+
71
+ 1: class Foo
72
+ 2: def self.foo
73
+ 3: a, b, c = 1, 2, 3
74
+ => 4: @foo = a + b + c
75
+ 5: end
76
+ 6:
77
+ 7: def initialize(name)
78
+ 8: @name = name
79
+ 9: end
80
+ pry(Foo):3> p a
81
+ 1
82
+ => 1
83
+ pry(Foo):3> ls -i
84
+ Instance variables: []
85
+ ```
86
+
87
+ ## Features
88
+
89
+ ### Break on a line
90
+
91
+ That's quite an important feature: just ``b file.rb:line`` to break whenever
92
+ that line gets executed. Notice you can pass file.rb, path/to/file.rb, or
93
+ /full/path/to/file.rb to refer to the same file.
94
+
95
+ It will run Pry once the breakpoint is reached. When you're done, just type
96
+ ``c`` to continue.
97
+
98
+ Breakpoints can be listed and removed whenever Pry is running:
99
+
100
+ ```
101
+ pry(Foo):3> b test_debug.rb:10
102
+ added breakpoint 1 at test_debug.rb:10
103
+ pry(Foo):3> bl
104
+ breakpoint 0 at Foo.foo
105
+ breakpoint 1 at test_debug.rb:10
106
+ pry(Foo):3> d 0
107
+ breakpoint 0 deleted
108
+ pry(Foo):3> bl
109
+ breakpoint 1 at test_debug.rb:10
110
+ ```
111
+
112
+ (You can use breakpoint instead of b, breakpoint-list instead of bl, delete
113
+ or del instead of d, and continue instead of c; I'm sure most people can't stand
114
+ typing the full command names, though)
115
+
116
+ ### Break on a method
117
+
118
+ Often, you just want to break when a method gets called. Surely you can find the
119
+ location of a method in your code most of the time, but that won't work for
120
+ C-defined methods, and it is more work than just typing the name of the method,
121
+ isn't it?
122
+
123
+ ```
124
+ pry(Foo):3> b SomeClass#some_method
125
+ addded breakpoint 2 at SomeClass#some_method
126
+ ```
127
+
128
+ SomeClass#some_method is used to break on the instance method ``some_method`` of
129
+ the class SomeClass. Both ``SomeClass.some_method`` and
130
+ ``SomeClass::some_method`` will break on a class method.
131
+
132
+ Note that, due to implementation details, it is currently hard for PryDebug to
133
+ identify that some call to Class#new was in fact a call to Foo.new. It still can
134
+ find what was called in most other cases.
135
+
136
+ ### Conditional breakpoints
137
+
138
+ You may want a breakpoint to be run only when a particular condition is met. You
139
+ can add a condition to the breakpoint using ``cond breakpoint_id some code``,
140
+ where some code will get run every time the breakpoint is reached. If it
141
+ evaluated to nil or false, then PryDebug won't actually stop at that
142
+ point. Conditions can also be removed using ``uncond breakpoint_id``.
143
+
144
+ ```
145
+ pry(Foo):3> b foo.rb:15
146
+ added breakpoint 3 at foo.rb:15
147
+ pry(Foo):3> cond 3 @array.size > 10
148
+ condition set to @array.size > 10
149
+ pry(Foo):3> bl
150
+ breakpoint 3 at foo.rb:15 (if @array.size > 10)
151
+ ```
152
+
153
+ (Beware, exceptions in conditions are silently ignored)
154
+
155
+ ### Step and next
156
+
157
+ Breaking at a given place is sometimes not enough. Sometimes, it is useful to
158
+ execute each line to find out what changed. That's what the ``step`` command is for:
159
+ it makes the debugger execute code until the next line before breaking. You can
160
+ also use ``step`` instead of ``run``. It will then break on the first line of
161
+ your code.
162
+
163
+ ``next`` is a similar command, that will break on the next line in the same
164
+ file (though it would ideally break on the next line in the same method instead
165
+ of stepping into it if it is defined in the same file).
166
+
167
+ ### Exceptions
168
+
169
+ You don't need to do anything to benefit from this feature: whenever an
170
+ exception isn't rescued, PryDebug will rescue it and bring you back to the place
171
+ where it occured so you can find out why it was raised:
172
+
173
+ ```
174
+ unrescued exception: RuntimeError: foo
175
+ returning back to where the exception was raised
176
+
177
+ From: /home/kilian/code/pry_debug/test_debug.rb @ line 22 in Object#N/A:
178
+
179
+ 17:
180
+ 18: 100.times.map do |n|
181
+ 19: n * 2
182
+ 20: end
183
+ 21:
184
+ => 22: raise "foo"
185
+ 23:
186
+ 24: __END__
187
+ 25: b Foo.foo
188
+ 26: b Class#inherted
189
+ 27: b FooBar.jump
190
+ pry(main):3>
191
+ ```
192
+
193
+ ### Break on raise
194
+
195
+ This is disabled by default because it is annoying in code where an exception
196
+ being raised and then rescued is normal. It can still be helpful: once enabled,
197
+ raising an exception causes PryDebug to stop. This can be toggled using the
198
+ ``bor`` (``break-on-raise``) command.
199
+
200
+ ```
201
+ pry(main):4> bor
202
+ break on raise enabled
203
+ pry(main):4> r
204
+ exception raised: RuntimeError: foo
205
+
206
+ From: /home/kilian/code/pry_debug/test_debug.rb @ line 22 in Object#N/A:
207
+
208
+ 17:
209
+ 18: 100.times.map do |n|
210
+ 19: n * 2
211
+ 20: end
212
+ 21:
213
+ => 22: raise "foo"
214
+ 23:
215
+ 24: __END__
216
+ 25: b Foo.foo
217
+ 26: b Class#inherted
218
+ 27: b FooBar.jump
219
+ pry(main):5>
220
+ ```
221
+
222
+ ### Start in an existing program
223
+
224
+ Instead of starting your program from PryDebug, you can start PryDebug from your
225
+ program. In this case, it won't handle unrescued exceptions automatically,
226
+ though.
227
+
228
+ ```ruby
229
+ require 'pry_debug'
230
+ PryDebug.start(false) # true makes PryDebug load its own file
231
+
232
+ # you can make it handle exceptions yourself (or break-on-raise instead):
233
+ begin
234
+ # ...
235
+ rescue SystemExit
236
+ # nothing
237
+ rescue Exception => ex
238
+ # PryDebug can still be started
239
+
240
+ if binding = PryDebug.context_of_exception(ex)
241
+ PryDebug.start_pry binding
242
+ else
243
+ PryDebug.start_pry ex
244
+ end
245
+ end
246
+ ```
247
+
248
+ ### Threads
249
+
250
+ It is completely possible you will want to use PryDebug in code that uses
251
+ Thread. PryDebug will ensure that only one thread uses Pry. It will also keep
252
+ information about breakpoints and stepping on a thread basis, to avoid
253
+ unexpected and undetermined results.
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+
3
+ task :test do
4
+ path = File.expand_path("test/run_all.rb", File.dirname(__FILE__))
5
+
6
+ if defined? RUBY_ENGINE and RUBY_ENGINE =~ /jruby/i
7
+ ruby path
8
+ else
9
+ load path
10
+ end
11
+ end
12
+
13
+ task :install do
14
+ ruby "-S gem build pry_debug.gemspec"
15
+ ruby "-S gem install -l pry_debug"
16
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pry_debug'
3
+
4
+ if ARGV[0]
5
+ PryDebug.file = ARGV[0]
6
+ puts "debugged file set to #{ARGV[0]}"
7
+ end
8
+
9
+ PryDebug.start
@@ -0,0 +1,279 @@
1
+ require 'thread'
2
+
3
+ require 'pry'
4
+
5
+ require 'pry_debug/conditional_breakpoint'
6
+ require 'pry_debug/line_breakpoint'
7
+ require 'pry_debug/method_breakpoint'
8
+ require 'pry_debug/commands'
9
+
10
+ module PryDebug
11
+ DefinitionFile = File.expand_path(__FILE__)
12
+
13
+ class << self
14
+ # @return [Array<LineBreakpoint,MethodBreakpoint>] All the enabled breakpoints
15
+ attr_reader :breakpoints
16
+
17
+ attr_accessor :breakpoint_count
18
+
19
+ # @return [String, nil] File that PryDebug loads
20
+ attr_accessor :file
21
+
22
+ # @return [String, nil] If not nil, the file where PryDebug needs to stop
23
+ # (implying that next was called)
24
+ def stepped_file
25
+ Thread.current[:__pry_debug_stepped_file]
26
+ end
27
+
28
+ def stepped_file=(val)
29
+ Thread.current[:__pry_debug_stepped_file] = val
30
+ end
31
+
32
+ # @return [true, false] True if stepping
33
+ def stepping
34
+ Thread.current[:__pry_debug_steppping]
35
+ end
36
+
37
+ def stepping=(val)
38
+ Thread.current[:__pry_debug_stepping] = val
39
+ end
40
+
41
+ # @return [Binding, nil] Binding where last_exception was raised
42
+ def exception_binding
43
+ Thread.current[:__pry_debug_exception_binding]
44
+ end
45
+
46
+ def exception_binding=(b)
47
+ Thread.current[:__pry_debug_exception_binding] = b
48
+ end
49
+
50
+ # @return [Exception, nil] Last exception that PryDebug has heard of
51
+ def last_exception
52
+ Thread.current[:__pry_debug_last_exception]
53
+ end
54
+
55
+ def last_exception=(ex)
56
+ Thread.current[:__pry_debug_last_exception] = ex
57
+ end
58
+
59
+ # @return [true, false] True if PryDebug breaks on raise
60
+ attr_accessor :break_on_raise
61
+
62
+ attr_accessor :debugging
63
+ attr_accessor :will_load
64
+
65
+ attr_reader :mutex
66
+
67
+ # @return [Array<LineBreakpoint>] Breakpoints on a line
68
+ def line_breakpoints
69
+ breakpoints.select { |bp| bp.is_a? LineBreakpoint }
70
+ end
71
+
72
+ # @return [Array<MethodBreakpoint>] Breakpoints on a method
73
+ def method_breakpoints
74
+ breakpoints.select { |bp| bp.is_a? MethodBreakpoint }
75
+ end
76
+
77
+ # @return [Binding, nil] Binding where the exception was raised, if still in
78
+ # memory.
79
+ def context_of_exception(ex)
80
+ if ex.equal? last_exception
81
+ exception_binding
82
+ end
83
+ end
84
+
85
+ # Resets PryDebug to its default state.
86
+ def clean_up
87
+ @breakpoints = []
88
+ @breakpoint_count = -1
89
+ @file = nil
90
+
91
+ Thread.list.each do |th|
92
+ th[:__pry_debug_stepped_file] = nil
93
+ th[:__pry_debug_stepping] = false
94
+
95
+ th[:__pry_debug_exception_binding] = nil
96
+ th[:__pry_debug_last_exception] = nil
97
+ end
98
+
99
+ @break_on_raise = false
100
+ @debugging = false
101
+ @will_load = true
102
+
103
+ @mutex = Mutex.new
104
+ end
105
+ end
106
+
107
+ clean_up
108
+
109
+ module_function
110
+
111
+ # Starts the debguger.
112
+ #
113
+ # @param [true, false] load_file When set to false, PryDebug won't load
114
+ # a file, and simply enable the tracing and let the user setup breakpoints.
115
+ def start(load_file = true)
116
+ PryDebug.will_load = load_file
117
+
118
+ # Importing user-defined commands.
119
+ # NB: what about commands defined in both sets? Currently, user-defined
120
+ # commands override PryDebug's. What about doing it the other way around?
121
+ Pry.load_rc if Pry.config.should_load_rc # user might change Pry.commands
122
+ Pry.config.should_load_rc = false # avoid loading config twice
123
+ ShortCommands.import Pry.commands
124
+
125
+ loop do
126
+ should_start = catch(:start_debugging!) do
127
+ Pry.start(TOPLEVEL_BINDING, :commands => ShortCommands)
128
+ end
129
+
130
+ if should_start == :now!
131
+ set_trace_func trace_proc
132
+ PryDebug.debugging = true
133
+
134
+ return unless load_file
135
+
136
+ begin
137
+ load PryDebug.file
138
+ rescue SystemExit
139
+ # let this go
140
+ rescue Exception => ex
141
+ set_trace_func nil
142
+ puts "unrescued exception: #{ex.class}: #{ex.message}"
143
+
144
+ if binding = PryDebug.context_of_exception(ex)
145
+ msg = "returning back to where the exception was raised"
146
+ start_pry binding, nil, msg
147
+ else
148
+ msg = "context of the exception is unknown, starting pry into\n"
149
+ msg << "the exception."
150
+
151
+ start_pry ex, nil, msg
152
+ end
153
+ end
154
+
155
+ PryDebug.last_exception = PryDebug.exception_binding = nil
156
+ PryDebug.debugging = false
157
+
158
+ set_trace_func nil
159
+ puts "execution terminated"
160
+ else
161
+ break # debugger wasn't started, leave now
162
+ end
163
+ end
164
+ end
165
+
166
+ # Starts Pry with access to ShortCommands
167
+ # @param [Binding, object] binding Context to go to
168
+ # @param [String, nil] file Current file. Used for the next command.
169
+ # @param [String, nil] header Line to print before starting the debugger
170
+ def start_pry(binding, file = nil, header = nil)
171
+ PryDebug.synchronize do
172
+ puts header if header
173
+
174
+ ret = catch(:resume_debugging!) do
175
+ Pry.start(binding, :commands => ShortCommands)
176
+ end
177
+
178
+ if ret == :next
179
+ PryDebug.stepped_file = file
180
+ end
181
+
182
+ # In case trace_func was changed
183
+ set_trace_func trace_proc
184
+ end
185
+ end
186
+
187
+ def synchronize(&block)
188
+ PryDebug.mutex.synchronize(&block)
189
+ end
190
+
191
+ def trace_proc
192
+ proc do |*args|
193
+ PryDebug.trace_func(*args)
194
+ end
195
+ end
196
+
197
+ def trace_func(event, file, line, method, binding, klass)
198
+ # Ignore events in this file
199
+ return if file && File.expand_path(file) == DefinitionFile
200
+
201
+ case event
202
+ when 'line'
203
+ if PryDebug.stepped_file == file
204
+ PryDebug.stepped_file = nil
205
+ start_pry binding, file, "stepped at #{file}:#{line} in #{Thread.current}"
206
+ elsif PryDebug.stepping
207
+ PryDebug.stepping = false
208
+ start_pry binding, file, "stepped at #{file}:#{line} in #{Thread.current}"
209
+ elsif bp = PryDebug.line_breakpoints.find { |b| b.is_at?(file, line, binding) }
210
+ start_pry binding, file, "reached #{bp} in #{Thread.current}"
211
+ end
212
+ when 'c-call', 'call'
213
+ return unless Module === klass
214
+
215
+ # Whether we are calling a class method or an instance method, klass
216
+ # is the same thing, making it harder to guess if it's a class or an
217
+ # instance method.
218
+ #
219
+ # In case of C method calls, self cannot be trusted. Both will be tried,
220
+ # unless we can find out there is only an instance method of that name
221
+ # using instance_methods.
222
+ #
223
+ # Otherwise, assume it's an instance method if self is_a? klass
224
+ #
225
+ # Notice that since self could be a BasicObject, it may not respond to
226
+ # is_a? (even when not breaking on BasicObject#thing, this code may be
227
+ # triggered).
228
+ class_method, try_both = if event == "call"
229
+ [!(klass === binding.eval("self")), false]
230
+ else
231
+ if (klass.instance_methods & klass.methods).include? method
232
+ [false, true]
233
+ elsif klass.instance_methods.include? method
234
+ [false, false]
235
+ elsif klass.methods.include? method
236
+ [true, false]
237
+ else # should never happen
238
+ [false, true]
239
+ end
240
+ end
241
+
242
+ bp = PryDebug.method_breakpoints.find do |b|
243
+ if try_both
244
+ b.is_at?(klass, method.to_s, true, binding) ||
245
+ b.is_at?(klass, method.to_s, false, binding)
246
+ else
247
+ b.is_at?(klass, method.to_s, class_method, binding)
248
+ end
249
+ end
250
+
251
+ if bp
252
+ start_pry binding, file, "reached #{bp} in #{Thread.current}"
253
+ end
254
+ when 'raise'
255
+ return unless $!
256
+
257
+ PryDebug.last_exception = $!
258
+ PryDebug.exception_binding = binding
259
+
260
+ if PryDebug.break_on_raise
261
+ msg = "exception raised in #{Thread.current}: #{$!.class}: #{$!.message} "
262
+ start_pry binding, file, msg
263
+ end
264
+ end
265
+ end
266
+
267
+ def add_line_breakpoint(file, line)
268
+ bp = LineBreakpoint.new(PryDebug.breakpoint_count += 1, file, line)
269
+ PryDebug.breakpoints << bp
270
+ bp
271
+ end
272
+
273
+ def add_method_breakpoint(klass, method, class_method)
274
+ bp = MethodBreakpoint.new(PryDebug.breakpoint_count += 1, klass, method,
275
+ class_method)
276
+ PryDebug.breakpoints << bp
277
+ bp
278
+ end
279
+ end
@@ -0,0 +1,141 @@
1
+ module PryDebug
2
+ Commands = Pry::CommandSet.new do
3
+ command "breakpoint", "adds a breakpoint" do |argument, *|
4
+ if argument =~ /(.+):(\d+)/
5
+ file, line = $1, $2.to_i
6
+
7
+ bp = PryDebug.add_line_breakpoint(file, line)
8
+ output.puts "added #{bp}"
9
+ elsif argument =~ /(.+)(#|\.|::)([^#\.:]+)/
10
+ klass, separator, meth = $1, $2, $3
11
+ class_method = (separator != "#")
12
+
13
+ bp = PryDebug.add_method_breakpoint(klass, meth, class_method)
14
+ output.puts "added #{bp}"
15
+ else
16
+ output.puts "usage: breakpoint FILE:LINE"
17
+ output.puts " or breakpoint CLASS(#|.|::)METHOD"
18
+ output.puts
19
+ output.puts "FILE can be foo.rb or /full/path/to/foo.rb."
20
+ output.puts "# as a separator means instance method. . and :: both mean"
21
+ output.puts "class method."
22
+ end
23
+ end
24
+
25
+ command "breakpoint-list", "prints breakpoint list" do
26
+ output.puts PryDebug.breakpoints
27
+ end
28
+
29
+ command "delete", "deletes a breakpoint" do |id, *|
30
+ PryDebug.breakpoints.reject! { |b| b.id == id.to_i }
31
+ output.puts "breakpoint #{id} deleted"
32
+ end
33
+
34
+ command "cond", "adds a condition to a breakpoint" do
35
+ id = string = nil
36
+ if arg_string =~ /^(\d+) (.+)$/
37
+ id, string = [$1.to_i, $2]
38
+ else
39
+ output.puts "usage: cond ID CODE"
40
+ next
41
+ end
42
+
43
+ if bp = PryDebug.breakpoints.find { |b| b.id == id }
44
+ bp.condition = string
45
+ output.puts "condition set to #{bp.condition}"
46
+ else
47
+ output.puts "error: could not find breakpoint #{id}"
48
+ end
49
+ end
50
+
51
+ command "uncond", "removes the condition of a breakpoint" do |id, *|
52
+ if id =~ /^\d+$/ && (bp = PryDebug.breakpoints.find { |b| b.id == id.to_i })
53
+ bp.condition = nil
54
+ output.puts "condition unset"
55
+ else
56
+ output.puts "error: could not find breakpoint #{id}"
57
+ end
58
+ end
59
+
60
+ command "file", "sets the file to start the debugger at" do |file, *|
61
+ PryDebug.file = file
62
+ output.puts "debugged file set to #{file}"
63
+ end
64
+
65
+ command "run", "starts the debugger" do |file, *|
66
+ if PryDebug.debugging
67
+ output.puts "error: debugger already started"
68
+ next
69
+ end
70
+
71
+ PryDebug.file = file if file
72
+
73
+ if !PryDebug.will_load || (PryDebug.file and File.exist? PryDebug.file)
74
+ throw :start_debugging!, :now!
75
+ else
76
+ if PryDebug.file
77
+ output.puts "error: file does not exist: #{PryDebug.file}"
78
+ else
79
+ output.puts "error: file is not set: #{PryDebug.file}"
80
+ end
81
+
82
+ output.puts "create it or set a new file using the 'file' command."
83
+ end
84
+ end
85
+
86
+ command "continue", "resumes execution" do
87
+ if !PryDebug.debugging
88
+ output.puts "error: debugger hasn't been started yet"
89
+ else
90
+ throw :resume_debugging!
91
+ end
92
+ end
93
+
94
+ command "next", "resumes execution until next line in the same file" do
95
+ if !PryDebug.debugging
96
+ output.puts "error: debugger hasn't been started yet"
97
+ else
98
+ throw :resume_debugging!, :next
99
+ end
100
+ end
101
+
102
+ command "step", "resumes execution until next line gets executed" do
103
+ PryDebug.stepping = true
104
+
105
+ if PryDebug.debugging
106
+ throw :resume_debugging!
107
+ else # just start debugging with stepping set to true
108
+ if !PryDebug.will_load || (PryDebug.file and File.exist? PryDebug.file)
109
+ throw :start_debugging!, :now!
110
+ else
111
+ output.puts "error: file does not exist: #{PryDebug.file}"
112
+ output.puts "create it or set a new file using the 'file' command."
113
+ end
114
+ end
115
+ end
116
+
117
+ command "break-on-raise", "toggles break on raise" do
118
+ PryDebug.break_on_raise = !PryDebug.break_on_raise
119
+
120
+ if PryDebug.break_on_raise
121
+ output.puts "break on raise enabled"
122
+ else
123
+ output.puts "break on raise disabled"
124
+ end
125
+ end
126
+ end
127
+
128
+ ShortCommands = Pry::CommandSet.new Commands do
129
+ alias_command "f", "file"
130
+ alias_command "b", "breakpoint"
131
+ alias_command "bp", "breakpoint"
132
+ alias_command "bl", "breakpoint-list"
133
+ alias_command "del", "delete"
134
+ alias_command "d", "delete"
135
+ alias_command "r", "run"
136
+ alias_command "c", "continue"
137
+ alias_command "n", "next"
138
+ alias_command "s", "step"
139
+ alias_command "bor", "break-on-raise"
140
+ end
141
+ end
@@ -0,0 +1,15 @@
1
+ module PryDebug
2
+ module ConditionalBreakpoint
3
+ attr_accessor :condition
4
+
5
+ def is_at?(binding)
6
+ condition ? binding.eval(condition) : true
7
+ rescue Exception # error in the code
8
+ false
9
+ end
10
+
11
+ def to_s
12
+ condition ? " (if #{condition})" : ""
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ module PryDebug
2
+ LineBreakpoint = Struct.new(:id, :file, :line) do
3
+ include ConditionalBreakpoint
4
+
5
+ def at_location?(other_file, other_line)
6
+ return false unless line == other_line
7
+
8
+ path = ""
9
+ split_file(other_file).any? do |part|
10
+ path = File.join(part, path).chomp('/')
11
+ path == file
12
+ end
13
+ end
14
+
15
+ def is_at?(other_file, other_line, binding)
16
+ at_location?(other_file, other_line) && super(binding)
17
+ end
18
+
19
+ def split_file(file)
20
+ ary = []
21
+
22
+ loop do
23
+ dirname, filename = File.split(file)
24
+
25
+ ary << filename
26
+
27
+ if dirname == '.'
28
+ break
29
+ elsif dirname == '/'
30
+ ary << '/'
31
+ break
32
+ else
33
+ file = dirname
34
+ end
35
+ end
36
+
37
+ ary
38
+ end
39
+
40
+ def to_s
41
+ "breakpoint #{id} at #{file}:#{line}#{super}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,89 @@
1
+ module PryDebug
2
+ MethodBreakpoint = Struct.new(:id, :klass, :name, :class_method) do
3
+ include ConditionalBreakpoint
4
+
5
+ alias class_method? class_method
6
+
7
+ def at_method?(other_class, other_name, other_class_method)
8
+ return false unless Module === other_class
9
+
10
+ if klass == other_class.to_s && name == other_name &&
11
+ class_method == other_class_method
12
+ true # exactly the same parameters
13
+ else # find out if the method we are referring to is the same as the one
14
+ # that was called.
15
+ if klass = actual_class
16
+ other_method = if other_class_method && has_class_method?(other_class, other_name)
17
+ (class << other_class; self; end).instance_method(other_name)
18
+ elsif !other_class_method && has_instance_method?(other_class, other_name)
19
+ other_class.instance_method(other_name)
20
+ end
21
+
22
+ if referred_method && other_method
23
+ singleton_class = (class << klass; self; end)
24
+ (other_class < klass || other_class == klass ||
25
+ (singleton_class == other_class || singleton_class < klass)) &&
26
+ ((referred_method == other_method) ||
27
+ (referred_method.name == other_method.name &&
28
+ referred_method.owner == other_method.owner))
29
+ else
30
+ false
31
+ end
32
+ else
33
+ false # can't get information about the class
34
+ end
35
+ end
36
+ end
37
+
38
+ def is_at?(other_class, other_name, other_class_method, binding)
39
+ at_method?(other_class, other_name, other_class_method) && super(binding)
40
+ end
41
+
42
+ def to_s
43
+ "breakpoint #{id} at #{method_name}#{super}"
44
+ end
45
+
46
+ def separator
47
+ class_method ? "." : "#"
48
+ end
49
+
50
+ def method_name
51
+ klass + separator + name
52
+ end
53
+
54
+ def referred_method
55
+ if klass = actual_class
56
+ @referred_method ||= if class_method? && has_class_method?(klass, name)
57
+ (class << klass; self; end).instance_method(name)
58
+ elsif !class_method && has_instance_method?(klass, name)
59
+ klass.instance_method(name)
60
+ end
61
+ end
62
+ end
63
+
64
+ def actual_class
65
+ # this method uses Module#=== to be BasicObject safe
66
+ @actual_class ||= klass.split('::').inject(Object) do |mod, const_name|
67
+ if (Module === mod) && mod.const_defined?(const_name)
68
+ mod.const_get const_name
69
+ else
70
+ break
71
+ end
72
+ end
73
+
74
+ (Module === @actual_class) ? @actual_class : nil
75
+ end
76
+
77
+ def has_instance_method?(klass, method)
78
+ (klass.private_instance_methods + klass.instance_methods).any? do |m|
79
+ m.to_s == method
80
+ end
81
+ end
82
+
83
+ def has_class_method?(klass, method)
84
+ (klass.private_methods + klass.methods).any? do |m|
85
+ m.to_s == method
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = "pry_debug"
4
+ s.version = "0.0.1"
5
+ s.authors = ["Mon ouïe"]
6
+ s.email = ["mon.ouie@gmail.com"]
7
+ s.homepage = "http://github.com/Mon-Ouie/pry_debug"
8
+
9
+ s.summary = "A pure-ruby debugger"
10
+ s.description = <<EOD
11
+ A pure-ruby debugger. No more puts "HERE!!!" or p :var => var, :other => other
12
+ until you find what caused the bug. Just add a breakpoint and see the value of
13
+ any variable.
14
+ EOD
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- test/*`.split("\n")
18
+ s.executables = %w[pry_debug]
19
+ s.require_paths = %w[lib]
20
+
21
+ s.add_dependency "pry", "~> 0.9.0"
22
+ s.add_development_dependency "riot"
23
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path("helpers.rb", File.dirname(__FILE__))
2
+
3
+ context "a conditional line breakpoint" do
4
+ setup do
5
+ bp = PryDebug::LineBreakpoint.new(1, "test.rb", 10)
6
+ bp.condition = "n == 0"
7
+ bp
8
+ end
9
+
10
+ asserts(:condition).equals "n == 0"
11
+ asserts(:to_s).equals "breakpoint 1 at test.rb:10 (if n == 0)"
12
+
13
+ context "when causing an exception" do
14
+ denies(:is_at?, "test.rb", 10, binding)
15
+ end
16
+
17
+ context "when condition isn't met" do
18
+ n = -1
19
+ denies(:is_at?, "test.rb", 10, binding)
20
+ end
21
+
22
+ context "when condition is met" do
23
+ n = 0
24
+ asserts(:is_at?, "test.rb", 10, binding)
25
+ denies(:is_at?, "test.rb", 11, binding)
26
+ denies(:is_at?, "foo.rb", 10, binding)
27
+ end
28
+ end
29
+
30
+ context "a conditional method breakpoint" do
31
+ setup do
32
+ bp = PryDebug::MethodBreakpoint.new(1, "String", "try_convert", true)
33
+ bp.condition = "n == 0"
34
+ bp
35
+ end
36
+
37
+ asserts(:condition).equals "n == 0"
38
+ asserts(:to_s).equals "breakpoint 1 at String.try_convert (if n == 0)"
39
+
40
+ context "when causing an exception" do
41
+ denies(:is_at?, String, "try_convert", true, binding)
42
+ end
43
+
44
+ context "when condition isn't met" do
45
+ n = -1
46
+ denies(:is_at?, String, "try_convert", true, binding)
47
+ end
48
+
49
+ context "when condition is met" do
50
+ n = 0
51
+ asserts(:is_at?, String, "try_convert", true, binding)
52
+
53
+ denies(:is_at?, Array, "try_convert", true, binding)
54
+ denies(:is_at?, String, "new", true, binding)
55
+ denies(:is_at?, String, "try_convert", false, binding)
56
+ end
57
+ end
58
+
59
+ run_tests if $0 == __FILE__
@@ -0,0 +1,19 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require 'riot'
4
+ require 'pry_debug'
5
+
6
+ Riot.reporter = Riot::PrettyDotMatrixReporter
7
+ Riot.alone!
8
+
9
+ Pry.config.should_load_rc = false
10
+ Pry.config.plugins.enabled = false
11
+ Pry.config.history.load = false
12
+ Pry.config.history.save = false
13
+
14
+ Pry.color = false
15
+ Pry.pager = false
16
+
17
+ def run_tests
18
+ exit Riot.run.success?
19
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path("helpers.rb", File.dirname(__FILE__))
2
+
3
+ context "a line breakpoint" do
4
+ setup { PryDebug::LineBreakpoint.new(4, "test/foo.rb", 16) }
5
+
6
+ asserts(:condition).nil
7
+ asserts(:file).equals "test/foo.rb"
8
+ asserts(:line).equals 16
9
+ asserts(:id).equals 4
10
+
11
+ asserts(:to_s).equals "breakpoint 4 at test/foo.rb:16"
12
+
13
+ asserts(:is_at?, "test/foo.rb", 16, binding)
14
+ asserts(:is_at?, "foo/test/foo.rb", 16, binding)
15
+ asserts(:is_at?, "/bar/test/foo.rb", 16, binding)
16
+
17
+ denies(:is_at?, "test/foo.rb", 17, binding)
18
+ denies(:is_at?, "foo.rb", 16, binding)
19
+ denies(:is_at?, "oo.rb", 16, binding)
20
+ denies(:is_at?, "est/foo.rb", 16, binding)
21
+ denies(:is_at?, "test/bar.rb", 16, binding)
22
+ end
23
+
24
+ run_tests if $0 == __FILE__
@@ -0,0 +1,90 @@
1
+ require File.expand_path("helpers.rb", File.dirname(__FILE__))
2
+
3
+ context "a method breakpoint" do
4
+ setup { PryDebug::MethodBreakpoint.new(2, "Time", "new", true) }
5
+
6
+ asserts(:condition).nil
7
+ asserts(:klass).equals "Time"
8
+ asserts(:name).equals "new"
9
+ asserts(:id).equals 2
10
+
11
+ asserts(:actual_class).equals Time
12
+ asserts(:referred_method).equals((class << Time; self; end).instance_method(:new))
13
+
14
+ asserts(:to_s).equals "breakpoint 2 at Time.new"
15
+
16
+ asserts(:is_at?, Time, "new", true, binding)
17
+ asserts(:is_at?, Class.new(Time), "new", true, binding)
18
+
19
+ asserts(:is_at?, (class << Time; self; end), "new", false, binding)
20
+
21
+ denies(:is_at?, Class, "new", false, binding)
22
+ denies(:is_at?, Time, "new", false, binding)
23
+ denies(:is_at?, String, "new", true, binding)
24
+ # "now" doesn't pass on rbx because it's an alias
25
+ denies(:is_at?, Time, "parse", true, binding)
26
+ denies(:is_at?, Class.new(Time) {def self.new;end}, "new", true, binding)
27
+ end
28
+
29
+ context "a method breakpoint on an instance method" do
30
+ setup { PryDebug::MethodBreakpoint.new(2, "String", "size", false) }
31
+
32
+ asserts(:condition).nil
33
+ asserts(:klass).equals "String"
34
+ asserts(:name).equals "size"
35
+ asserts(:id).equals 2
36
+
37
+ asserts(:actual_class).equals String
38
+ asserts(:referred_method).equals String.instance_method(:size)
39
+
40
+ asserts(:to_s).equals "breakpoint 2 at String#size"
41
+
42
+ asserts(:is_at?, String, "size", false, binding)
43
+ # in 1.8, aliased methods aren't equal
44
+ asserts(:is_at?, String, "length", false, binding) if RUBY_VERSION >= "1.9"
45
+ asserts(:is_at?, Class.new(String), "size", false, binding)
46
+
47
+ denies(:is_at?, String, "size", true, binding)
48
+ denies(:is_at?, Time, "size", false, binding)
49
+ denies(:is_at?, String, "foo", false, binding)
50
+ denies(:is_at?, Class.new(String) {def size;end}, "size", false, binding)
51
+ end
52
+
53
+ context "a method breakpoint with unknown method" do
54
+ setup { PryDebug::MethodBreakpoint.new(2, "Time", "foo", true) }
55
+
56
+ asserts(:actual_class).equals Time
57
+ asserts(:referred_method).nil
58
+
59
+ denies(:is_at?, Class.new(Time), "foo", true, binding)
60
+ denies(:is_at?, Time, "now", false, binding)
61
+ denies(:is_at?, String, "now", true, binding)
62
+ denies(:is_at?, Time, "new", true, binding)
63
+ denies(:is_at?, Class.new(Time) {def self.now;end}, "now", true, binding)
64
+ end
65
+
66
+ context "a method breakpoint with unknown class" do
67
+ setup { PryDebug::MethodBreakpoint.new(2, "Bar", "foo", true) }
68
+
69
+ asserts(:actual_class).nil
70
+ asserts(:referred_method).nil
71
+
72
+ denies(:is_at?, Time, "foo", true, binding)
73
+ denies(:is_at?, Class.new(Time), "foo", true, binding)
74
+ denies(:is_at?, Time, "now", false, binding)
75
+ denies(:is_at?, String, "now", true, binding)
76
+ denies(:is_at?, Time, "new", true, binding)
77
+ denies(:is_at?, Class.new {def self.now;end}, "foo", true, binding)
78
+ end
79
+
80
+ context "a method breakpoint with a non-class" do
81
+ setup { PryDebug::MethodBreakpoint.new(2, "File::SEPARATOR", "size", true) }
82
+
83
+ asserts(:actual_class).nil
84
+ asserts(:referred_method).nil
85
+
86
+ denies(:is_at?, File::SEPARATOR, "size", true, binding)
87
+ denies(:is_at?, String, "size", false, binding)
88
+ end
89
+
90
+ run_tests if $0 == __FILE__
@@ -0,0 +1,7 @@
1
+ require File.expand_path("helpers.rb", File.dirname(__FILE__))
2
+
3
+ Dir.glob("#{File.dirname(__FILE__)}/**/*_test.rb") do |file|
4
+ load file
5
+ end
6
+
7
+ run_tests
@@ -0,0 +1,177 @@
1
+ require File.expand_path("helpers.rb", File.dirname(__FILE__))
2
+
3
+ class InputTester
4
+ def initialize(actions)
5
+ @orig_actions = actions.dup
6
+ @actions = actions
7
+ end
8
+
9
+ def readline(*)
10
+ @actions.shift
11
+ end
12
+
13
+ def rewind
14
+ @actions = @orig_actions.dup
15
+ end
16
+ end
17
+
18
+ context "a PryDebug session" do
19
+ helper(:clean_up) { PryDebug.clean_up }
20
+
21
+ helper(:run_debugger) do |input|
22
+ input = InputTester.new(input)
23
+ output = StringIO.new
24
+
25
+ pry = Pry.new(:input => input, :output => output,
26
+ :commands => PryDebug::ShortCommands)
27
+ pry.repl(TOPLEVEL_BINDING)
28
+
29
+ output.string
30
+ end
31
+
32
+ setup { PryDebug }
33
+
34
+ asserts(:breakpoints).size 0
35
+ asserts(:line_breakpoints).size 0
36
+ asserts(:method_breakpoints).size 0
37
+
38
+ asserts(:file).nil
39
+
40
+ denies(:stepping)
41
+ asserts(:stepped_file).nil
42
+
43
+ denies(:break_on_raise)
44
+ denies(:debugging)
45
+
46
+ context "after adding two breakpoints" do
47
+ hookup do
48
+ @output = run_debugger(["b foo.rb:15", "b Foo#bar"]).split("\n")
49
+ @breakpoint_list = run_debugger ["bl"]
50
+ end
51
+
52
+ asserts(:breakpoints).size 2
53
+ asserts(:line_breakpoints).size 1
54
+ asserts(:method_breakpoints).size 1
55
+
56
+ asserts(:breakpoints).same_elements {
57
+ topic.line_breakpoints + topic.method_breakpoints
58
+ }
59
+
60
+ asserts("output") { @output[0] }.matches "added breakpoint 0 at foo.rb:15"
61
+ asserts("output") { @output[1] }.matches "added breakpoint 1 at Foo#bar"
62
+
63
+ asserts("breakpoint list") { @breakpoint_list }.matches "breakpoint 0 at foo.rb:15"
64
+ asserts("breakpoint list") { @breakpoint_list }.matches "breakpoint 1 at Foo#bar"
65
+
66
+ context "line breakpoint" do
67
+ setup do
68
+ topic.line_breakpoints.first
69
+ end
70
+
71
+ asserts_topic.kind_of PryDebug::LineBreakpoint
72
+
73
+ asserts(:condition).nil
74
+ asserts(:file).equals "foo.rb"
75
+ asserts(:line).equals 15
76
+ asserts(:id).equals 0
77
+ end
78
+
79
+ context "method breakpoint" do
80
+ setup do
81
+ topic.method_breakpoints.first
82
+ end
83
+
84
+ asserts_topic.kind_of PryDebug::MethodBreakpoint
85
+
86
+ asserts(:condition).nil
87
+ asserts(:klass).equals "Foo"
88
+ asserts(:name).equals "bar"
89
+ asserts(:id).equals 1
90
+ end
91
+
92
+ context "method breakpoint after adding a condition" do
93
+ setup do
94
+ @output = run_debugger [%{cond 1 @var == "foo"}]
95
+ @breakpoint_list = run_debugger ["bl"]
96
+
97
+ topic.method_breakpoints.first
98
+ end
99
+
100
+ asserts(:condition).equals %{@var == "foo"}
101
+ asserts("output") { @output }.matches /condition set to @var == "foo"/
102
+ asserts("breakpoint list") { @breakpoint_list }.matches <<desc.chomp
103
+ breakpoint 1 at Foo#bar (if @var == "foo")
104
+ desc
105
+
106
+ context "and disabling it" do
107
+ hookup do
108
+ @output = run_debugger [%{uncond 1}]
109
+ @breakpoint_list = run_debugger ["bl"]
110
+ end
111
+
112
+ asserts(:condition).nil
113
+
114
+ asserts("output") { @output }.matches /condition unset/
115
+ denies("breakpoint list") { @breakpoint_list }.matches /\(if .+\)/
116
+ end
117
+ end
118
+
119
+ context "and deleting one" do
120
+ hookup do
121
+ @output = run_debugger ["del 0"]
122
+ @breakpoint_list = run_debugger ["bl"]
123
+ end
124
+
125
+ asserts(:breakpoints).size 1
126
+ asserts(:line_breakpoints).size 0
127
+ asserts(:method_breakpoints).size 1
128
+
129
+ asserts("output") { @output }.matches /breakpoint 0 deleted/
130
+ asserts("breakpoint list") { @breakpoint_list }.matches /breakpoint 1/
131
+ denies("breakpoint list") { @breakpoint_list }.matches /breakpoint 0/
132
+ end
133
+ end
134
+
135
+ context "after disabling condition on an unknown breakpoint" do
136
+ hookup do
137
+ @output = run_debugger ["uncond 0"]
138
+ end
139
+
140
+ asserts("output") { @output }.matches "error: could not find breakpoint 0"
141
+ end
142
+
143
+ context "after enableing condition on an unknown breakpoint" do
144
+ hookup do
145
+ @output = run_debugger ["cond 0 foo"]
146
+ end
147
+
148
+ asserts("output") { @output }.matches "error: could not find breakpoint 0"
149
+ end
150
+
151
+ context "after changing file" do
152
+ hookup do
153
+ @output = run_debugger ["f #{__FILE__}"]
154
+ end
155
+
156
+ asserts("output") { @output }.matches "debugged file set to #{__FILE__}"
157
+ asserts(:file).equals __FILE__
158
+ end
159
+
160
+ context "after enabling break-on-raise" do
161
+ hookup { @output = run_debugger ["bor"] }
162
+
163
+ asserts("output") { @output }.matches "break on raise enabled"
164
+ asserts(:break_on_raise)
165
+
166
+ context "and disabling it" do
167
+ hookup { @output = run_debugger ["bor"] }
168
+
169
+ asserts("output") { @output }.matches "break on raise disabled"
170
+ denies(:break_on_raise)
171
+ end
172
+ end
173
+
174
+ teardown { clean_up }
175
+ end
176
+
177
+ run_tests if $0 == __FILE__
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pry_debug
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - "Mon ou\xC3\xAFe"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-16 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: pry
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.0
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: riot
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :development
36
+ version_requirements: *id002
37
+ description: |
38
+ A pure-ruby debugger. No more puts "HERE!!!" or p :var => var, :other => other
39
+ until you find what caused the bug. Just add a breakpoint and see the value of
40
+ any variable.
41
+
42
+ email:
43
+ - mon.ouie@gmail.com
44
+ executables:
45
+ - pry_debug
46
+ extensions: []
47
+
48
+ extra_rdoc_files: []
49
+
50
+ files:
51
+ - .gemtest
52
+ - .gitignore
53
+ - README.md
54
+ - Rakefile
55
+ - bin/pry_debug
56
+ - lib/pry_debug.rb
57
+ - lib/pry_debug/commands.rb
58
+ - lib/pry_debug/conditional_breakpoint.rb
59
+ - lib/pry_debug/line_breakpoint.rb
60
+ - lib/pry_debug/method_breakpoint.rb
61
+ - pry_debug.gemspec
62
+ - test/conditional_breakpoint_test.rb
63
+ - test/helpers.rb
64
+ - test/line_breakpoint_test.rb
65
+ - test/method_breakpoint_test.rb
66
+ - test/run_all.rb
67
+ - test/session_test.rb
68
+ homepage: http://github.com/Mon-Ouie/pry_debug
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.8.5
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: A pure-ruby debugger
95
+ test_files:
96
+ - test/conditional_breakpoint_test.rb
97
+ - test/helpers.rb
98
+ - test/line_breakpoint_test.rb
99
+ - test/method_breakpoint_test.rb
100
+ - test/run_all.rb
101
+ - test/session_test.rb