kefka 0.0.3 → 0.0.4
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/TODO +6 -0
- data/VERSION +1 -1
- data/app.rb +12 -21
- data/kefka.gemspec +5 -4
- data/lib/kefka.rb +90 -64
- data/public/javascripts/app.js +21 -25
- data/spec/fixture/sample_a.rb +73 -0
- data/spec/kekfa_spec.rb +294 -0
- data/{test/helper.rb → spec/spec_helper.rb} +3 -5
- metadata +24 -23
- data/test/test_kefka.rb +0 -7
data/TODO
CHANGED
@@ -51,3 +51,9 @@ edge cases
|
|
51
51
|
- if the user really wants to see everything, he has to specify granurality cmd line option
|
52
52
|
what if there's a binding.pry in the code
|
53
53
|
|
54
|
+
ways to reduce clutter
|
55
|
+
1. only show variable values if that variable is present on the line
|
56
|
+
2. input file
|
57
|
+
show values over there
|
58
|
+
line values
|
59
|
+
check file
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
data/app.rb
CHANGED
@@ -12,40 +12,31 @@ end
|
|
12
12
|
get '/callgraph' do
|
13
13
|
content_type :json
|
14
14
|
|
15
|
-
|
16
|
-
file = File.open(path)
|
15
|
+
file_path = "#{File.expand_path(File.dirname(__FILE__))}/examples/sample_a.rb"
|
17
16
|
|
18
|
-
@tracer = Kefka::Tracer.new
|
19
|
-
@tracer.trace(
|
17
|
+
@tracer = Kefka::Tracer.new(Logger::INFO)
|
18
|
+
@tracer.trace(file_path)
|
20
19
|
|
21
20
|
# input code
|
22
|
-
|
21
|
+
input = CodeRay.scan(@tracer.code, :ruby).div(:line_numbers => :table)
|
23
22
|
|
24
23
|
graph = @tracer.method_graph
|
25
24
|
|
26
25
|
# output call graph using dot if graphviz is installed
|
27
|
-
if
|
26
|
+
if is_graphviz_installed = system("which dot")
|
28
27
|
graph.write_to_graphic_file("png", "#{File.expand_path(File.dirname(__FILE__))}/public/graph")
|
29
28
|
end
|
30
29
|
|
31
30
|
# html code graph
|
32
31
|
graph.vertices.each { |method| method.format = :html }
|
33
32
|
|
33
|
+
# locals
|
34
|
+
locals = @tracer.local_values
|
35
|
+
|
34
36
|
{
|
35
|
-
:
|
36
|
-
:
|
37
|
-
:graph => graph
|
37
|
+
:input => input,
|
38
|
+
:is_graphviz_installed => is_graphviz_installed,
|
39
|
+
:graph => graph,
|
40
|
+
:locals => locals
|
38
41
|
}.to_json
|
39
42
|
end
|
40
|
-
|
41
|
-
get '/locals' do
|
42
|
-
content_type :json
|
43
|
-
|
44
|
-
path = "#{File.expand_path(File.dirname(__FILE__))}/examples/sample_a.rb"
|
45
|
-
file = File.open(path)
|
46
|
-
|
47
|
-
tracer = Kefka::Tracer.new
|
48
|
-
tracer.trace(file, :local_values_handler)
|
49
|
-
results = tracer.local_values
|
50
|
-
results.to_json
|
51
|
-
end
|
data/kefka.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "kefka"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Reginald Tan"]
|
12
|
-
s.date = "2012-08-
|
12
|
+
s.date = "2012-08-18"
|
13
13
|
s.description = " It traces the execution path of a program and displays the source code of each method call in the callgraph "
|
14
14
|
s.email = "redge.tan@gmail.com"
|
15
15
|
s.executables = ["kefka"]
|
@@ -34,8 +34,9 @@ Gem::Specification.new do |s|
|
|
34
34
|
"public/javascripts/app.js",
|
35
35
|
"public/javascripts/jquery-1.7.2.js",
|
36
36
|
"public/stylesheets/application.css",
|
37
|
-
"
|
38
|
-
"
|
37
|
+
"spec/fixture/sample_a.rb",
|
38
|
+
"spec/kekfa_spec.rb",
|
39
|
+
"spec/spec_helper.rb",
|
39
40
|
"views/index.erb"
|
40
41
|
]
|
41
42
|
s.homepage = "http://github.com/redgetan/kefka"
|
data/lib/kefka.rb
CHANGED
@@ -3,6 +3,7 @@ require 'rgl/adjacency'
|
|
3
3
|
require 'rgl/dot'
|
4
4
|
require 'method_source'
|
5
5
|
|
6
|
+
require 'ripper'
|
6
7
|
require 'forwardable'
|
7
8
|
require 'logger'
|
8
9
|
|
@@ -10,23 +11,24 @@ class Kefka
|
|
10
11
|
|
11
12
|
class Method
|
12
13
|
|
13
|
-
attr_reader :classname, :id, :file, :line
|
14
|
-
attr_accessor :format
|
14
|
+
attr_reader :classname, :id, :file, :line
|
15
|
+
attr_accessor :source, :format
|
15
16
|
|
16
17
|
def initialize(options={})
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
18
|
+
raise ArgumentError, "missing file + line" unless options[:file] && options[:line]
|
19
|
+
@classname = options[:classname]
|
20
|
+
@id = options[:id]
|
21
|
+
@file = options[:file]
|
22
|
+
@start_line = options[:line]
|
23
|
+
@format = options[:format] || :plain
|
22
24
|
end
|
23
25
|
|
24
26
|
def source_location
|
25
|
-
[@file,@
|
27
|
+
[@file,@start_line]
|
26
28
|
end
|
27
29
|
|
28
30
|
def end_line
|
29
|
-
@
|
31
|
+
@start_line + source.lines.count - 1
|
30
32
|
end
|
31
33
|
|
32
34
|
def key
|
@@ -34,7 +36,7 @@ class Kefka
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def contains?(file, line)
|
37
|
-
@file == file && @
|
39
|
+
@file == file && @start_line <= line && end_line > line
|
38
40
|
end
|
39
41
|
|
40
42
|
def source
|
@@ -49,12 +51,18 @@ class Kefka
|
|
49
51
|
def formatted_source
|
50
52
|
if @format == :html
|
51
53
|
CodeRay.scan(source, :ruby)
|
52
|
-
.div(:line_numbers => :table, :line_number_start => @
|
54
|
+
.div(:line_numbers => :table, :line_number_start => @start_line)
|
53
55
|
else
|
54
56
|
source
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
60
|
+
def source_at_line(line)
|
61
|
+
# what if source is not known, ie. eval
|
62
|
+
index = line - @start_line
|
63
|
+
source.lines.take(index + 1)[index]
|
64
|
+
end
|
65
|
+
|
58
66
|
def to_s
|
59
67
|
"#{classname} #{id}"
|
60
68
|
end
|
@@ -63,8 +71,10 @@ class Kefka
|
|
63
71
|
self.key == other.key
|
64
72
|
end
|
65
73
|
|
74
|
+
alias :== :eql?
|
75
|
+
|
66
76
|
def hash
|
67
|
-
[@file,@
|
77
|
+
[@file,@start_line].hash
|
68
78
|
end
|
69
79
|
|
70
80
|
def to_json(*a)
|
@@ -72,7 +82,7 @@ class Kefka
|
|
72
82
|
:classname => @classname,
|
73
83
|
:id => @id,
|
74
84
|
:file => @file,
|
75
|
-
:line => @
|
85
|
+
:line => @start_line,
|
76
86
|
:end_line => end_line,
|
77
87
|
:source => formatted_source
|
78
88
|
}.to_json(*a)
|
@@ -115,35 +125,29 @@ class Kefka
|
|
115
125
|
@logger.level = log_level
|
116
126
|
end
|
117
127
|
|
118
|
-
def
|
119
|
-
|
120
|
-
end
|
128
|
+
def trace(file_path)
|
129
|
+
file = File.open(file_path)
|
121
130
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
131
|
+
thread = Thread.new {
|
132
|
+
@code = file.read
|
133
|
+
#@callstack << create_top_level_method(file_path)
|
134
|
+
eval(@code, TOPLEVEL_BINDING, file.path, 1)
|
135
|
+
}
|
127
136
|
|
128
|
-
|
129
|
-
|
130
|
-
locals.inject({}) do |result,l|
|
131
|
-
val = target.eval(l.to_s)
|
132
|
-
val = deep_copy(val)
|
133
|
-
result.merge!({ l => val })
|
134
|
-
result
|
135
|
-
end
|
137
|
+
thread.set_trace_func method(:trace_handler).to_proc
|
138
|
+
thread.join
|
136
139
|
end
|
137
140
|
|
138
141
|
def disable_event_handlers?
|
139
142
|
!@event_disable.empty?
|
140
143
|
end
|
141
144
|
|
142
|
-
def
|
145
|
+
def trace_handler(event, file, line, id, binding, classname)
|
143
146
|
return if file == __FILE__
|
144
147
|
|
145
148
|
@logger.debug "#{event} - #{file}:#{line} #{classname} #{id}"
|
146
149
|
|
150
|
+
# skip event handling when iseq is happening inside class loading
|
147
151
|
if disable_event_handlers?
|
148
152
|
@event_disable.pop if event == "end"
|
149
153
|
return
|
@@ -167,6 +171,28 @@ class Kefka
|
|
167
171
|
end
|
168
172
|
|
169
173
|
@callstack << method
|
174
|
+
when "line"
|
175
|
+
# skip variables that should not be tracked
|
176
|
+
# 1. anything in current lib (i.e __FILE__)
|
177
|
+
# 2. all local variables that are in TOP LEVEL BINDING before tracing
|
178
|
+
# - but these variables may be overwritten by the traced program,
|
179
|
+
# excluding them would mean not displaying certain relevant
|
180
|
+
# vars in that program
|
181
|
+
#current_method = @callstack.last
|
182
|
+
|
183
|
+
# given current file & line, determine what method I am in
|
184
|
+
|
185
|
+
method = @method_graph.vertices
|
186
|
+
.select { |method| method.contains?(file,line) }
|
187
|
+
.first
|
188
|
+
|
189
|
+
# skip if not in any previously called method
|
190
|
+
if method
|
191
|
+
line_source = method.source_at_line(line)
|
192
|
+
|
193
|
+
iseq_key = [file, line].join(":")
|
194
|
+
@local_values[iseq_key] = get_values_of_locals_from_binding(binding, line_source)
|
195
|
+
end
|
170
196
|
when "return"
|
171
197
|
@callstack.pop
|
172
198
|
else
|
@@ -177,48 +203,48 @@ class Kefka
|
|
177
203
|
Process.kill("KILL", $$)
|
178
204
|
end
|
179
205
|
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
206
|
+
def get_values_of_locals_from_binding(target, line_source)
|
207
|
+
locals = get_locals(target,line_source)
|
208
|
+
locals.inject({}) do |result,l|
|
209
|
+
val = target.eval(l.to_s)
|
210
|
+
val = deep_copy(val)
|
211
|
+
result.merge!({ l => val })
|
212
|
+
result
|
213
|
+
end
|
214
|
+
end
|
188
215
|
|
189
|
-
|
216
|
+
def get_locals(target, line_source)
|
217
|
+
scope_locals = target.eval("local_variables")
|
218
|
+
scope_locals.map! { |local| local.to_s }
|
190
219
|
|
191
|
-
|
192
|
-
@event_disable.pop if event == "end"
|
193
|
-
return
|
194
|
-
end
|
220
|
+
tokens = Ripper.lex(line_source)
|
195
221
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
key = [file, line].join(":")
|
201
|
-
@local_values[key] = get_values_of_locals_from_binding(binding)
|
202
|
-
else
|
203
|
-
# do nothing
|
204
|
-
end
|
205
|
-
end
|
222
|
+
tokens.select! { |token|
|
223
|
+
type = token[1]
|
224
|
+
type == :on_ident || type == :on_ivar || type == :on_cvar
|
225
|
+
}
|
206
226
|
|
207
|
-
|
208
|
-
|
209
|
-
|
227
|
+
possible_line_variables = tokens.map { |token| token[2] }
|
228
|
+
line_variables = scope_locals & possible_line_variables
|
229
|
+
line_variables
|
210
230
|
end
|
211
231
|
|
212
|
-
def
|
213
|
-
|
232
|
+
def deep_copy(val)
|
233
|
+
Marshal.load(Marshal.dump(val))
|
234
|
+
rescue TypeError
|
235
|
+
"_unknown_"
|
236
|
+
end
|
214
237
|
|
215
|
-
thread = Thread.new {
|
216
|
-
@code = file.read
|
217
|
-
eval(@code, TOPLEVEL_BINDING, file.path, 1)
|
218
|
-
}
|
219
238
|
|
220
|
-
|
221
|
-
|
239
|
+
def create_top_level_method(file)
|
240
|
+
method = Method.new(
|
241
|
+
:classname => "Object",
|
242
|
+
:id => "<main>",
|
243
|
+
:file => file,
|
244
|
+
:line => 1
|
245
|
+
)
|
246
|
+
method.source = File.readlines(file).join
|
247
|
+
return method
|
222
248
|
end
|
223
249
|
|
224
250
|
end
|
data/public/javascripts/app.js
CHANGED
@@ -2,27 +2,34 @@ var jqSelectorEscape = function(text) {
|
|
2
2
|
return text.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$&");
|
3
3
|
};
|
4
4
|
|
5
|
-
var
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
var outputTrace = function(data) {
|
6
|
+
displayInput(data.input);
|
7
|
+
displayGraphiz(data.is_graphviz_installed);
|
8
|
+
createCodeBubbles(data.graph);
|
9
|
+
displayLocals(data.locals);
|
10
|
+
}
|
11
|
+
|
12
|
+
var displayInput = function(input) {
|
10
13
|
$("div#input").last().append(input);
|
14
|
+
};
|
11
15
|
|
12
|
-
|
16
|
+
var displayGraphiz = function(is_graphviz_installed) {
|
17
|
+
if (is_graphviz_installed) {
|
13
18
|
$("div#callGraph").last().append("<img src='graph.png'/>");
|
14
19
|
} else {
|
15
20
|
$("div#callGraph").last().append("Graphviz visualization not Available. Install Graphviz to enable it.");
|
16
21
|
}
|
22
|
+
};
|
17
23
|
|
18
|
-
|
24
|
+
var createCodeBubbles = function(graph) {
|
25
|
+
console.log(graph);
|
19
26
|
|
20
27
|
var xPos = 0;
|
21
28
|
var bubbleDiv, $bubble, $code,
|
22
29
|
key, header,
|
23
30
|
lineCount, column;
|
24
31
|
|
25
|
-
var methods =
|
32
|
+
var methods = graph.vertices;
|
26
33
|
|
27
34
|
for (var i = 0; i < methods.length; i++ )
|
28
35
|
{
|
@@ -68,23 +75,21 @@ var createCodeBubbles = function(data) {
|
|
68
75
|
}
|
69
76
|
};
|
70
77
|
|
71
|
-
var
|
72
|
-
console.log(
|
73
|
-
|
74
|
-
var localValues = data;
|
78
|
+
var displayLocals = function(locals) {
|
79
|
+
console.log(locals);
|
75
80
|
|
76
81
|
var values, keys, file, line, $bubbles, $line;
|
77
82
|
|
78
|
-
for (var key in
|
83
|
+
for (var key in locals)
|
79
84
|
{
|
80
85
|
values = "";
|
81
86
|
|
82
|
-
for (var local in
|
87
|
+
for (var local in locals[key])
|
83
88
|
{
|
84
89
|
values += "\t";
|
85
90
|
values += local;
|
86
91
|
values += ": ";
|
87
|
-
values +=
|
92
|
+
values += locals[key][local];
|
88
93
|
}
|
89
94
|
|
90
95
|
keys = key.split(":");
|
@@ -102,20 +107,11 @@ var displayLocalValues = function(data) {
|
|
102
107
|
|
103
108
|
$(document).ready(function(){
|
104
109
|
|
105
|
-
var getLocalValues = function() {
|
106
|
-
$.ajax({
|
107
|
-
url: "/locals",
|
108
|
-
dataType: "json",
|
109
|
-
success: displayLocalValues
|
110
|
-
});
|
111
|
-
};
|
112
|
-
|
113
110
|
$.ajax({
|
114
111
|
url: "/callgraph",
|
115
112
|
dataType: "json",
|
116
|
-
success:
|
113
|
+
success: outputTrace
|
117
114
|
});
|
118
115
|
|
119
|
-
|
120
116
|
});
|
121
117
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'date'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
# class with function calls inside class loading
|
6
|
+
class Book
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@store, :first, :last
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@store = [1,2]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []
|
15
|
+
"array accessor"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# class with variable of IO instance
|
20
|
+
class Displayer
|
21
|
+
def initialize(out)
|
22
|
+
@out = out
|
23
|
+
end
|
24
|
+
|
25
|
+
def puts(val)
|
26
|
+
@out.puts val
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def hello
|
31
|
+
shout("holdon")
|
32
|
+
|
33
|
+
num = init
|
34
|
+
x = 4
|
35
|
+
x = x * 2
|
36
|
+
b = Book.new
|
37
|
+
|
38
|
+
shout("yeah")
|
39
|
+
|
40
|
+
if b.first
|
41
|
+
puts "lol"
|
42
|
+
end
|
43
|
+
|
44
|
+
c = b[]
|
45
|
+
display = Displayer.new($stdout)
|
46
|
+
display.puts "Hello #{num}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def shout(text)
|
50
|
+
a = text
|
51
|
+
a.split("").map(&:ord)
|
52
|
+
end
|
53
|
+
|
54
|
+
def init
|
55
|
+
a = 1 + rand(9)
|
56
|
+
(0..3).each do |x|
|
57
|
+
a = a + time_diff + x
|
58
|
+
end
|
59
|
+
a
|
60
|
+
end
|
61
|
+
|
62
|
+
# function called inside a loop
|
63
|
+
def time_diff
|
64
|
+
before = Time.now
|
65
|
+
after = Time.now
|
66
|
+
(after - before).to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
hello
|
70
|
+
|
71
|
+
# stdlib function call (network)
|
72
|
+
Net::HTTP.get_response(URI.parse("http://www.twitter.com")).body
|
73
|
+
|
data/spec/kekfa_spec.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
def trace(source)
|
5
|
+
file = File.open(StringIO.new(source))
|
6
|
+
tracer = Kefka::Tracer.new
|
7
|
+
tracer.trace(file)
|
8
|
+
tracer
|
9
|
+
end
|
10
|
+
|
11
|
+
# start with user story
|
12
|
+
# understanding URI.parse
|
13
|
+
#
|
14
|
+
# key features
|
15
|
+
# reduce clutter
|
16
|
+
# only show variable values when line contains that var
|
17
|
+
# variables include instance/class/local variable
|
18
|
+
# on_ident (do not include method, i.e. if preceded by period)
|
19
|
+
# on_ivar
|
20
|
+
# on_cvar
|
21
|
+
# all variable values within a method should replace their variable counterparts
|
22
|
+
# when mouse hovers over a variable in that method
|
23
|
+
# collapsible
|
24
|
+
# only show bubbles at toplevel (main entry point of program), others are hidden
|
25
|
+
# each bubble will have a # children counter which specifies how many bubbles it has down the chain
|
26
|
+
# bubble children count = sum of each methodcall children count
|
27
|
+
# expanding a bubble
|
28
|
+
# can be a specific method call
|
29
|
+
# can be all method calls within bubble
|
30
|
+
# immediate children becomes visible to view
|
31
|
+
#
|
32
|
+
#
|
33
|
+
#
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
describe "Kefka::Inspector" do
|
38
|
+
|
39
|
+
# file = File.open(file_path)
|
40
|
+
# puts "\n#{e.class}: #{e.message} \n from #{e.backtrace.join("\n")}"
|
41
|
+
#
|
42
|
+
context "when current line contains any local_variables" do
|
43
|
+
it "should evaluate the local_variables for that line" do
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when current line does not contain any local_variables" do
|
49
|
+
it "should not do anything" do
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a method_call, line_no, N-th visit
|
56
|
+
# line_locals.should == { :x => 4, y => "reg" }
|
57
|
+
#
|
58
|
+
#
|
59
|
+
# Future: Given a method, determine code paths
|
60
|
+
# given a vertex, show all adjacency lists
|
61
|
+
#
|
62
|
+
#
|
63
|
+
#
|
64
|
+
#
|
65
|
+
#
|
66
|
+
# determine what exactly the output of callgraph is gonna be
|
67
|
+
# could reperesent just methodname callgraph
|
68
|
+
# |----|
|
69
|
+
# | |
|
70
|
+
# | |
|
71
|
+
# |----|
|
72
|
+
#
|
73
|
+
# Method
|
74
|
+
# has many locals (array)
|
75
|
+
#
|
76
|
+
# MethodLocal
|
77
|
+
# has many line locals
|
78
|
+
#
|
79
|
+
# callstack: call1<method:line> -> call2
|
80
|
+
# iteration: 6
|
81
|
+
# line_locals: [line_local, line_local, ..]
|
82
|
+
#
|
83
|
+
# LineLocal
|
84
|
+
# iteration: 3 #{based on length of data array
|
85
|
+
# data: { :x => 1, :y => 4}
|
86
|
+
#
|
87
|
+
# Call
|
88
|
+
# method:
|
89
|
+
# line:
|
90
|
+
|
91
|
+
#
|
92
|
+
# Use case
|
93
|
+
# I'm at a method bubble
|
94
|
+
#
|
95
|
+
# i wanna see all the execution paths that have gone through this bubble
|
96
|
+
# i should be able to filter incoming paths by their caller/callstack/n-th iteration
|
97
|
+
# i should be able to filter a methods locals by their caller/callstack/n-th iteration
|
98
|
+
#
|
99
|
+
#
|
100
|
+
# 3 containers
|
101
|
+
# codegraph - graph of what method source called what method source (vertices)
|
102
|
+
# callgraph - graph of what line of method source called what method source (links)
|
103
|
+
#
|
104
|
+
# power - line value
|
105
|
+
#
|
106
|
+
describe "Kekfa::Callgraph" do
|
107
|
+
# holds methodcalls (entity)
|
108
|
+
|
109
|
+
# {
|
110
|
+
# vertices => []
|
111
|
+
# }
|
112
|
+
it "#to_json" do
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "edges" do
|
117
|
+
# source is a methodcall
|
118
|
+
# target is a methodcall
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "links" do
|
122
|
+
# source is a line
|
123
|
+
# line - identified by methodcall + lineno
|
124
|
+
# target is a methodcall
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "Kefka::Method" do
|
130
|
+
describe "#source_location" do
|
131
|
+
it "should be an array containing file and line where method is defined" do
|
132
|
+
file, line = "filename.rb", 20
|
133
|
+
method = Method.new(:file => file, :line => line)
|
134
|
+
method.source_location.should == [file, line]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Callstack
|
141
|
+
#
|
142
|
+
# "call" -> push method, line into stack if method && line
|
143
|
+
# "call" -> store curr_method
|
144
|
+
# "line" -> store curr_line
|
145
|
+
|
146
|
+
describe "Kefka::Call" do
|
147
|
+
it "stores current method" do
|
148
|
+
|
149
|
+
end
|
150
|
+
it "stores current line" do
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
#
|
157
|
+
describe "Kekfa::Tracer" do
|
158
|
+
|
159
|
+
describe "Locals" do
|
160
|
+
|
161
|
+
describe "line entered once" do
|
162
|
+
before do
|
163
|
+
@source = <<-RUBY
|
164
|
+
def hello
|
165
|
+
x = 1
|
166
|
+
x += 2
|
167
|
+
end
|
168
|
+
|
169
|
+
hello
|
170
|
+
RUBY
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should able to get locals value" do
|
174
|
+
locals = trace(@source).locals
|
175
|
+
|
176
|
+
locals = locals.for_method(:hello).iter(0)
|
177
|
+
|
178
|
+
locals.for_line(0).iter(0)[:x].should == nil
|
179
|
+
locals.for_line(1).iter(0)[:x].should == 1
|
180
|
+
locals.for_line(2).iter(0)[:x].should == 3
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "line entered multiple times" do
|
185
|
+
|
186
|
+
describe "iteration" do
|
187
|
+
before do
|
188
|
+
@source = <<-RUBY
|
189
|
+
def hello
|
190
|
+
x = 1
|
191
|
+
(0..3).each do
|
192
|
+
x += 2
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
hello
|
197
|
+
RUBY
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should able to get locals value" do
|
201
|
+
locals = trace(@source).locals
|
202
|
+
|
203
|
+
locals = locals.for_method(:hello).iter(0)
|
204
|
+
|
205
|
+
locals.for_line(2).iter(0)[:x].should == 3
|
206
|
+
locals.for_line(2).iter(1)[:x].should == 5
|
207
|
+
locals.for_line(2).iter(2)[:x].should == 7
|
208
|
+
locals.for_line(2).iter(3)[:x].should == 9
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "different call_source same method + diff line" do
|
213
|
+
before do
|
214
|
+
@source = <<-RUBY
|
215
|
+
def hello(x)
|
216
|
+
y = x
|
217
|
+
end
|
218
|
+
|
219
|
+
def day
|
220
|
+
hello(3)
|
221
|
+
end
|
222
|
+
|
223
|
+
def night
|
224
|
+
hello(8)
|
225
|
+
end
|
226
|
+
|
227
|
+
day
|
228
|
+
night
|
229
|
+
RUBY
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should able to get locals value" do
|
233
|
+
locals = trace(@source).locals
|
234
|
+
|
235
|
+
locals = locals.for_method(:hello).iter(0)
|
236
|
+
locals.for_line(0).iter(0)[:x].should == 3
|
237
|
+
line_locals[0][0].var(:x).value.should == 3
|
238
|
+
|
239
|
+
locals = locals.for_method(:hello).iter(1)
|
240
|
+
locals.for_line(0).iter(0)[:x].should == 8
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "different call_source diff method + diff line" do
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "recursion" do
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
describe "locals value storage" do
|
256
|
+
describe "line entered once" do
|
257
|
+
# sample_a.rb:30 -> x should be nil
|
258
|
+
# sample_a.rb:31 -> x should be 4
|
259
|
+
# sample_a.rb:32 -> x should be 8
|
260
|
+
it "should show variable output" do
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe "line entered more than once" do
|
266
|
+
# sample_a.rb:46:0 -> x should be nil
|
267
|
+
# sample_a.rb:46:1 -> x should be 4
|
268
|
+
# sample_a.rb:46:2 -> x should be 8
|
269
|
+
# sample_a.rb:46 -> array
|
270
|
+
# array index representh the N-th iteration
|
271
|
+
# idx 0 -> LocalValue
|
272
|
+
# keys -> x,a
|
273
|
+
# values ->
|
274
|
+
#
|
275
|
+
it "should show variable output" do
|
276
|
+
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
# should store locals_value_table
|
281
|
+
it "should store locals_value_table" do
|
282
|
+
end
|
283
|
+
#
|
284
|
+
end
|
285
|
+
|
286
|
+
#class Inspector
|
287
|
+
#def initialize
|
288
|
+
|
289
|
+
#end
|
290
|
+
|
291
|
+
#def
|
292
|
+
|
293
|
+
#end
|
294
|
+
#end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler'
|
3
|
+
|
3
4
|
begin
|
4
5
|
Bundler.setup(:default, :development)
|
5
6
|
rescue Bundler::BundlerError => e
|
@@ -7,12 +8,9 @@ rescue Bundler::BundlerError => e
|
|
7
8
|
$stderr.puts "Run `bundle install` to install missing gems"
|
8
9
|
exit e.status_code
|
9
10
|
end
|
10
|
-
require 'test/unit'
|
11
|
-
require 'shoulda'
|
12
11
|
|
13
12
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
-
|
13
|
+
|
15
14
|
require 'kefka'
|
16
15
|
|
17
|
-
|
18
|
-
end
|
16
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kefka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: yajl-ruby
|
16
|
-
requirement: &
|
16
|
+
requirement: &70342061670580 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70342061670580
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: coderay
|
27
|
-
requirement: &
|
27
|
+
requirement: &70342061668920 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70342061668920
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sinatra
|
38
|
-
requirement: &
|
38
|
+
requirement: &70342061667860 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70342061667860
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: method_source
|
49
|
-
requirement: &
|
49
|
+
requirement: &70342061665980 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70342061665980
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rgl
|
60
|
-
requirement: &
|
60
|
+
requirement: &70342061665180 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70342061665180
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry
|
71
|
-
requirement: &
|
71
|
+
requirement: &70342061656240 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70342061656240
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: pry-doc
|
82
|
-
requirement: &
|
82
|
+
requirement: &70342061655300 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70342061655300
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: rspec
|
93
|
-
requirement: &
|
93
|
+
requirement: &70342061654480 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70342061654480
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: jeweler
|
104
|
-
requirement: &
|
104
|
+
requirement: &70342061653480 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70342061653480
|
113
113
|
description: ! ' It traces the execution path of a program and displays the source
|
114
114
|
code of each method call in the callgraph '
|
115
115
|
email: redge.tan@gmail.com
|
@@ -136,8 +136,9 @@ files:
|
|
136
136
|
- public/javascripts/app.js
|
137
137
|
- public/javascripts/jquery-1.7.2.js
|
138
138
|
- public/stylesheets/application.css
|
139
|
-
-
|
140
|
-
-
|
139
|
+
- spec/fixture/sample_a.rb
|
140
|
+
- spec/kekfa_spec.rb
|
141
|
+
- spec/spec_helper.rb
|
141
142
|
- views/index.erb
|
142
143
|
- TODO
|
143
144
|
homepage: http://github.com/redgetan/kefka
|
@@ -155,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
156
|
version: '0'
|
156
157
|
segments:
|
157
158
|
- 0
|
158
|
-
hash:
|
159
|
+
hash: 3078071895690683820
|
159
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
161
|
none: false
|
161
162
|
requirements:
|