pry_debug 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +2 -0
- data/README.md +253 -0
- data/Rakefile +16 -0
- data/bin/pry_debug +9 -0
- data/lib/pry_debug.rb +279 -0
- data/lib/pry_debug/commands.rb +141 -0
- data/lib/pry_debug/conditional_breakpoint.rb +15 -0
- data/lib/pry_debug/line_breakpoint.rb +44 -0
- data/lib/pry_debug/method_breakpoint.rb +89 -0
- data/pry_debug.gemspec +23 -0
- data/test/conditional_breakpoint_test.rb +59 -0
- data/test/helpers.rb +19 -0
- data/test/line_breakpoint_test.rb +24 -0
- data/test/method_breakpoint_test.rb +90 -0
- data/test/run_all.rb +7 -0
- data/test/session_test.rb +177 -0
- metadata +101 -0
data/.gemtest
ADDED
File without changes
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/pry_debug
ADDED
data/lib/pry_debug.rb
ADDED
@@ -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
|
data/pry_debug.gemspec
ADDED
@@ -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__
|
data/test/helpers.rb
ADDED
@@ -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__
|
data/test/run_all.rb
ADDED
@@ -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
|