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.
- 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
|