pry_debug 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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