rkid 0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.txt +55 -2
- data/Rakefile +1 -2
- data/TODO +1 -2
- data/bin/rkid +0 -1
- data/lib/rkid.rb +1 -1
- data/lib/rkid/analyzer.rb +70 -48
- data/lib/rkid/models.rb +4 -21
- data/rkid.db +0 -0
- data/spec/rkid/analyzer_spec.rb +1 -2
- metadata +11 -2
data/README.txt
CHANGED
@@ -1,5 +1,58 @@
|
|
1
1
|
Rkid is Rcov In Database
|
2
2
|
|
3
|
-
|
3
|
+
WHAT
|
4
4
|
|
5
|
-
|
5
|
+
Rkid is a tool to surface RCov coverage and callsite data in a relational database. It also provides a friendly ActiveRecord model so you can easily query the data.
|
6
|
+
|
7
|
+
WHY
|
8
|
+
|
9
|
+
I want to ask questions of my codebase:
|
10
|
+
|
11
|
+
* What classes are abstract?
|
12
|
+
* Who calls method #x?
|
13
|
+
* What coverage does this little unit test have?
|
14
|
+
* What classes of objects are passed into #x?
|
15
|
+
* What classes does class A collaborate with?
|
16
|
+
* which collaborators are manufactured by class A?
|
17
|
+
* which collaborators in injected into class A?
|
18
|
+
* what implicit interfaces/protocols does A rely on?
|
19
|
+
* What implicit interfaces exist in my system (e.g., methods in different class hierarchies invoked from the same callsites)
|
20
|
+
* etc.
|
21
|
+
|
22
|
+
My ultimate goal is to produce a rich code browser along the lines of the Smalltalk browsers. This should also have wiki-like functionality for adding marginalia to code.
|
23
|
+
|
24
|
+
WARNING
|
25
|
+
|
26
|
+
Rkid provides a rake task to run your spec suite with rcov turned on. Unlike *normal* rake spec:rcov, I turn on the callsite information. This is the data the illustrates, e.g., who calls what methods; what lines of code one individual test covers; and so forth.
|
27
|
+
|
28
|
+
Please note that Rkid is slow. But don't complain: I've optimized from 16 hours down to 1.5 minutes on a sample project. But don't get too excited: because the sample project normally runs 300 specs in 3 seconds. Rkid is currently too slow to run on a Rails app. Don't even bother trying unless you're developing on a Cray.
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
|
32
|
+
* Rspec-based test suite
|
33
|
+
* Sqlite3
|
34
|
+
|
35
|
+
USAGE
|
36
|
+
|
37
|
+
Install the gem from rubyforge
|
38
|
+
|
39
|
+
gem install rkid
|
40
|
+
|
41
|
+
Add these lines to your rakefile:
|
42
|
+
|
43
|
+
require 'rkid/rake/task'
|
44
|
+
Rkid::Task.new
|
45
|
+
|
46
|
+
Then, execute this on the command line:
|
47
|
+
|
48
|
+
rake rkid
|
49
|
+
|
50
|
+
Finally, Query the data like this:
|
51
|
+
|
52
|
+
require 'rubygems'
|
53
|
+
require 'activerecord'
|
54
|
+
require 'rkid'
|
55
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => 'rkid.db')
|
56
|
+
Rkid::Klass.find(:all)
|
57
|
+
|
58
|
+
And so forth.
|
data/Rakefile
CHANGED
data/TODO
CHANGED
data/bin/rkid
CHANGED
data/lib/rkid.rb
CHANGED
data/lib/rkid/analyzer.rb
CHANGED
@@ -1,18 +1,8 @@
|
|
1
1
|
module Rkid
|
2
|
-
mattr_accessor :root, :
|
3
|
-
|
4
|
-
IGNORE_FILES = [
|
5
|
-
/\A#{Regexp.escape(Pathname.new(Config::CONFIG["libdir"]).cleanpath.to_s)}/,
|
6
|
-
/\btc_[^.]*.rb/,
|
7
|
-
/\bgems\//,
|
8
|
-
/\bvendor\//,
|
9
|
-
/\A#{Regexp.escape(__FILE__)}\z/
|
10
|
-
]
|
2
|
+
mattr_accessor :root, :callsite_analyzer, :coverage_analyzer
|
11
3
|
|
12
4
|
def self.analyze(&block)
|
13
|
-
prepare
|
14
|
-
yield
|
15
|
-
report
|
5
|
+
prepare; yield; report
|
16
6
|
end
|
17
7
|
|
18
8
|
def self.prepare
|
@@ -24,57 +14,89 @@ module Rkid
|
|
24
14
|
callsite_analyzer.remove_hook; coverage_analyzer.remove_hook
|
25
15
|
prepare_connection_to_database
|
26
16
|
analyze_callsite(callsite_analyzer); analyze_coverage(coverage_analyzer)
|
17
|
+
close_connection_to_database
|
27
18
|
end
|
28
19
|
|
29
20
|
private
|
30
21
|
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
module Database
|
23
|
+
def prepare_connection_to_database
|
24
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => 'rkid.db')
|
25
|
+
load ::File.join(root, 'db/schema.rb')
|
26
|
+
ActiveRecord::Base.connection.raw_connection.transaction
|
27
|
+
end
|
28
|
+
|
29
|
+
def close_connection_to_database
|
30
|
+
ActiveRecord::Base.connection.raw_connection.commit
|
31
|
+
end
|
34
32
|
end
|
33
|
+
extend Database
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
module Analyze
|
36
|
+
IGNORE_FILES = [
|
37
|
+
/\A#{Regexp.escape(Pathname.new(Config::CONFIG["libdir"]).cleanpath.to_s)}/,
|
38
|
+
/\btc_[^.]*.rb/,
|
39
|
+
/\bgems\//,
|
40
|
+
/\bvendor\//,
|
41
|
+
/\A#{Regexp.escape(__FILE__)}\z/
|
42
|
+
]
|
43
|
+
|
44
|
+
def analyze_callsite(callsite_analyzer)
|
45
|
+
total = callsite_analyzer.analyzed_classes.size
|
46
|
+
callsite_analyzer.analyzed_classes.each_with_index do |klass_name, i|
|
47
|
+
puts "Processing class '#{klass_name}', #{i+1} of #{total}"
|
48
|
+
klass = nil
|
41
49
|
|
42
|
-
|
43
|
-
|
44
|
-
|
50
|
+
callsite_analyzer.methods_for_class(klass_name).each do |method_name|
|
51
|
+
defsite = callsite_analyzer.defsite(klass_method = klass_name + "#" + method_name)
|
52
|
+
next if IGNORE_FILES.any? { |pattern| defsite.file =~ pattern }
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
klass ||= Klass.create('name' => klass_name)
|
55
|
+
line = lines[[defsite.file, defsite.line]]
|
56
|
+
method = Method.create(
|
57
|
+
'klass_id' => klass.id,
|
58
|
+
'name' => method_name,
|
59
|
+
'defsite_id' => line.id
|
60
|
+
)
|
61
|
+
callsite_analyzer.callsites(klass_method).each do |site, count|
|
62
|
+
callsite = nil
|
63
|
+
site.backtrace.each_with_index do |frame, i|
|
64
|
+
file_name = frame[2]
|
65
|
+
next if IGNORE_FILES.any? { |pattern| file_name =~ pattern }
|
66
|
+
|
67
|
+
callsite ||= Callsite.create('method_id' => method.id, 'count' => count)
|
68
|
+
line = lines[[file_name, number = frame[3]]]
|
69
|
+
Frame.create('callsite_id' => callsite.id, 'line_id' => line.id, 'level' => i)
|
70
|
+
end
|
63
71
|
end
|
64
72
|
end
|
65
73
|
end
|
66
74
|
end
|
67
|
-
end
|
68
75
|
|
69
|
-
|
70
|
-
|
71
|
-
|
76
|
+
def analyze_coverage(coverage_analyzer)
|
77
|
+
coverage_analyzer.analyzed_files.each do |file_name|
|
78
|
+
next if IGNORE_FILES.any? { |pattern| file_name =~ pattern }
|
72
79
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
80
|
+
lines_in_file, marked, count = coverage_analyzer.data(file_name)
|
81
|
+
lines_in_file.each_with_index do |body, i|
|
82
|
+
Line.update(lines[[file_name, i]], 'body' => body, 'covered' => marked[i], 'times_called' => count[i])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def lines
|
90
|
+
@lines ||= Hash.new do |h, (file_name, number)|
|
91
|
+
h[[file_name, number]] = Line.create('file_id' => files[file_name].id, 'number' => number)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def files
|
96
|
+
@files ||= Hash.new do |h, file_name|
|
97
|
+
h[file_name] = File.create('name' => file_name)
|
77
98
|
end
|
78
99
|
end
|
79
100
|
end
|
101
|
+
extend Analyze
|
80
102
|
end
|
data/lib/rkid/models.rb
CHANGED
@@ -4,7 +4,7 @@ module Rkid
|
|
4
4
|
ActiveRecord::Base.connection.raw_connection
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def create(attributes)
|
8
8
|
insert = db.prepare <<-SQL
|
9
9
|
INSERT INTO #{table_name}
|
10
10
|
(#{attributes.keys.join(', ')}) VALUES (#{attributes.keys.collect { |c| ":#{c}" }.join(', ')})
|
@@ -12,24 +12,12 @@ module Rkid
|
|
12
12
|
insert.execute attributes
|
13
13
|
instantiate(attributes.merge('id' => db.last_insert_row_id))
|
14
14
|
end
|
15
|
-
|
16
|
-
def create(attributes)
|
17
|
-
begin
|
18
|
-
insert(attributes)
|
19
|
-
rescue SQLite3::SQLException
|
20
|
-
update(attributes)
|
21
|
-
end
|
22
|
-
end
|
23
15
|
end
|
24
16
|
|
25
17
|
class Klass < ::ActiveRecord::Base
|
26
18
|
extend FastCreate
|
27
19
|
|
28
20
|
has_many :methods, :class_name => 'Method', :dependent => :destroy
|
29
|
-
|
30
|
-
def self.update(attributes)
|
31
|
-
find_by_name(attributes['name'])
|
32
|
-
end
|
33
21
|
end
|
34
22
|
|
35
23
|
class Method < ::ActiveRecord::Base
|
@@ -39,10 +27,6 @@ module Rkid
|
|
39
27
|
has_many :callsites, :dependent => :destroy
|
40
28
|
has_many :lines, :dependent => :destroy
|
41
29
|
belongs_to :defsite, :class_name => 'Line'
|
42
|
-
|
43
|
-
def self.update(attributes)
|
44
|
-
find_by_name_and_klass_id(attributes['name'], attributes['klass_id'])
|
45
|
-
end
|
46
30
|
end
|
47
31
|
|
48
32
|
class File < ::ActiveRecord::Base
|
@@ -68,7 +52,6 @@ module Rkid
|
|
68
52
|
|
69
53
|
belongs_to :callsite
|
70
54
|
belongs_to :line
|
71
|
-
belongs_to :callsite
|
72
55
|
end
|
73
56
|
|
74
57
|
class Line < ::ActiveRecord::Base
|
@@ -77,8 +60,7 @@ module Rkid
|
|
77
60
|
belongs_to :method
|
78
61
|
belongs_to :file
|
79
62
|
|
80
|
-
def self.update(attributes)
|
81
|
-
line = find_by_number_and_file_id(attributes['number'], attributes['file_id'])
|
63
|
+
def self.update(line, attributes)
|
82
64
|
assignments = attributes.keys.collect do |key|
|
83
65
|
"#{key} = :#{key}"
|
84
66
|
end.join ', '
|
@@ -87,7 +69,8 @@ module Rkid
|
|
87
69
|
#{assignments}
|
88
70
|
WHERE id = :id
|
89
71
|
SQL
|
90
|
-
update.execute attributes.
|
72
|
+
update.execute attributes.merge('id' => line.id)
|
73
|
+
line.attributes = attributes
|
91
74
|
line
|
92
75
|
end
|
93
76
|
end
|
data/rkid.db
CHANGED
Binary file
|
data/spec/rkid/analyzer_spec.rb
CHANGED
@@ -4,7 +4,6 @@ describe Rkid do
|
|
4
4
|
|
5
5
|
before(:all) do
|
6
6
|
require 'spec/fixtures/test_class.rb'
|
7
|
-
Rkid.env = 'test'
|
8
7
|
Rkid.analyze { TestedClass.new.tested_method }
|
9
8
|
end
|
10
9
|
|
@@ -23,7 +22,7 @@ describe Rkid do
|
|
23
22
|
method = Rkid::Klass.find_by_name('TestedClass').methods.find_by_name('tested_method')
|
24
23
|
callsite = method.callsites.first
|
25
24
|
callsite.count.should == 1
|
26
|
-
callsite.frames.first.line.number.should ==
|
25
|
+
callsite.frames.first.line.number.should == File.open(__FILE__).readlines.index(" Rkid.analyze { TestedClass.new.tested_method }\n") + 1
|
27
26
|
callsite.frames.first.line.file.name.should == "./spec/rkid/analyzer_spec.rb"
|
28
27
|
end
|
29
28
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rkid
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
7
|
-
date: 2008-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2008-04-05 00:00:00 -07:00
|
8
8
|
summary: Rcov in Database
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -79,6 +79,15 @@ dependencies:
|
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: 0.0.0
|
81
81
|
version:
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: activerecord
|
84
|
+
version_requirement:
|
85
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.0.0
|
90
|
+
version:
|
82
91
|
- !ruby/object:Gem::Dependency
|
83
92
|
name: hoe
|
84
93
|
version_requirement:
|