unroller 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/Readme +182 -0
  2. data/lib/unroller.rb +3 -0
  3. data/lib/unroller/unroller.rb +564 -0
  4. metadata +69 -0
data/Readme ADDED
@@ -0,0 +1,182 @@
1
+ = <i>Ruby Unroller</i> -- a code tracing tool for Ruby
2
+
3
+ [<b>Home page</b>:] http://unroller.rubyforge.org/
4
+ [<b>Project site</b>:] http://rubyforge.org/projects/unroller/
5
+ [<b>Wiki</b>:] http://whynotwiki.com/Ruby_Unroller
6
+ [<b>Author</b>:] Tyler Rick
7
+ [<b>Copyright</b>:] 2007 QualitySmith, Inc.
8
+ [<b>License</b>:] GPL
9
+
10
+ ==Introduction / What it is
11
+
12
+ Ruby Unroller is a tool for generating human-readable "execution traces".
13
+ While it is enabled, it will watch every Ruby statement and method call that gets executed and will display the source code on your screen in real-time as it is being executed.
14
+
15
+ You can use it for...
16
+
17
+ ===Debugging
18
+
19
+ A very efficient tool for seeing what code actually gets executed... Add it to your toolbox along with the other tricks you already know, like inspecting <tt>caller()</tt> or printing "got here" at various places in your code.
20
+
21
+ ===Learning/Exploring
22
+
23
+ It's a great tool for exploring 3rd-party source code that you aren't familiar with. If you've ever found yourself wondering "How was that method implemented?" or "What's going on behind the scenes when I call such-and-such?", then this tool is for you!
24
+
25
+
26
+ ==Usage
27
+
28
+ Just insert these line before the point you want to start tracing:
29
+
30
+ require 'rubygems'
31
+ require 'unroller'
32
+ Unroller::trace
33
+
34
+ And if you want the tracing to stop, just add this line somewhere:
35
+
36
+ Unroller::trace_off
37
+
38
+ You can also pass a block to <tt>Unroller::trace</tt> and it will automatically turn off tracing as soon as the block has been executed.
39
+
40
+ Unroller::trace do
41
+ ...
42
+ end
43
+
44
+ ===Example
45
+
46
+ Say I have an ActiveRecord model and want to know exactly what actually goes on behind the scenes when I call model.save! . All I have to do is wrap the method call in a "trace" block, like this:
47
+
48
+ Unroller::trace do
49
+ model.save!
50
+ end
51
+
52
+ , start the script, and then watch as the source code is unfolded before my very eyes!
53
+
54
+ Screenshot[link:include/screenshot1.png]
55
+
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
+
58
+
59
+ ===Reducing verbosity
60
+
61
+ This can generate some really *verbose* output... A couple options are available to help things under control:
62
+
63
+ Ignore a section of code that is within the block passed to +trace+.
64
+
65
+ Unroller::trace do
66
+ stuff_you_care_about
67
+ Unroller::exclude do
68
+ stuff_that_you_really_dont_care_about
69
+ end
70
+ stuff_you_care_about
71
+ end
72
+
73
+ You may find that your trace is cluttered/dominated by calls to a small set of methods and classes that you don't care about. These options help you to exclude the worst offenders in a hurry:
74
+
75
+ <tt>:exclude_classes</tt> ::
76
+ Unroller won't show a trace for any calls to methods from the given class or classes (regular expressions).
77
+ Pass [/class_name/, :recursive] to also not show the trace for any calls made *from* those uninteresting methods.
78
+ <tt>:exclude_methods</tt> (not implementd) ::
79
+ Like <tt>:exclude_classes</tt> only you give it _method_ names instead of class names. Maybe will have the ability to specify a class name as well as a method name if you want to be more specific (f.e., <tt>ThatOneClass#format</tt> if you still want to include calls to other methods named <tt>format<tt>).
80
+
81
+ These options are for more general overall control:
82
+
83
+ <tt>:omit_depth_greater_than => depth</tt> ::
84
+ Use this to prevent from going more than <tt>depth<tt> levels deep if you find that the trace is cluttered by a bunch of really deep calls.
85
+ <tt>:stop_after_lines_output</tt> ::
86
+ 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...
87
+
88
+ Examples:
89
+
90
+ Unroller::trace :stop_after_lines_output => 100, :exclude_classes => /Boring/ { ... }
91
+
92
+ Unroller::trace :if => proc{$tracing_enabled}, :omit_depth_greater_than => 9, :exclude_classes =>
93
+ [
94
+ /Benchmark/,
95
+ /Logger/,
96
+ /MonitorMixin/,
97
+ /Set/,
98
+ /HashWithIndifferentAccess/,
99
+ /ERB/,
100
+ /ActiveRecord/,
101
+ /SQLite3/,
102
+ /Class/,
103
+ /ActiveSupport/,
104
+ /ActiveSupport::Deprecation/,
105
+ /Pathname/
106
+ ] do
107
+ ...
108
+ end
109
+
110
+
111
+
112
+ ===How did that method even get *called*?
113
+
114
+ Some of the time, you may be trying to answer the question "How did that method even get *called*?" This tool can't directly help answer that question unless you're willing to enable tracing really early and wade through pages and pages of output.
115
+
116
+ If you enable tracing from within that method, it will only show the calls that came *after* the mysterious call to the mystery function, not what came *before*, which is what you're interested in.
117
+
118
+ So... you could always <tt>p caller(0)</tt> within your mystery function and then put a trace around one of the calls leading up to this call, a little further up the stack...
119
+
120
+
121
+ ===Usage in Rails
122
+
123
+ If you want to see all code that gets executed within a certain action in your controller, you can just add this snippet to your controller, request the page, and watch the console where you have Webrick running as pages and pages of ActionController/ActionRecord code go flying by...
124
+
125
+ around_filter do |controller, action|
126
+ Unroller::trace do
127
+ action.call
128
+ end
129
+ end
130
+
131
+ ==Installation
132
+
133
+ sudo gem install unroller
134
+
135
+ ==Status
136
+
137
+ It's pretty much fully functional, but may still have a couple rough edges.
138
+
139
+ Occasionally it has caused some segmentation faults and other weirdness for me while using it, but maybe I was just doing something stupid. Generally it behaves quite reliably and stably.
140
+
141
+ (Don't even *think* about leaving this in your production code!)
142
+
143
+ ==About the name
144
+
145
+ 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".)
146
+
147
+ If there are method calls (even recursion), it will sort of "unroll" the method definition for you so you can see it.
148
+
149
+ It also alludes to some ideas from compiler design. Think {"method inlining"}[http://en.wikipedia.org/wiki/Inline_expansion] and {"loop unrolling"}[http://en.wikipedia.org/wiki/Loop_unrolling] in real-time. Not quite the same thing, but close enough.
150
+
151
+ It takes something that is actually very stack-based (the path that Ruby interpreter takes while executing your source code) and "flattens" it. It still tries to keep things appearing hierarchical (by means of the indent levels), but in essence it takes code from a bunch of different files and merges them into one "flat" output stream.
152
+
153
+ Phew!
154
+
155
+ Other name ideas are welcome!
156
+ * Unfolder?
157
+ * ...
158
+
159
+ ==Similarity to debuggers/profilers / What it is not
160
+
161
+ It's sort of like a cross between a Ruby debugger and a profiler.
162
+
163
+ It's like a debugger because it lets you step through code execution one line at time (similar to the "step into" / "next" commands in most debuggers). It's not a debugger, though, because it doesn't let you inspect variables, set breakpoints, etc. It lets you *watch* the execution ... and that's it! So it's not as powerful as a real debugger, but it can be a lot *faster*! Rather than pressing F8 (or whatever the shortcut is for the 'step' command) 1000 times, you can just tell it "trace from here to here" and it will.
164
+
165
+ And like a profiler, it follows every method call, using Ruby's set_trace_func. Like a profiler, it can be helpful for diagnosing performance bottlenecks in your application -- at least those that involve excessive or unwanted method calls or recursion. It's not a profiler, though, because it doesn't _time_ the method calls like a 'real' profiler would.
166
+
167
+ It's also sort of like a call stack (caller(0)). But unlike the callstack you usually see, which only shows where you've been, this one doesn't show where you've been; it only shows where you _going_ -- or more accurately, where you *go* after you enable the tracing. So while it works equally well for tracing while the call stack is being unwound (returning from calls, which pops that call off the stack), the most common use of the tracer is to see what happens "within" a method call -- that method will in turn call _other_ methods, and the stack will grow temporarily -- but then it will unwind again and you'll get back to the method where you started the trace (hopefully -- eventually?), with a net change in stack size of 0.
168
+
169
+ (If you _do_ want to watch the trace as the call stack is unwound completely, be sure to pass <tt>:depth => caller(0).size</tt> to the Unroller so that it can start the indenting at an appropriate level...)
170
+
171
+ ==Possibly related projects
172
+
173
+ * http://rubyforge.org/projects/dev-utils/ : Collects utilities that aid Ruby development, e.g. testing and debugging. Version 1.0 contains simple debug logging, tracing, and escaping to IRB.
174
+ * http://rubyforge.org/projects/ruby-uml/ : Generates uml diagrams by tracing the run of an application for analysation of an existing application and to provide support for refactorisations.
175
+
176
+ ==To do
177
+
178
+ * Make a GUI interface that lets you quickly collapse/nodes nodes of the tree.
179
+ * 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....
180
+ * :include_classes option in addition to :exclude_classes?
181
+
182
+
data/lib/unroller.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'facets/core/kernel/require_local'
3
+ require_local 'unroller/unroller'
@@ -0,0 +1,564 @@
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
+ @stop_after_lines_output = nil # 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
+ @omit_depth_greater_than = nil # Don't go too many levels deep. (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 #{@stop_after_lines_output} (@stop_after_lines_output) lines now. Turning off tracing..."
117
+ trace_off
118
+ return
119
+ end
120
+
121
+ case event
122
+
123
+
124
+
125
+ when 'call'
126
+ unless @excluding_calls_made_within_unintersting_call or calling_method_in_an_uninteresting_class?(klass.to_s) or too_deep?
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
+
142
+ when 'line'
143
+ unless @excluding_calls_made_within_unintersting_call or calling_method_in_an_uninteresting_class?(klass.to_s) or too_deep?
144
+ column code_for(file, line, ' ', :bold), @column_widths[0]
145
+ file_column file, line
146
+ newline
147
+
148
+ @lines_output += 1
149
+ end
150
+
151
+
152
+
153
+ when 'return'
154
+ puts "Warning: @depth < 0. You may wish to call trace with a :depth => depth value greater than #{@initial_depth}" if @depth < 0
155
+ @depth -= 1 unless @depth == 0
156
+
157
+
158
+ unless @excluding_calls_made_within_unintersting_call or calling_method_in_an_uninteresting_class?(klass.to_s) or too_deep?
159
+ code = code_for(file, line, '\\'.magenta, :green)
160
+ code = code_for(file, line, '\\'.magenta + ' (returning)'.green, :green) unless code =~ /return|end/
161
+ # I've seen some really weird statements listed as "return" statements.
162
+ # I'm not really sure *why* it thinks these are returns, but let's at least identify those lines for the user. Examples:
163
+ # * must_be_open!
164
+ # * @db = db
165
+ # * stmt = @statement_factory.new( self, sql )
166
+ # 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.
167
+ # But I don't know if that explains all such cases or not...
168
+ column code, @column_widths[0]
169
+ file_column file, line
170
+ newline
171
+
172
+ @lines_output += 1
173
+ end
174
+
175
+ # Did we just get out of an uninteresting call?? Are we back in interesting land again??
176
+ if @excluding_calls_made_within_unintersting_call and @excluding_calls_made_within_unintersting_call == @depth
177
+ if @excluding_calls_made_within_unintersting_call == @depth
178
+ puts "Yay, we're back in interesting land!"
179
+ @excluding_calls_made_within_unintersting_call = nil
180
+ end
181
+ end
182
+
183
+
184
+
185
+ when 'raise'
186
+ column "Raising an error (#{$!}) from #{klass}".red.bold, @column_widths[0]
187
+ newline
188
+
189
+ column code_for(file, line, ' ').red, @column_widths[0]
190
+ file_column file, line
191
+ newline
192
+
193
+ else
194
+ printf "- (%8s) %10s %10s (%s:%-2d)", event, klass, id, file, line
195
+ newline
196
+ end
197
+
198
+
199
+ rescue Exception => exception
200
+ puts exception.inspect
201
+ raise
202
+ end # begin/rescue block
203
+ end) # set_trace_func
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+ end # if @condition.call
213
+
214
+ if block_given?
215
+ yield
216
+ end
217
+
218
+ ensure
219
+ trace_off if block_given?
220
+ end
221
+
222
+ def self.trace_off
223
+ @@instance.trace_off
224
+ end
225
+ def trace_off
226
+ @tracing = false
227
+ set_trace_func(nil)
228
+ end
229
+
230
+ protected
231
+ #----------------------------------------------------------
232
+ # Helpers
233
+
234
+ def too_deep?
235
+ # 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).
236
+ @omit_depth_greater_than and (@depth - @initial_depth + 1 > @omit_depth_greater_than)
237
+ end
238
+ def too_far?
239
+ @stop_after_lines_output and (@lines_output > @stop_after_lines_output)
240
+ end
241
+ def calling_method_in_an_uninteresting_class?(class_name)
242
+ ( @exclude_classes + [/#{self.class.name}/] ).any? do |item|
243
+ item = item.dup
244
+ if item.is_a?(Array)
245
+ # Expect item to be in format [/class_name/, :recursive, any other flags...]
246
+ regexp = item.shift
247
+ recursive = item.include?(:recursive)
248
+ else
249
+ regexp = item
250
+ recursive = false
251
+ end
252
+
253
+ returning(class_name =~ regexp) do |uninteresting|
254
+ if uninteresting && recursive && @excluding_calls_made_within_unintersting_call.nil?
255
+ puts "Turning tracing off until we get back to depth #{@depth} again because we're calling uninteresting #{class_name}:#{@id}"
256
+ @excluding_calls_made_within_unintersting_call = @depth
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ #----------------------------------------------------------
263
+ # Formatting stuff.
264
+ def indent
265
+ @indent_step*@depth
266
+ end
267
+
268
+ def remaining_width
269
+ @screen_width - @output_line.length_without_color
270
+ end
271
+
272
+ # +width+ is the minimum width for this column. It's also a maximum if +column_overflow+ is :chop_left or :chop_right
273
+ # +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.
274
+ def column(string, width = nil, column_overflow = :allow, color = nil)
275
+ raise ArgumentError if ![:allow, :chop_left, :chop_right].include?(column_overflow)
276
+ raise ArgumentError if width and !(width == :remainder or width.is_a?(Fixnum))
277
+
278
+ if @column_counter == 0
279
+ @output_line << indent
280
+ width -= indent.length_without_color if width
281
+ else
282
+ @output_line << @column_separator # So the columns won't be squashed up against each other
283
+ end
284
+
285
+ if width == :remainder
286
+ width = remaining_width()
287
+ end
288
+
289
+ if width
290
+ if column_overflow =~ /chop_/
291
+ #puts "width = #{width}"
292
+ string = string.code_unroller.make_it_fit(width, column_overflow)
293
+ end
294
+ string = string.code_unroller.make_it_fit(remaining_width) # Handles maximum width
295
+
296
+ string = string.ljust_without_color(width) # Handles minimum width
297
+ end
298
+
299
+ @output_line << string.send_if_not_nil(color)
300
+
301
+ @column_counter += 1
302
+ end # def column
303
+
304
+ def newline
305
+ unless @output_line.strip.length_without_color == 0 or @output_line == @last_line_printed
306
+ Kernel.print @output_line
307
+ Kernel.puts
308
+ @last_line_printed = @output_line
309
+ end
310
+
311
+ @output_line = ''
312
+ @column_counter = 0
313
+ end
314
+
315
+ def file_column(file, line)
316
+ column "#{file}:#{line}", :remainder, :chop_left, :magenta
317
+ end
318
+
319
+ #----------------------------------------------------------
320
+
321
+ def code_for(file, line, prefix, color = nil)
322
+ if file == '(eval)'
323
+ # Can't really read the source from the 'eval' file, unfortunately!
324
+ return ' ' + prefix + ' ' + file.send_if_not_nil(color) + ''
325
+ end
326
+
327
+ line -= 1 # Adjust for the fact that line arg is 0-based, readlines line is 1-based
328
+ begin
329
+ @files[file] ||= File.readlines(file)
330
+ line = [@files[file].size - 1, line].min
331
+ ' ' + prefix + ' ' + @files[file][line].strip.send_if_not_nil(color) + ''
332
+ rescue Errno::ENOENT
333
+ $stderr.puts( message = "Error Could not open #{file}" )
334
+ message
335
+ rescue Exception => exception
336
+ puts "Error while getting code for #{file}:#{line}:"
337
+ puts exception.inspect
338
+ end
339
+ end
340
+ end
341
+
342
+
343
+ class String
344
+ namespace :code_unroller do
345
+
346
+ def make_it_fit(max_width, overflow = :chop_right)
347
+ with(string = self) do
348
+ if string.length_without_color > max_width # Wider than desired column width; Needs to be chopped.
349
+ unless max_width < 4 # Is there even enough room for it if it *is* chopped?
350
+ if overflow == :chop_left
351
+ #puts "making string (#{string.length_without_color}) fit within #{max_width}"
352
+ #puts "chopping '#{string}' at -(#{max_width} - 3) .. -1!"
353
+ chopped_part = string[-(max_width - 3) .. -1]
354
+ string.replace '...' + chopped_part
355
+ elsif overflow == :chop_right
356
+ chopped_part = string[0 .. (max_width - 3)]
357
+ string.replace chopped_part + '...'
358
+ end
359
+ else
360
+ string = ''
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ end
367
+ end
368
+
369
+
370
+
371
+
372
+ if $0 == __FILE__
373
+ puts '-----------------------------------------------------------'
374
+ puts 'Simple test'
375
+ def jump!
376
+ 3.times do
377
+ 'jump!'
378
+ end
379
+ end
380
+ Unroller::trace
381
+ jump!
382
+ Unroller::trace_off
383
+
384
+ puts '-----------------------------------------------------------'
385
+ puts "Testing that this doesn't trace anything (condition == false proc)"
386
+ $trace = false
387
+ Unroller::trace(:condition => proc { $trace }) do
388
+ jump!
389
+ end
390
+
391
+ puts '-----------------------------------------------------------'
392
+ puts "Testing that this doesn't trace the inner method (method2), but does trace method1 and method3 (exclude)"
393
+ def method1; end
394
+ def method2
395
+ 'stuff!'
396
+ end
397
+ def method3; end
398
+ Unroller::trace do
399
+ method1
400
+ Unroller::exclude do
401
+ method2
402
+ end
403
+ method3
404
+ end
405
+
406
+ puts '-----------------------------------------------------------'
407
+ puts 'Test with block; very deep (test for over-wide columns)'
408
+ ('a'..last='y').each do |method_name|
409
+ next_method_name = method_name.next unless method_name == last
410
+ eval <<-End, binding, __FILE__, __LINE__ + 1
411
+ def #{method_name}
412
+ #{next_method_name}
413
+ end
414
+ End
415
+ end
416
+ Unroller::trace(:depth => 5) do
417
+ a
418
+ end
419
+
420
+ puts '-----------------------------------------------------------'
421
+ puts 'Test watching a call stack unwind (only)'
422
+ ('a'..last='y').each do |method_name|
423
+ next_method_name = method_name.next unless method_name == last
424
+ eval <<-End, binding, __FILE__, __LINE__ + 1
425
+ def #{method_name}
426
+ #{next_method_name}
427
+ #{'Unroller::trace(:depth => caller(0).size)' if method_name == last }
428
+ end
429
+ End
430
+ end
431
+ a
432
+ Unroller::trace_off
433
+
434
+
435
+ puts '-----------------------------------------------------------'
436
+ puts 'Testing :depth => :use_call_stack_depth'
437
+ def go_to_depth_and_call_1(depth, &block)
438
+ #puts caller(0).size
439
+ if caller(0).size == depth
440
+ puts 'calling a'
441
+ block.call
442
+ else
443
+ go_to_depth_and_call_2(depth, &block)
444
+ end
445
+ #puts caller(0).size
446
+ end
447
+ def go_to_depth_and_call_2(depth, &block)
448
+ #puts caller(0).size
449
+ if caller(0).size == depth
450
+ puts 'calling a'
451
+ block.call
452
+ else
453
+ go_to_depth_and_call_1(depth, &block)
454
+ end
455
+ #puts caller(0).size
456
+ end
457
+ ('a'..last='c').each do |method_name|
458
+ next_method_name = method_name.next unless method_name == last
459
+ eval <<-End, binding, __FILE__, __LINE__ + 1
460
+ def #{method_name}
461
+ #{next_method_name}
462
+ end
463
+ End
464
+ end
465
+ go_to_depth_and_call_1(14) do
466
+ Unroller::trace(:depth => :use_call_stack_depth) do
467
+ a
468
+ end
469
+ end
470
+
471
+ puts '-----------------------------------------------------------'
472
+ puts 'Testing without :depth => :use_call_stack_depth (for comparison)'
473
+ go_to_depth_and_call_1(14) do
474
+ Unroller::trace() do
475
+ a
476
+ end
477
+ end
478
+
479
+ puts '-----------------------------------------------------------'
480
+ puts "Test omit_depth_greater_than 5: We shouldn't see the calls to f, g, ... because their depth > 5"
481
+ ('a'..last='y').each do |method_name|
482
+ next_method_name = method_name.next unless method_name == last
483
+ eval <<-End, binding, __FILE__, __LINE__ + 1
484
+ def #{method_name}
485
+ #{next_method_name}
486
+ end
487
+ End
488
+ end
489
+ Unroller::trace(:omit_depth_greater_than => 5) do
490
+ a
491
+ end
492
+
493
+ puts '-----------------------------------------------------------'
494
+ puts 'Test with long filename (make sure it chops it correctly)'
495
+ File.open(filename = '_code_unroller_test_with_really_really_really_really_really_really_really_really_really_long_filename.rb', 'w') do |file|
496
+ file.puts "
497
+ def sit!
498
+ jump!
499
+ end
500
+ "
501
+ end
502
+ load filename
503
+ Unroller::trace(:depth => 5) do
504
+ sit!
505
+ end
506
+ require 'fileutils'
507
+ FileUtils.rm filename
508
+
509
+ puts '-----------------------------------------------------------'
510
+ puts 'Test @stop_after_lines_output'
511
+ ('a'..last='h').each do |method_name|
512
+ next_method_name = method_name.next unless method_name == last
513
+ eval <<-End, binding, __FILE__, __LINE__ + 1
514
+ def #{method_name}
515
+ #{next_method_name}
516
+ end
517
+ End
518
+ end
519
+ Unroller::trace(:stop_after_lines_output => 20) do
520
+ a
521
+ end
522
+
523
+ puts '-----------------------------------------------------------'
524
+ puts 'Test @exclude_classes'
525
+ puts 'Should only see calls to Interesting::...'
526
+ class Interesting # :nodoc: all
527
+ def self.method
528
+ '...'
529
+ end
530
+ def method
531
+ '...'
532
+ end
533
+ end
534
+ module Uninteresting # :nodoc: all
535
+ class ClassThatCluttersUpOnesTraces
536
+ ('a'..last='h').each do |method_name|
537
+ next_method_name = method_name.next unless method_name == last
538
+ eval <<-End, binding, __FILE__, __LINE__ + 1
539
+ def #{method_name}
540
+ #{next_method_name}
541
+ #{'Interesting::method' if method_name == last }
542
+ #{'Interesting.new.method' if method_name == last }
543
+ end
544
+ End
545
+ end
546
+ end
547
+ end
548
+ def create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
549
+ Uninteresting::ClassThatCluttersUpOnesTraces.new.a
550
+ end
551
+ Unroller::trace(:exclude_classes => /Uninteresting::ClassThatCluttersUpOnesTraces/) do
552
+ create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
553
+ end
554
+
555
+ puts '-----------------------------------------------------------'
556
+ 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!'
557
+ Unroller::trace(:exclude_classes => [[/Uninteresting/, :recursive]]) do
558
+ create_an_instance_of_UninterestingClassThatCluttersUpOnesTraces
559
+ jump!
560
+ end
561
+
562
+
563
+
564
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: unroller
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.3
7
+ date: 2007-04-13 00:00:00 -07:00
8
+ summary: A tool for generating human-readable "execution traces"
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage: http://rdoc.qualitysmith.com/unroller
13
+ rubyforge_project: unroller
14
+ description: A debugging/tracing tool for Ruby. While it is enabled, it will watch every Ruby statement and method call that gets executed and will display the source code that is being executed in real time on your screen.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Tyler Rick
31
+ files:
32
+ - lib/unroller.rb
33
+ - lib/unroller/unroller.rb
34
+ - Readme
35
+ test_files: []
36
+
37
+ rdoc_options:
38
+ - --title
39
+ - unroller
40
+ - --main
41
+ - Readme
42
+ - --line-numbers
43
+ extra_rdoc_files:
44
+ - Readme
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ requirements: []
50
+
51
+ dependencies:
52
+ - !ruby/object:Gem::Dependency
53
+ name: facets
54
+ version_requirement:
55
+ version_requirements: !ruby/object:Gem::Version::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 1.8.54
60
+ version:
61
+ - !ruby/object:Gem::Dependency
62
+ name: qualitysmith_extensions
63
+ version_requirement:
64
+ version_requirements: !ruby/object:Gem::Version::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.13
69
+ version: