unroller 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme +15 -2
- data/lib/unroller.rb +710 -2
- metadata +10 -2
- data/lib/unroller/unroller.rb +0 -589
data/Readme
CHANGED
@@ -55,6 +55,13 @@ Screenshot[link:include/screenshot1.png]
|
|
55
55
|
|
56
56
|
This is much more efficient and reliable than manually tracing through the execution yourself! (Trying to guess which file the <tt>save!</tt> method would be defined in, searching for the file in the depths of <tt>/usr/lib/ruby/gems</tt>, scrolling down to the right line number, and repeating a zillion times...)
|
57
57
|
|
58
|
+
===Inspecting variables
|
59
|
+
|
60
|
+
If you'd like to see the values of all arguments/parameters that were passed into the method for each method call, just pass in the :show_args => true option.
|
61
|
+
|
62
|
+
If you'd like to see the values of all local variables as they exist right before executing the current line, just pass in the :show_locals => true option.
|
63
|
+
|
64
|
+
Note: You might like to know that the state of those variables is _after_ executing that line, too, but currently that's not possible.
|
58
65
|
|
59
66
|
===Reducing verbosity
|
60
67
|
|
@@ -132,7 +139,9 @@ If you want to see all code that gets executed within a certain action in your c
|
|
132
139
|
|
133
140
|
==Installation
|
134
141
|
|
135
|
-
sudo gem install unroller
|
142
|
+
sudo gem install unroller --include-dependencies
|
143
|
+
|
144
|
+
The dependencies include: facets, qualitysmith_extensions, colored, and extensions
|
136
145
|
|
137
146
|
==Status
|
138
147
|
|
@@ -142,6 +151,11 @@ Occasionally it has caused some segmentation faults and other weirdness for me w
|
|
142
151
|
|
143
152
|
(Don't even *think* about leaving this in your production code!)
|
144
153
|
|
154
|
+
The code isn't really clean and there aren't (m)any automated tests yet because I've kind of thrown this together in a big hurry, but I hope to solve both of those problems eventually.
|
155
|
+
You might consider this a "prototype" right now -- it works, but it wouldn't hurt at some point to throw it away and re-implement it a bit better.
|
156
|
+
|
157
|
+
Also keep in mind that the API as subject to change as I try to think of better design ideas and as I get feedback from people telling me what they want to see.
|
158
|
+
|
145
159
|
==About the name
|
146
160
|
|
147
161
|
I called it "Ruby Unroller" because it visually "unrolls" a stack trace for you (like a scroll?). (And it sounds cooler than "Ruby Script Execution Tracer".)
|
@@ -180,7 +194,6 @@ It's also sort of like a call stack (caller(0)). But unlike the callstack you us
|
|
180
194
|
You're welcome to submit comments and/or patches.
|
181
195
|
|
182
196
|
* Make a GUI interface that lets you quickly collapse/nodes nodes of the tree.
|
183
|
-
* It would be nice if we could see what arguments are being passed to each method. This would be technically difficult, but I wonder if it would be possible to just wait for a 'call' event and when you see one, wrap/alias_method_chain the given classname/id with a wrapper method that has variable *args and does whatever you want to do with the args before actually doing the *real* method call....
|
184
197
|
* :include_classes option in addition to :exclude_classes?
|
185
198
|
* Have some "presets" for what you might want to exclude if tracing an ActiveRecord request for example. In that case, you probably don't want to see the internals of any support code, like any methods from ActiveSupport.
|
186
199
|
* :preset => :Rails : Exclude ActiveSupport, etc.
|
data/lib/unroller.rb
CHANGED
@@ -1,3 +1,711 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
3
|
-
|
2
|
+
gem 'facets'
|
3
|
+
require 'facets/core/module/namespace'
|
4
|
+
require 'facets/core/kernel/with'
|
5
|
+
require 'facets/core/kernel/set_with'
|
6
|
+
require 'facets/core/string/bracket'
|
7
|
+
gem 'qualitysmith_extensions'
|
8
|
+
require 'qualitysmith_extensions/object/send_if_not_nil'
|
9
|
+
require 'qualitysmith_extensions/kernel/trap_chain'
|
10
|
+
require 'qualitysmith_extensions/kernel/capture_output'
|
11
|
+
require 'qualitysmith_extensions/string/with_knowledge_of_color'
|
12
|
+
require 'qualitysmith_extensions/exception/inspect_with_backtrace'
|
13
|
+
require 'qualitysmith_extensions/symbol/match'
|
14
|
+
gem 'colored'
|
15
|
+
require 'colored'
|
16
|
+
gem 'extensions'
|
17
|
+
require 'extensions/symbol' # to_proc
|
18
|
+
|
19
|
+
# To disable color, uncomment this:
|
20
|
+
#class String
|
21
|
+
# def colorize(string, options = {})
|
22
|
+
# string
|
23
|
+
# end
|
24
|
+
#end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
#
|
30
|
+
class Unroller
|
31
|
+
class Variables
|
32
|
+
def initialize(which, binding)
|
33
|
+
@variables = eval("#{which}_variables", binding).map { |variable|
|
34
|
+
value = eval(variable, binding)
|
35
|
+
[variable, value]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
def to_s
|
39
|
+
#@variables.inspect
|
40
|
+
@variables.map do |variable|
|
41
|
+
name, value = *variable
|
42
|
+
"#{name} = #{value.inspect}"
|
43
|
+
end.join('; ').bracket(' (', ')')
|
44
|
+
end
|
45
|
+
def any?
|
46
|
+
!@variables.empty?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@@instance = nil
|
50
|
+
|
51
|
+
def self.trace(options = {}, &block)
|
52
|
+
@@instance = Unroller.new(options)
|
53
|
+
@@instance.trace &block
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_accessor :depth
|
57
|
+
attr_reader :tracing
|
58
|
+
|
59
|
+
def initialize(options = {})
|
60
|
+
# Defaults
|
61
|
+
@condition = Proc.new { true } # Only trace if this condition is true. Useful if the place where you put your trace {} statement gets called a lot and you only want it to actually trace for some of those calls.
|
62
|
+
@initial_depth = 1 # ("Call stack") depth to start at. Actually, you'll probably want this set considerably lower than the current call stack depth, so that the indentation isn't way off the screen.
|
63
|
+
@max_lines = nil # Stop tracing (permanently) after we have produced @max_lines lines of output. If you don't know where to place the trace(false) and you just want it to stop on its own after so many lines, you could use this...
|
64
|
+
@max_depth = nil # Don't trace anything when the depth is greater than this threshold. (This is *relative* to the starting depth, so whatever level you start at is considered depth "1".)
|
65
|
+
@exclude_classes = []
|
66
|
+
@show_args = true
|
67
|
+
@show_locals = false
|
68
|
+
@strip_comments = true # :todo:
|
69
|
+
@use_full_path = false # :todo:
|
70
|
+
@screen_width = 150
|
71
|
+
@column_widths = [70]
|
72
|
+
@indent_step = ' ' + '|'.magenta + ' '
|
73
|
+
@column_separator = ' ' + '|'.yellow.bold + ' '
|
74
|
+
instance_variables.each do |v|
|
75
|
+
self.class.class_eval do
|
76
|
+
attr_accessor v.gsub!(/^@/, '')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# "Presets"
|
81
|
+
# Experimental -- subject to change a lot before it's finalized
|
82
|
+
[:rails].each do |preset|
|
83
|
+
if options.has_key?(preset)
|
84
|
+
options.delete(preset)
|
85
|
+
case preset
|
86
|
+
when :rails
|
87
|
+
options[:exclude_classes] ||= []
|
88
|
+
options[:exclude_classes].concat [
|
89
|
+
/Benchmark/,
|
90
|
+
/Gem/,
|
91
|
+
/Dependencies/,
|
92
|
+
/Logger/,
|
93
|
+
/MonitorMixin/,
|
94
|
+
/Set/,
|
95
|
+
/HashWithIndifferentAccess/,
|
96
|
+
/ERB/,
|
97
|
+
/ActiveRecord/,
|
98
|
+
/SQLite3/,
|
99
|
+
/Class/,
|
100
|
+
/ActiveSupport/,
|
101
|
+
/ActiveSupport::Deprecation/,
|
102
|
+
/Pathname/
|
103
|
+
]
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Options
|
110
|
+
options[:condition] = options.delete(:if) if options.has_key?(:if)
|
111
|
+
options[:initial_depth] = options.delete(:depth) if options.has_key?(:depth)
|
112
|
+
options[:initial_depth] = caller(0).size if options[:initial_depth] == :use_call_stack_depth
|
113
|
+
if options.has_key?(:exclude_classes)
|
114
|
+
options[:exclude_classes] = [options[:exclude_classes]] if options[:exclude_classes].is_a?(Regexp)
|
115
|
+
raise ArgumentError if !options[:exclude_classes].is_a?(Array)
|
116
|
+
end
|
117
|
+
set_with(options)
|
118
|
+
|
119
|
+
# Private
|
120
|
+
@call_stack = [] # Used to keep track of what method we're currently in so that when we hit a 'return' event we can display something useful.
|
121
|
+
# This is useful for two reasons:
|
122
|
+
# 1. Sometimes (and I don't know why), the code that gets shown for a 'return' event doesn't even look like it has anything to do with a return...
|
123
|
+
# 2. If we've been stuck in this method for a long time and we're really deep, the user has probably forgotten by now which method we are returning from
|
124
|
+
# (the filename may give some clue, but not enough), so this is likely to be a welcome reminder a lot of the time.
|
125
|
+
@depth = @initial_depth
|
126
|
+
@output_line = ''
|
127
|
+
@column_counter = 0
|
128
|
+
@tracing = false
|
129
|
+
@files = {}
|
130
|
+
@lines_output = 0
|
131
|
+
@excluding_calls_made_within_unintersting_call = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.exclude(*args, &block)
|
135
|
+
@@instance.exclude(*args, &block)
|
136
|
+
end
|
137
|
+
def exclude(&block)
|
138
|
+
old_tracing = @tracing
|
139
|
+
(trace_off; puts 'Suspending tracing')
|
140
|
+
yield
|
141
|
+
(trace; puts 'Resuming tracing') if old_tracing
|
142
|
+
end
|
143
|
+
|
144
|
+
def trace(&block)
|
145
|
+
(puts 'Already tracing!'; return) if @tracing # This does not work. :fixme:
|
146
|
+
@tracing = true
|
147
|
+
|
148
|
+
|
149
|
+
if @condition.call
|
150
|
+
|
151
|
+
trap_chain("INT") { set_trace_func(nil) }
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
# (This is the meat of the library right here, so let's set it off with at least 5 blank lines.)
|
162
|
+
set_trace_func( proc do |event, file, line, id, binding, klass|
|
163
|
+
begin # begin/rescue block
|
164
|
+
@event, @file, @line, @id, @binding, @klass =
|
165
|
+
event, file, line, id, binding, klass
|
166
|
+
|
167
|
+
# Sometimes klass is false and id is nil. Not sure why, but that's the way it is.
|
168
|
+
#printf "- (event=%8s) (klass=%10s) (id=%10s) (%s:%-2d)\n", event, klass, id, file, line #if klass.to_s == 'false'
|
169
|
+
#puts 'false!!!!!!!'+klass.inspect if klass.to_s == 'false'
|
170
|
+
|
171
|
+
return if ['c-call', 'c-return'].include? event
|
172
|
+
#(puts 'exclude') if @excluding_calls_made_within_unintersting_call unless event == 'return' # Until we hit a return and can break out of this uninteresting call, we don't want to do *anything*.
|
173
|
+
#return if uninteresting_class?(klass.to_s) unless (klass == false)
|
174
|
+
|
175
|
+
if too_far?
|
176
|
+
puts "We've read #{@max_lines} (@max_lines) lines now. Turning off tracing..."
|
177
|
+
trace_off
|
178
|
+
return
|
179
|
+
end
|
180
|
+
|
181
|
+
case event
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
when 'call'
|
186
|
+
unless skip_line?
|
187
|
+
# :todo: use # instead of :: if klass.constantize.instance_methods.include?(id)
|
188
|
+
column sprintf(' ' + '+'.cyan + ' calling'.cyan + ' ' + '%s'.underline.cyan, fully_qualified_method), @column_widths[0]
|
189
|
+
newline
|
190
|
+
|
191
|
+
column code_for(file, line, '/'.magenta, :green), @column_widths[0]
|
192
|
+
file_column file, line
|
193
|
+
newline
|
194
|
+
|
195
|
+
# The locals at this point will be simply be the arguments that were passed in to this method.
|
196
|
+
do_show_locals if show_args
|
197
|
+
|
198
|
+
@lines_output += 1
|
199
|
+
|
200
|
+
@call_stack.push fully_qualified_method
|
201
|
+
end
|
202
|
+
|
203
|
+
@depth += 1
|
204
|
+
|
205
|
+
|
206
|
+
when 'class'
|
207
|
+
when 'end'
|
208
|
+
when 'line'
|
209
|
+
unless skip_line?
|
210
|
+
# Show the state of the locals *before* executing the current line. (I might add the option to show it after instead/as well, but I don't think that would be easy...)
|
211
|
+
do_show_locals if show_locals
|
212
|
+
|
213
|
+
column code_for(file, line, ' ', :bold), @column_widths[0]
|
214
|
+
file_column file, line
|
215
|
+
newline
|
216
|
+
|
217
|
+
@lines_output += 1
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
when 'return'
|
223
|
+
puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth < 0
|
224
|
+
@depth -= 1 unless @depth == 0
|
225
|
+
returning_from = @call_stack.pop
|
226
|
+
|
227
|
+
unless skip_line?
|
228
|
+
code = code_for(file, line, '\\'.magenta, :green, suffix = " (returning from #{returning_from})".green)
|
229
|
+
code = code_for(file, line, '\\'.magenta + " (returning from #{returning_from})".green, :green) unless code =~ /return|end/
|
230
|
+
# I've seen some really weird statements listed as "return" statements.
|
231
|
+
# I'm not really sure *why* it thinks these are returns, but let's at least identify those lines for the user. Examples:
|
232
|
+
# * must_be_open!
|
233
|
+
# * @db = db
|
234
|
+
# * stmt = @statement_factory.new( self, sql )
|
235
|
+
# I think some of the time this happens it might be because people pass the wrong line number to eval (__LINE__ instead of __LINE__ + 1, for example), so the line number is just not accurate.
|
236
|
+
# But I don't know if that explains all such cases or not...
|
237
|
+
column code, @column_widths[0]
|
238
|
+
file_column file, line
|
239
|
+
newline
|
240
|
+
|
241
|
+
@lines_output += 1
|
242
|
+
end
|
243
|
+
|
244
|
+
# Did we just get out of an uninteresting call?? Are we back in interesting land again??
|
245
|
+
if @excluding_calls_made_within_unintersting_call and @excluding_calls_made_within_unintersting_call == @depth
|
246
|
+
if @excluding_calls_made_within_unintersting_call == @depth
|
247
|
+
puts "Yay, we're back in interesting land!"
|
248
|
+
@excluding_calls_made_within_unintersting_call = nil
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
|
254
|
+
when 'raise'
|
255
|
+
# We probably always want to see these (?)... Never skip displaying them, even if we are "too deep".
|
256
|
+
column "Raising an error (#{$!}) from #{klass}".red.bold, @column_widths[0]
|
257
|
+
newline
|
258
|
+
|
259
|
+
column code_for(file, line, ' ').red, @column_widths[0]
|
260
|
+
file_column file, line
|
261
|
+
newline
|
262
|
+
|
263
|
+
else
|
264
|
+
column sprintf("- (%8s) %10s %10s (%s:%-2d)", event, klass, id, file, line)
|
265
|
+
newline
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
rescue Exception => exception
|
270
|
+
puts exception.inspect
|
271
|
+
raise
|
272
|
+
end # begin/rescue block
|
273
|
+
end) # set_trace_func
|
274
|
+
|
275
|
+
|
276
|
+
|
277
|
+
|
278
|
+
|
279
|
+
|
280
|
+
|
281
|
+
|
282
|
+
end # if @condition.call
|
283
|
+
|
284
|
+
if block_given?
|
285
|
+
yield
|
286
|
+
end
|
287
|
+
|
288
|
+
ensure
|
289
|
+
trace_off if block_given?
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.trace_off
|
293
|
+
@@instance.trace_off
|
294
|
+
end
|
295
|
+
def trace_off
|
296
|
+
@tracing = false
|
297
|
+
set_trace_func(nil)
|
298
|
+
end
|
299
|
+
|
300
|
+
protected
|
301
|
+
#----------------------------------------------------------
|
302
|
+
# Helpers
|
303
|
+
|
304
|
+
def fully_qualified_method
|
305
|
+
"#{@klass}::#{@id}"
|
306
|
+
end
|
307
|
+
def do_show_locals
|
308
|
+
variables = Variables.new(:local, @binding)
|
309
|
+
if variables.any?
|
310
|
+
column variables.to_s
|
311
|
+
newline
|
312
|
+
end
|
313
|
+
end
|
314
|
+
def skip_line?
|
315
|
+
@excluding_calls_made_within_unintersting_call or calling_method_in_an_uninteresting_class?(@klass.to_s) or too_deep?
|
316
|
+
end
|
317
|
+
def too_deep?
|
318
|
+
# The + 1 is because if they're still at the initial depth (@depth - @initial_depth == 0), we want it treated as "depth 1" (1-based, for humans).
|
319
|
+
@max_depth and (@depth - @initial_depth + 1 > @max_depth)
|
320
|
+
end
|
321
|
+
def too_far?
|
322
|
+
@max_lines and (@lines_output > @max_lines)
|
323
|
+
end
|
324
|
+
def calling_method_in_an_uninteresting_class?(class_name)
|
325
|
+
( @exclude_classes + [/#{self.class.name}/] ).any? do |item|
|
326
|
+
item = item.dup
|
327
|
+
if item.is_a?(Array)
|
328
|
+
# Expect item to be in format [/class_name/, :recursive, any other flags...]
|
329
|
+
regexp = item.shift
|
330
|
+
recursive = item.include?(:recursive)
|
331
|
+
else
|
332
|
+
regexp = item
|
333
|
+
recursive = false
|
334
|
+
end
|
335
|
+
|
336
|
+
returning(class_name =~ regexp) do |uninteresting|
|
337
|
+
if uninteresting && recursive && @excluding_calls_made_within_unintersting_call.nil?
|
338
|
+
puts "Turning tracing off until we get back to depth #{@depth} again because we're calling uninteresting #{class_name}:#{@id}"
|
339
|
+
@excluding_calls_made_within_unintersting_call = @depth
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
#----------------------------------------------------------
|
346
|
+
# Formatting stuff.
|
347
|
+
def indent
|
348
|
+
@indent_step*@depth
|
349
|
+
end
|
350
|
+
|
351
|
+
def remaining_width
|
352
|
+
@screen_width - @output_line.length_without_color
|
353
|
+
end
|
354
|
+
|
355
|
+
# +width+ is the minimum width for this column. It's also a maximum if +column_overflow+ is :chop_left or :chop_right
|
356
|
+
# +color+ is only needed if you plan on doing some chopping, because if you apply the color *before* the chopping, the color code might get chopped off.
|
357
|
+
def column(string, width = nil, column_overflow = :allow, color = nil)
|
358
|
+
raise ArgumentError if ![:allow, :chop_left, :chop_right].include?(column_overflow)
|
359
|
+
raise ArgumentError if width and !(width == :remainder or width.is_a?(Fixnum))
|
360
|
+
|
361
|
+
if @column_counter == 0
|
362
|
+
@output_line << indent
|
363
|
+
width -= indent.length_without_color if width
|
364
|
+
else
|
365
|
+
@output_line << @column_separator # So the columns won't be squashed up against each other
|
366
|
+
end
|
367
|
+
|
368
|
+
if width == :remainder
|
369
|
+
width = remaining_width()
|
370
|
+
end
|
371
|
+
|
372
|
+
if width
|
373
|
+
if column_overflow =~ /chop_/
|
374
|
+
#puts "width = #{width}"
|
375
|
+
string = string.code_unroller.make_it_fit(width, column_overflow)
|
376
|
+
end
|
377
|
+
string = string.code_unroller.make_it_fit(remaining_width) # Handles maximum width
|
378
|
+
|
379
|
+
string = string.ljust_without_color(width) # Handles minimum width
|
380
|
+
end
|
381
|
+
|
382
|
+
@output_line << string.send_if_not_nil(color)
|
383
|
+
|
384
|
+
@column_counter += 1
|
385
|
+
end # def column
|
386
|
+
|
387
|
+
def newline
|
388
|
+
unless @output_line.strip.length_without_color == 0 or @output_line == @last_line_printed
|
389
|
+
Kernel.print @output_line
|
390
|
+
Kernel.puts
|
391
|
+
@last_line_printed = @output_line
|
392
|
+
end
|
393
|
+
|
394
|
+
@output_line = ''
|
395
|
+
@column_counter = 0
|
396
|
+
end
|
397
|
+
|
398
|
+
def file_column(file, line)
|
399
|
+
column "#{file}:#{line}", :remainder, :chop_left, :magenta
|
400
|
+
end
|
401
|
+
|
402
|
+
#----------------------------------------------------------
|
403
|
+
|
404
|
+
def code_for(file, line, prefix, color = nil, suffix = '')
|
405
|
+
if file == '(eval)'
|
406
|
+
# Can't really read the source from the 'eval' file, unfortunately!
|
407
|
+
return ' ' + prefix + ' ' + file.send_if_not_nil(color) + ''
|
408
|
+
end
|
409
|
+
|
410
|
+
line -= 1 # Adjust for the fact that line arg is 0-based, readlines line is 1-based
|
411
|
+
begin
|
412
|
+
@files[file] ||= File.readlines(file)
|
413
|
+
line = [@files[file].size - 1, line].min
|
414
|
+
' ' + prefix + ' ' + @files[file][line].strip.send_if_not_nil(color) + suffix
|
415
|
+
rescue Errno::ENOENT
|
416
|
+
$stderr.puts( message = "Error Could not open #{file}" )
|
417
|
+
message
|
418
|
+
rescue Exception => exception
|
419
|
+
puts "Error while getting code for #{file}:#{line}:"
|
420
|
+
puts exception.inspect
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
class String
|
427
|
+
namespace :code_unroller do
|
428
|
+
|
429
|
+
def make_it_fit(max_width, overflow = :chop_right)
|
430
|
+
with(string = self) do
|
431
|
+
if string.length_without_color > max_width # Wider than desired column width; Needs to be chopped.
|
432
|
+
unless max_width < 4 # Is there even enough room for it if it *is* chopped?
|
433
|
+
if overflow == :chop_left
|
434
|
+
#puts "making string (#{string.length_without_color}) fit within #{max_width}"
|
435
|
+
#puts "chopping '#{string}' at -(#{max_width} - 3) .. -1!"
|
436
|
+
chopped_part = string[-(max_width - 3) .. -1]
|
437
|
+
string.replace '...' + chopped_part
|
438
|
+
elsif overflow == :chop_right
|
439
|
+
chopped_part = string[0 .. (max_width - 3)]
|
440
|
+
string.replace chopped_part + '...'
|
441
|
+
end
|
442
|
+
else
|
443
|
+
string = ''
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
|
454
|
+
|
455
|
+
if $0 == __FILE__
|
456
|
+
puts '-----------------------------------------------------------'
|
457
|
+
puts 'Simple test'
|
458
|
+
def jump!(how_high = 3)
|
459
|
+
how_high.times do
|
460
|
+
'jump!'
|
461
|
+
end
|
462
|
+
end
|
463
|
+
Unroller::trace
|
464
|
+
jump!(2)
|
465
|
+
Unroller::trace_off
|
466
|
+
|
467
|
+
puts '-----------------------------------------------------------'
|
468
|
+
puts "Testing that this doesn't trace anything (condition == false proc)"
|
469
|
+
$trace = false
|
470
|
+
Unroller::trace(:condition => proc { $trace }) do
|
471
|
+
jump!
|
472
|
+
end
|
473
|
+
|
474
|
+
puts '-----------------------------------------------------------'
|
475
|
+
puts "Testing that this doesn't trace the inner method (method2), but does trace method1 and method3 (exclude)"
|
476
|
+
def method1; end
|
477
|
+
def method2
|
478
|
+
'stuff!'
|
479
|
+
end
|
480
|
+
def method3; end
|
481
|
+
Unroller::trace do
|
482
|
+
method1
|
483
|
+
Unroller::exclude do
|
484
|
+
method2
|
485
|
+
end
|
486
|
+
method3
|
487
|
+
end
|
488
|
+
|
489
|
+
puts '-----------------------------------------------------------'
|
490
|
+
puts 'Test with block; very deep (test for over-wide columns)'
|
491
|
+
('a'..last='y').each do |method_name|
|
492
|
+
next_method_name = method_name.next unless method_name == last
|
493
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
494
|
+
def #{method_name}
|
495
|
+
#{next_method_name}
|
496
|
+
end
|
497
|
+
End
|
498
|
+
end
|
499
|
+
Unroller::trace(:depth => 5) do
|
500
|
+
a
|
501
|
+
end
|
502
|
+
|
503
|
+
puts '-----------------------------------------------------------'
|
504
|
+
puts 'Test watching a call stack unwind (only)'
|
505
|
+
('a'..last='y').each do |method_name|
|
506
|
+
next_method_name = method_name.next unless method_name == last
|
507
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
508
|
+
def #{method_name}
|
509
|
+
#{next_method_name}
|
510
|
+
#{'Unroller::trace(:depth => caller(0).size)' if method_name == last }
|
511
|
+
end
|
512
|
+
End
|
513
|
+
end
|
514
|
+
a
|
515
|
+
Unroller::trace_off
|
516
|
+
|
517
|
+
|
518
|
+
puts '-----------------------------------------------------------'
|
519
|
+
puts 'Testing :depth => :use_call_stack_depth'
|
520
|
+
def go_to_depth_and_call_1(depth, &block)
|
521
|
+
#puts caller(0).size
|
522
|
+
if caller(0).size == depth
|
523
|
+
puts 'calling a'
|
524
|
+
block.call
|
525
|
+
else
|
526
|
+
go_to_depth_and_call_2(depth, &block)
|
527
|
+
end
|
528
|
+
#puts caller(0).size
|
529
|
+
end
|
530
|
+
def go_to_depth_and_call_2(depth, &block)
|
531
|
+
#puts caller(0).size
|
532
|
+
if caller(0).size == depth
|
533
|
+
puts 'calling a'
|
534
|
+
block.call
|
535
|
+
else
|
536
|
+
go_to_depth_and_call_1(depth, &block)
|
537
|
+
end
|
538
|
+
#puts caller(0).size
|
539
|
+
end
|
540
|
+
('a'..last='c').each do |method_name|
|
541
|
+
next_method_name = method_name.next unless method_name == last
|
542
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
543
|
+
def #{method_name}
|
544
|
+
#{next_method_name}
|
545
|
+
end
|
546
|
+
End
|
547
|
+
end
|
548
|
+
go_to_depth_and_call_1(14) do
|
549
|
+
Unroller::trace(:depth => :use_call_stack_depth) do
|
550
|
+
a
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
puts '-----------------------------------------------------------'
|
555
|
+
puts 'Testing without :depth => :use_call_stack_depth (for comparison)'
|
556
|
+
go_to_depth_and_call_1(14) do
|
557
|
+
Unroller::trace() do
|
558
|
+
a
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
puts '-----------------------------------------------------------'
|
563
|
+
puts "Test max_depth 5: We shouldn't see the calls to f, g, ... because their depth > 5"
|
564
|
+
('a'..last='y').each do |method_name|
|
565
|
+
next_method_name = method_name.next unless method_name == last
|
566
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
567
|
+
def #{method_name}
|
568
|
+
#{next_method_name}
|
569
|
+
end
|
570
|
+
End
|
571
|
+
end
|
572
|
+
Unroller::trace(:max_depth => 5) do
|
573
|
+
a
|
574
|
+
end
|
575
|
+
|
576
|
+
puts '-----------------------------------------------------------'
|
577
|
+
puts 'Test with long filename (make sure it chops it correctly)'
|
578
|
+
File.open(filename = '_code_unroller_test_with_really_really_really_really_really_really_really_really_really_long_filename.rb', 'w') do |file|
|
579
|
+
file.puts "
|
580
|
+
def sit!
|
581
|
+
jump!
|
582
|
+
end
|
583
|
+
"
|
584
|
+
end
|
585
|
+
load filename
|
586
|
+
Unroller::trace(:depth => 5) do
|
587
|
+
sit!
|
588
|
+
end
|
589
|
+
require 'fileutils'
|
590
|
+
FileUtils.rm filename
|
591
|
+
|
592
|
+
puts '-----------------------------------------------------------'
|
593
|
+
puts 'Test @max_lines'
|
594
|
+
('a'..last='h').each do |method_name|
|
595
|
+
next_method_name = method_name.next unless method_name == last
|
596
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
597
|
+
def #{method_name}
|
598
|
+
#{next_method_name}
|
599
|
+
end
|
600
|
+
End
|
601
|
+
end
|
602
|
+
Unroller::trace(:max_lines => 20) do
|
603
|
+
a
|
604
|
+
end
|
605
|
+
|
606
|
+
puts '-----------------------------------------------------------'
|
607
|
+
puts 'Test @exclude_classes'
|
608
|
+
puts 'Should only see calls to Interesting::...'
|
609
|
+
class Interesting # :nodoc: all
|
610
|
+
def self.method
|
611
|
+
'...'
|
612
|
+
end
|
613
|
+
def method
|
614
|
+
'...'
|
615
|
+
end
|
616
|
+
end
|
617
|
+
module Uninteresting # :nodoc: all
|
618
|
+
class ClassThatCluttersUpOnesTraces
|
619
|
+
('a'..last='h').each do |method_name|
|
620
|
+
next_method_name = method_name.next unless method_name == last
|
621
|
+
eval <<-End, binding, __FILE__, __LINE__ + 1
|
622
|
+
def #{method_name}
|
623
|
+
#{next_method_name}
|
624
|
+
#{'Interesting::method' if method_name == last }
|
625
|
+
#{'Interesting.new.method' if method_name == last }
|
626
|
+
end
|
627
|
+
End
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
def create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
632
|
+
Uninteresting::ClassThatCluttersUpOnesTraces.new.a
|
633
|
+
end
|
634
|
+
Unroller::trace(:exclude_classes => /Uninteresting::ClassThatCluttersUpOnesTraces/) do
|
635
|
+
create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
636
|
+
end
|
637
|
+
|
638
|
+
puts '-----------------------------------------------------------'
|
639
|
+
puts 'Now let\'s be recursive! We should *not* see any calls to Interesting::* this time. But after we return from it, we should see the call to jump!'
|
640
|
+
Unroller::trace(:exclude_classes => [[/Uninteresting/, :recursive]]) do
|
641
|
+
create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
642
|
+
jump!
|
643
|
+
end
|
644
|
+
|
645
|
+
|
646
|
+
puts '-----------------------------------------------------------'
|
647
|
+
puts 'Test class definition'
|
648
|
+
Unroller::trace do
|
649
|
+
class NewClass
|
650
|
+
def hi
|
651
|
+
'hi'
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
|
657
|
+
puts '-----------------------------------------------------------'
|
658
|
+
puts 'Test rescuing exception'
|
659
|
+
def raise_an_error
|
660
|
+
raise 'an error'
|
661
|
+
end
|
662
|
+
Unroller::trace do
|
663
|
+
raise_an_error
|
664
|
+
end rescue nil
|
665
|
+
|
666
|
+
puts '-----------------------------------------------------------'
|
667
|
+
puts 'Demonstrate how :if condition is useful for local variables too (especially loops and iterators)'
|
668
|
+
(1..6).each do |i|
|
669
|
+
Unroller::trace :if => proc {
|
670
|
+
if (3..4).include?(i)
|
671
|
+
puts "Yep, it's a 3 or a 4. I guess we'll enable the tracer for this iteration then...:"
|
672
|
+
true
|
673
|
+
end
|
674
|
+
} do
|
675
|
+
puts "i is now equal to #{i}"
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
puts '-----------------------------------------------------------'
|
680
|
+
puts 'Testing the :rails "preset"'
|
681
|
+
module ActiveSupport
|
682
|
+
def self.whatever
|
683
|
+
'whatever'
|
684
|
+
end
|
685
|
+
end
|
686
|
+
module ActiveMongoose
|
687
|
+
def self.whatever
|
688
|
+
'whatever'
|
689
|
+
end
|
690
|
+
end
|
691
|
+
Unroller::trace :rails => 1 do
|
692
|
+
ActiveSupport.whatever
|
693
|
+
ActiveMongoose.whatever
|
694
|
+
ActiveSupport.whatever
|
695
|
+
end
|
696
|
+
|
697
|
+
puts '-----------------------------------------------------------'
|
698
|
+
puts 'Testing showing local variables'
|
699
|
+
def sum(a, b, c)
|
700
|
+
a + b + c
|
701
|
+
end
|
702
|
+
def my_very_own_method
|
703
|
+
Unroller::trace :show_args => false, :show_locals => true do
|
704
|
+
sum = sum(1, 2, 3)
|
705
|
+
sum
|
706
|
+
sum = sum(3, 3, 3)
|
707
|
+
end
|
708
|
+
end
|
709
|
+
my_very_own_method
|
710
|
+
|
711
|
+
end
|
metadata
CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: unroller
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
6
|
+
version: 0.0.8
|
7
7
|
date: 2007-04-20 00:00:00 -07:00
|
8
8
|
summary: A tool for generating human-readable "execution traces"
|
9
9
|
require_paths:
|
@@ -30,7 +30,6 @@ authors:
|
|
30
30
|
- Tyler Rick
|
31
31
|
files:
|
32
32
|
- lib/unroller.rb
|
33
|
-
- lib/unroller/unroller.rb
|
34
33
|
- Readme
|
35
34
|
test_files: []
|
36
35
|
|
@@ -76,3 +75,12 @@ dependencies:
|
|
76
75
|
- !ruby/object:Gem::Version
|
77
76
|
version: 0.0.0
|
78
77
|
version:
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: extensions
|
80
|
+
version_requirement:
|
81
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.0.0
|
86
|
+
version:
|
data/lib/unroller/unroller.rb
DELETED
@@ -1,589 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
gem 'facets'
|
3
|
-
require 'facets/core/module/namespace'
|
4
|
-
require 'facets/core/kernel/with'
|
5
|
-
require 'facets/core/kernel/set_with'
|
6
|
-
gem 'qualitysmith_extensions'
|
7
|
-
require 'qualitysmith_extensions/object/send_if_not_nil'
|
8
|
-
require 'qualitysmith_extensions/kernel/trap_chain'
|
9
|
-
require 'qualitysmith_extensions/kernel/capture_output'
|
10
|
-
require 'qualitysmith_extensions/string/with_knowledge_of_color'
|
11
|
-
require 'qualitysmith_extensions/exception/inspect_with_backtrace'
|
12
|
-
require 'qualitysmith_extensions/symbol/match'
|
13
|
-
gem 'colored'
|
14
|
-
require 'colored'
|
15
|
-
|
16
|
-
# To disable color, uncomment this:
|
17
|
-
#class String
|
18
|
-
# def colorize(string, options = {})
|
19
|
-
# string
|
20
|
-
# end
|
21
|
-
#end
|
22
|
-
|
23
|
-
#
|
24
|
-
class Unroller
|
25
|
-
@@instance = nil
|
26
|
-
|
27
|
-
def self.trace(options = {}, &block)
|
28
|
-
@@instance = Unroller.new(options)
|
29
|
-
@@instance.trace &block
|
30
|
-
end
|
31
|
-
|
32
|
-
attr_accessor :depth
|
33
|
-
attr_reader :tracing
|
34
|
-
|
35
|
-
def initialize(options = {})
|
36
|
-
# Defaults
|
37
|
-
@condition = Proc.new { true } # Only trace if this condition is true. Useful if the place where you put your trace {} statement gets called a lot and you only want it to actually trace for some of those calls.
|
38
|
-
@initial_depth = 0 # ("Call stack") depth to start at. Actually, you'll probably want this set considerably lower than the current call stack depth, so that the indentation isn't way off the screen.
|
39
|
-
@max_lines = nil # Stop tracing (permanently) after we have produced @max_lines lines of output. If you don't know where to place the trace(false) and you just want it to stop on its own after so many lines, you could use this...
|
40
|
-
@max_depth = nil # Don't trace anything when the depth is greater than this threshold. (This is *relative* to the starting depth, so whatever level you start at is considered depth "1".)
|
41
|
-
@exclude_classes = []
|
42
|
-
@strip_comments = true # :todo:
|
43
|
-
@use_full_path = false # :todo:
|
44
|
-
@screen_width = 150
|
45
|
-
@column_widths = [70]
|
46
|
-
@indent_step = ' ' + '|'.magenta + ' '
|
47
|
-
@column_separator = ' ' + '|'.yellow.bold + ' '
|
48
|
-
instance_variables.each do |v|
|
49
|
-
self.class.class_eval do
|
50
|
-
attr_accessor v.gsub!(/^@/, '')
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Options
|
55
|
-
options[:condition] = options.delete(:if) if options.has_key?(:if)
|
56
|
-
options[:initial_depth] = options.delete(:depth) if options.has_key?(:depth)
|
57
|
-
options[:initial_depth] = caller(0).size if options[:initial_depth] == :use_call_stack_depth
|
58
|
-
if options.has_key?(:exclude_classes)
|
59
|
-
options[:exclude_classes] = [options[:exclude_classes]] if options[:exclude_classes].is_a?(Regexp)
|
60
|
-
raise ArgumentError if !options[:exclude_classes].is_a?(Array)
|
61
|
-
end
|
62
|
-
set_with(options)
|
63
|
-
|
64
|
-
# Private
|
65
|
-
@depth = @initial_depth
|
66
|
-
@output_line = ''
|
67
|
-
@column_counter = 0
|
68
|
-
@tracing = false
|
69
|
-
@files = {}
|
70
|
-
@lines_output = 0
|
71
|
-
@excluding_calls_made_within_unintersting_call = nil
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.exclude(*args, &block)
|
75
|
-
@@instance.exclude(*args, &block)
|
76
|
-
end
|
77
|
-
def exclude(&block)
|
78
|
-
old_tracing = @tracing
|
79
|
-
(trace_off; puts 'Suspending tracing')
|
80
|
-
yield
|
81
|
-
(trace; puts 'Resuming tracing') if old_tracing
|
82
|
-
end
|
83
|
-
|
84
|
-
def trace(&block)
|
85
|
-
(puts 'Already tracing!'; return) if @tracing # This does not work. :fixme:
|
86
|
-
@tracing = true
|
87
|
-
|
88
|
-
|
89
|
-
if @condition.call
|
90
|
-
|
91
|
-
trap_chain("INT") { set_trace_func(nil) }
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# (This is the meat of the library right here, so let's set it off with at least 5 blank lines.)
|
102
|
-
set_trace_func( proc do |event, file, line, id, binding, klass|
|
103
|
-
begin # begin/rescue block
|
104
|
-
@event, @file, @line, @id, @binding, @klass =
|
105
|
-
event, file, line, id, binding, klass
|
106
|
-
|
107
|
-
# Sometimes klass is false and id is nil. Not sure why, but that's the way it is.
|
108
|
-
#printf "- (event=%8s) (klass=%10s) (id=%10s) (%s:%-2d)\n", event, klass, id, file, line #if klass.to_s == 'false'
|
109
|
-
#puts 'false!!!!!!!'+klass.inspect if klass.to_s == 'false'
|
110
|
-
|
111
|
-
return if ['c-call', 'c-return'].include? event
|
112
|
-
#(puts 'exclude') if @excluding_calls_made_within_unintersting_call unless event == 'return' # Until we hit a return and can break out of this uninteresting call, we don't want to do *anything*.
|
113
|
-
#return if uninteresting_class?(klass.to_s) unless (klass == false)
|
114
|
-
|
115
|
-
if too_far?
|
116
|
-
puts "We've read #{@max_lines} (@max_lines) lines now. Turning off tracing..."
|
117
|
-
trace_off
|
118
|
-
return
|
119
|
-
end
|
120
|
-
|
121
|
-
case event
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
when 'call'
|
126
|
-
unless skip_line?
|
127
|
-
# :todo: use # instead of :: if klass.constantize.instance_methods.include?(id)
|
128
|
-
column sprintf(' ' + '+'.cyan + ' calling'.cyan + ' ' + '%s::%s'.underline.cyan, klass, id), @column_widths[0]
|
129
|
-
newline
|
130
|
-
|
131
|
-
column code_for(file, line, '/'.magenta, :green), @column_widths[0]
|
132
|
-
file_column file, line
|
133
|
-
newline
|
134
|
-
|
135
|
-
@lines_output += 1
|
136
|
-
end
|
137
|
-
|
138
|
-
@depth += 1
|
139
|
-
|
140
|
-
|
141
|
-
when 'class'
|
142
|
-
when 'end'
|
143
|
-
when 'line'
|
144
|
-
unless skip_line?
|
145
|
-
column code_for(file, line, ' ', :bold), @column_widths[0]
|
146
|
-
file_column file, line
|
147
|
-
newline
|
148
|
-
|
149
|
-
@lines_output += 1
|
150
|
-
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
when 'return'
|
155
|
-
puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth < 0
|
156
|
-
@depth -= 1 unless @depth == 0
|
157
|
-
|
158
|
-
|
159
|
-
unless skip_line?
|
160
|
-
code = code_for(file, line, '\\'.magenta, :green)
|
161
|
-
code = code_for(file, line, '\\'.magenta + ' (returning)'.green, :green) unless code =~ /return|end/
|
162
|
-
# I've seen some really weird statements listed as "return" statements.
|
163
|
-
# I'm not really sure *why* it thinks these are returns, but let's at least identify those lines for the user. Examples:
|
164
|
-
# * must_be_open!
|
165
|
-
# * @db = db
|
166
|
-
# * stmt = @statement_factory.new( self, sql )
|
167
|
-
# I think some of the time this happens it might be because people pass the wrong line number to eval (__LINE__ instead of __LINE__ + 1, for example), so the line number is just not accurate.
|
168
|
-
# But I don't know if that explains all such cases or not...
|
169
|
-
column code, @column_widths[0]
|
170
|
-
file_column file, line
|
171
|
-
newline
|
172
|
-
|
173
|
-
@lines_output += 1
|
174
|
-
end
|
175
|
-
|
176
|
-
# Did we just get out of an uninteresting call?? Are we back in interesting land again??
|
177
|
-
if @excluding_calls_made_within_unintersting_call and @excluding_calls_made_within_unintersting_call == @depth
|
178
|
-
if @excluding_calls_made_within_unintersting_call == @depth
|
179
|
-
puts "Yay, we're back in interesting land!"
|
180
|
-
@excluding_calls_made_within_unintersting_call = nil
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
when 'raise'
|
187
|
-
# We probably always want to see these (?)... Never skip displaying them, even if we are "too deep".
|
188
|
-
column "Raising an error (#{$!}) from #{klass}".red.bold, @column_widths[0]
|
189
|
-
newline
|
190
|
-
|
191
|
-
column code_for(file, line, ' ').red, @column_widths[0]
|
192
|
-
file_column file, line
|
193
|
-
newline
|
194
|
-
|
195
|
-
else
|
196
|
-
column sprintf("- (%8s) %10s %10s (%s:%-2d)", event, klass, id, file, line)
|
197
|
-
newline
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
rescue Exception => exception
|
202
|
-
puts exception.inspect
|
203
|
-
raise
|
204
|
-
end # begin/rescue block
|
205
|
-
end) # set_trace_func
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
end # if @condition.call
|
215
|
-
|
216
|
-
if block_given?
|
217
|
-
yield
|
218
|
-
end
|
219
|
-
|
220
|
-
ensure
|
221
|
-
trace_off if block_given?
|
222
|
-
end
|
223
|
-
|
224
|
-
def self.trace_off
|
225
|
-
@@instance.trace_off
|
226
|
-
end
|
227
|
-
def trace_off
|
228
|
-
@tracing = false
|
229
|
-
set_trace_func(nil)
|
230
|
-
end
|
231
|
-
|
232
|
-
protected
|
233
|
-
#----------------------------------------------------------
|
234
|
-
# Helpers
|
235
|
-
|
236
|
-
def skip_line?
|
237
|
-
@excluding_calls_made_within_unintersting_call or calling_method_in_an_uninteresting_class?(@klass.to_s) or too_deep?
|
238
|
-
end
|
239
|
-
def too_deep?
|
240
|
-
# The + 1 is because if they're still at the initial depth (@depth - @initial_depth == 0), we want it treated as "depth 1" (1-based, for humans).
|
241
|
-
@max_depth and (@depth - @initial_depth + 1 > @max_depth)
|
242
|
-
end
|
243
|
-
def too_far?
|
244
|
-
@max_lines and (@lines_output > @max_lines)
|
245
|
-
end
|
246
|
-
def calling_method_in_an_uninteresting_class?(class_name)
|
247
|
-
( @exclude_classes + [/#{self.class.name}/] ).any? do |item|
|
248
|
-
item = item.dup
|
249
|
-
if item.is_a?(Array)
|
250
|
-
# Expect item to be in format [/class_name/, :recursive, any other flags...]
|
251
|
-
regexp = item.shift
|
252
|
-
recursive = item.include?(:recursive)
|
253
|
-
else
|
254
|
-
regexp = item
|
255
|
-
recursive = false
|
256
|
-
end
|
257
|
-
|
258
|
-
returning(class_name =~ regexp) do |uninteresting|
|
259
|
-
if uninteresting && recursive && @excluding_calls_made_within_unintersting_call.nil?
|
260
|
-
puts "Turning tracing off until we get back to depth #{@depth} again because we're calling uninteresting #{class_name}:#{@id}"
|
261
|
-
@excluding_calls_made_within_unintersting_call = @depth
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
#----------------------------------------------------------
|
268
|
-
# Formatting stuff.
|
269
|
-
def indent
|
270
|
-
@indent_step*@depth
|
271
|
-
end
|
272
|
-
|
273
|
-
def remaining_width
|
274
|
-
@screen_width - @output_line.length_without_color
|
275
|
-
end
|
276
|
-
|
277
|
-
# +width+ is the minimum width for this column. It's also a maximum if +column_overflow+ is :chop_left or :chop_right
|
278
|
-
# +color+ is only needed if you plan on doing some chopping, because if you apply the color *before* the chopping, the color code might get chopped off.
|
279
|
-
def column(string, width = nil, column_overflow = :allow, color = nil)
|
280
|
-
raise ArgumentError if ![:allow, :chop_left, :chop_right].include?(column_overflow)
|
281
|
-
raise ArgumentError if width and !(width == :remainder or width.is_a?(Fixnum))
|
282
|
-
|
283
|
-
if @column_counter == 0
|
284
|
-
@output_line << indent
|
285
|
-
width -= indent.length_without_color if width
|
286
|
-
else
|
287
|
-
@output_line << @column_separator # So the columns won't be squashed up against each other
|
288
|
-
end
|
289
|
-
|
290
|
-
if width == :remainder
|
291
|
-
width = remaining_width()
|
292
|
-
end
|
293
|
-
|
294
|
-
if width
|
295
|
-
if column_overflow =~ /chop_/
|
296
|
-
#puts "width = #{width}"
|
297
|
-
string = string.code_unroller.make_it_fit(width, column_overflow)
|
298
|
-
end
|
299
|
-
string = string.code_unroller.make_it_fit(remaining_width) # Handles maximum width
|
300
|
-
|
301
|
-
string = string.ljust_without_color(width) # Handles minimum width
|
302
|
-
end
|
303
|
-
|
304
|
-
@output_line << string.send_if_not_nil(color)
|
305
|
-
|
306
|
-
@column_counter += 1
|
307
|
-
end # def column
|
308
|
-
|
309
|
-
def newline
|
310
|
-
unless @output_line.strip.length_without_color == 0 or @output_line == @last_line_printed
|
311
|
-
Kernel.print @output_line
|
312
|
-
Kernel.puts
|
313
|
-
@last_line_printed = @output_line
|
314
|
-
end
|
315
|
-
|
316
|
-
@output_line = ''
|
317
|
-
@column_counter = 0
|
318
|
-
end
|
319
|
-
|
320
|
-
def file_column(file, line)
|
321
|
-
column "#{file}:#{line}", :remainder, :chop_left, :magenta
|
322
|
-
end
|
323
|
-
|
324
|
-
#----------------------------------------------------------
|
325
|
-
|
326
|
-
def code_for(file, line, prefix, color = nil)
|
327
|
-
if file == '(eval)'
|
328
|
-
# Can't really read the source from the 'eval' file, unfortunately!
|
329
|
-
return ' ' + prefix + ' ' + file.send_if_not_nil(color) + ''
|
330
|
-
end
|
331
|
-
|
332
|
-
line -= 1 # Adjust for the fact that line arg is 0-based, readlines line is 1-based
|
333
|
-
begin
|
334
|
-
@files[file] ||= File.readlines(file)
|
335
|
-
line = [@files[file].size - 1, line].min
|
336
|
-
' ' + prefix + ' ' + @files[file][line].strip.send_if_not_nil(color) + ''
|
337
|
-
rescue Errno::ENOENT
|
338
|
-
$stderr.puts( message = "Error Could not open #{file}" )
|
339
|
-
message
|
340
|
-
rescue Exception => exception
|
341
|
-
puts "Error while getting code for #{file}:#{line}:"
|
342
|
-
puts exception.inspect
|
343
|
-
end
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
|
348
|
-
class String
|
349
|
-
namespace :code_unroller do
|
350
|
-
|
351
|
-
def make_it_fit(max_width, overflow = :chop_right)
|
352
|
-
with(string = self) do
|
353
|
-
if string.length_without_color > max_width # Wider than desired column width; Needs to be chopped.
|
354
|
-
unless max_width < 4 # Is there even enough room for it if it *is* chopped?
|
355
|
-
if overflow == :chop_left
|
356
|
-
#puts "making string (#{string.length_without_color}) fit within #{max_width}"
|
357
|
-
#puts "chopping '#{string}' at -(#{max_width} - 3) .. -1!"
|
358
|
-
chopped_part = string[-(max_width - 3) .. -1]
|
359
|
-
string.replace '...' + chopped_part
|
360
|
-
elsif overflow == :chop_right
|
361
|
-
chopped_part = string[0 .. (max_width - 3)]
|
362
|
-
string.replace chopped_part + '...'
|
363
|
-
end
|
364
|
-
else
|
365
|
-
string = ''
|
366
|
-
end
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
if $0 == __FILE__
|
378
|
-
puts '-----------------------------------------------------------'
|
379
|
-
puts 'Simple test'
|
380
|
-
def jump!
|
381
|
-
3.times do
|
382
|
-
'jump!'
|
383
|
-
end
|
384
|
-
end
|
385
|
-
Unroller::trace
|
386
|
-
jump!
|
387
|
-
Unroller::trace_off
|
388
|
-
|
389
|
-
puts '-----------------------------------------------------------'
|
390
|
-
puts "Testing that this doesn't trace anything (condition == false proc)"
|
391
|
-
$trace = false
|
392
|
-
Unroller::trace(:condition => proc { $trace }) do
|
393
|
-
jump!
|
394
|
-
end
|
395
|
-
|
396
|
-
puts '-----------------------------------------------------------'
|
397
|
-
puts "Testing that this doesn't trace the inner method (method2), but does trace method1 and method3 (exclude)"
|
398
|
-
def method1; end
|
399
|
-
def method2
|
400
|
-
'stuff!'
|
401
|
-
end
|
402
|
-
def method3; end
|
403
|
-
Unroller::trace do
|
404
|
-
method1
|
405
|
-
Unroller::exclude do
|
406
|
-
method2
|
407
|
-
end
|
408
|
-
method3
|
409
|
-
end
|
410
|
-
|
411
|
-
puts '-----------------------------------------------------------'
|
412
|
-
puts 'Test with block; very deep (test for over-wide columns)'
|
413
|
-
('a'..last='y').each do |method_name|
|
414
|
-
next_method_name = method_name.next unless method_name == last
|
415
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
416
|
-
def #{method_name}
|
417
|
-
#{next_method_name}
|
418
|
-
end
|
419
|
-
End
|
420
|
-
end
|
421
|
-
Unroller::trace(:depth => 5) do
|
422
|
-
a
|
423
|
-
end
|
424
|
-
|
425
|
-
puts '-----------------------------------------------------------'
|
426
|
-
puts 'Test watching a call stack unwind (only)'
|
427
|
-
('a'..last='y').each do |method_name|
|
428
|
-
next_method_name = method_name.next unless method_name == last
|
429
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
430
|
-
def #{method_name}
|
431
|
-
#{next_method_name}
|
432
|
-
#{'Unroller::trace(:depth => caller(0).size)' if method_name == last }
|
433
|
-
end
|
434
|
-
End
|
435
|
-
end
|
436
|
-
a
|
437
|
-
Unroller::trace_off
|
438
|
-
|
439
|
-
|
440
|
-
puts '-----------------------------------------------------------'
|
441
|
-
puts 'Testing :depth => :use_call_stack_depth'
|
442
|
-
def go_to_depth_and_call_1(depth, &block)
|
443
|
-
#puts caller(0).size
|
444
|
-
if caller(0).size == depth
|
445
|
-
puts 'calling a'
|
446
|
-
block.call
|
447
|
-
else
|
448
|
-
go_to_depth_and_call_2(depth, &block)
|
449
|
-
end
|
450
|
-
#puts caller(0).size
|
451
|
-
end
|
452
|
-
def go_to_depth_and_call_2(depth, &block)
|
453
|
-
#puts caller(0).size
|
454
|
-
if caller(0).size == depth
|
455
|
-
puts 'calling a'
|
456
|
-
block.call
|
457
|
-
else
|
458
|
-
go_to_depth_and_call_1(depth, &block)
|
459
|
-
end
|
460
|
-
#puts caller(0).size
|
461
|
-
end
|
462
|
-
('a'..last='c').each do |method_name|
|
463
|
-
next_method_name = method_name.next unless method_name == last
|
464
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
465
|
-
def #{method_name}
|
466
|
-
#{next_method_name}
|
467
|
-
end
|
468
|
-
End
|
469
|
-
end
|
470
|
-
go_to_depth_and_call_1(14) do
|
471
|
-
Unroller::trace(:depth => :use_call_stack_depth) do
|
472
|
-
a
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
puts '-----------------------------------------------------------'
|
477
|
-
puts 'Testing without :depth => :use_call_stack_depth (for comparison)'
|
478
|
-
go_to_depth_and_call_1(14) do
|
479
|
-
Unroller::trace() do
|
480
|
-
a
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
puts '-----------------------------------------------------------'
|
485
|
-
puts "Test max_depth 5: We shouldn't see the calls to f, g, ... because their depth > 5"
|
486
|
-
('a'..last='y').each do |method_name|
|
487
|
-
next_method_name = method_name.next unless method_name == last
|
488
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
489
|
-
def #{method_name}
|
490
|
-
#{next_method_name}
|
491
|
-
end
|
492
|
-
End
|
493
|
-
end
|
494
|
-
Unroller::trace(:max_depth => 5) do
|
495
|
-
a
|
496
|
-
end
|
497
|
-
|
498
|
-
puts '-----------------------------------------------------------'
|
499
|
-
puts 'Test with long filename (make sure it chops it correctly)'
|
500
|
-
File.open(filename = '_code_unroller_test_with_really_really_really_really_really_really_really_really_really_long_filename.rb', 'w') do |file|
|
501
|
-
file.puts "
|
502
|
-
def sit!
|
503
|
-
jump!
|
504
|
-
end
|
505
|
-
"
|
506
|
-
end
|
507
|
-
load filename
|
508
|
-
Unroller::trace(:depth => 5) do
|
509
|
-
sit!
|
510
|
-
end
|
511
|
-
require 'fileutils'
|
512
|
-
FileUtils.rm filename
|
513
|
-
|
514
|
-
puts '-----------------------------------------------------------'
|
515
|
-
puts 'Test @max_lines'
|
516
|
-
('a'..last='h').each do |method_name|
|
517
|
-
next_method_name = method_name.next unless method_name == last
|
518
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
519
|
-
def #{method_name}
|
520
|
-
#{next_method_name}
|
521
|
-
end
|
522
|
-
End
|
523
|
-
end
|
524
|
-
Unroller::trace(:max_lines => 20) do
|
525
|
-
a
|
526
|
-
end
|
527
|
-
|
528
|
-
puts '-----------------------------------------------------------'
|
529
|
-
puts 'Test @exclude_classes'
|
530
|
-
puts 'Should only see calls to Interesting::...'
|
531
|
-
class Interesting # :nodoc: all
|
532
|
-
def self.method
|
533
|
-
'...'
|
534
|
-
end
|
535
|
-
def method
|
536
|
-
'...'
|
537
|
-
end
|
538
|
-
end
|
539
|
-
module Uninteresting # :nodoc: all
|
540
|
-
class ClassThatCluttersUpOnesTraces
|
541
|
-
('a'..last='h').each do |method_name|
|
542
|
-
next_method_name = method_name.next unless method_name == last
|
543
|
-
eval <<-End, binding, __FILE__, __LINE__ + 1
|
544
|
-
def #{method_name}
|
545
|
-
#{next_method_name}
|
546
|
-
#{'Interesting::method' if method_name == last }
|
547
|
-
#{'Interesting.new.method' if method_name == last }
|
548
|
-
end
|
549
|
-
End
|
550
|
-
end
|
551
|
-
end
|
552
|
-
end
|
553
|
-
def create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
554
|
-
Uninteresting::ClassThatCluttersUpOnesTraces.new.a
|
555
|
-
end
|
556
|
-
Unroller::trace(:exclude_classes => /Uninteresting::ClassThatCluttersUpOnesTraces/) do
|
557
|
-
create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
558
|
-
end
|
559
|
-
|
560
|
-
puts '-----------------------------------------------------------'
|
561
|
-
puts 'Now let\'s be recursive! We should *not* see any calls to Interesting::* this time. But after we return from it, we should see the call to jump!'
|
562
|
-
Unroller::trace(:exclude_classes => [[/Uninteresting/, :recursive]]) do
|
563
|
-
create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
|
564
|
-
jump!
|
565
|
-
end
|
566
|
-
|
567
|
-
|
568
|
-
puts '-----------------------------------------------------------'
|
569
|
-
puts 'Test class definition'
|
570
|
-
Unroller::trace do
|
571
|
-
class NewClass
|
572
|
-
def hi
|
573
|
-
'hi'
|
574
|
-
end
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
|
579
|
-
puts '-----------------------------------------------------------'
|
580
|
-
puts 'Test rescuing exception'
|
581
|
-
def raise_an_error
|
582
|
-
raise 'an error'
|
583
|
-
end
|
584
|
-
Unroller::trace do
|
585
|
-
raise_an_error
|
586
|
-
end
|
587
|
-
|
588
|
-
|
589
|
-
end
|