rkid 0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -1,5 +1,58 @@
1
1
  Rkid is Rcov In Database
2
2
 
3
- Until there is better packaging (see the todo), customize the following line for your needs:
3
+ WHAT
4
4
 
5
- ruby -S bin/rkid "/opt/local/lib/ruby/gems/1.8/gems/rspec-1.1.2/bin/spec" -- "/Users/nkallen/Sites/arel/spec/active_relation/unit/predicates/binary_spec.rb"
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
@@ -19,6 +19,5 @@ Hoe.new('Rkid', Rkid::VERSION) do |p|
19
19
  p.email = 'nick@pivotallabs.com'
20
20
  p.summary = "Rcov in Database"
21
21
  p.url = ""
22
- p.extra_deps << ['rspec']
23
- p.extra_deps << ['rcov']
22
+ p.extra_deps += [['rspec'], ['rcov'], ['activerecord']]
24
23
  end
data/TODO CHANGED
@@ -1,4 +1,3 @@
1
- 1. Create rake task factory a la Rspec/HOE and/or patch Rspec's factory
1
+ 1. Improve performance by moving code to C. Ignore files are important in the data collecting phase.
2
2
  2. Build primitive browser
3
- 3. Improve performance by moving code to C
4
3
  4. Fancify browser
data/bin/rkid CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rubygems'
2
2
  require 'rkid'
3
- Rkid.env = 'production'
4
3
 
5
4
  $ORIGINAL_ARGV = ARGV.clone
6
5
  if idx = ARGV.index("--")
@@ -1,7 +1,7 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
3
  module Rkid
4
- VERSION = '0.1'
4
+ VERSION = '0.1.1'
5
5
  end
6
6
 
7
7
  require 'rubygems'
@@ -1,18 +1,8 @@
1
1
  module Rkid
2
- mattr_accessor :root, :env, :callsite_analyzer, :coverage_analyzer
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
- def self.prepare_connection_to_database
32
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => 'rkid.db')
33
- load ::File.join(root, 'db/schema.rb')
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
- def self.analyze_callsite(callsite_analyzer)
37
- total = callsite_analyzer.analyzed_classes.size
38
- callsite_analyzer.analyzed_classes.each_with_index do |klass_name, i|
39
- puts "Processing class '#{klass_name}', #{i+1} of #{total}"
40
- klass = nil
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
- callsite_analyzer.methods_for_class(klass_name).each do |method_name|
43
- defsite = callsite_analyzer.defsite(klass_method = klass_name + "#" + method_name)
44
- next if IGNORE_FILES.any? { |pattern| defsite.file =~ pattern }
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
- klass ||= Klass.create('name' => klass_name)
47
- defsite_file = File.create('name' => defsite.file)
48
- defsite_line = Line.create('file_id' => defsite_file.id, 'number' => defsite.line)
49
- method = Method.create(
50
- 'klass_id' => klass.id,
51
- 'name' => method_name,
52
- 'defsite_id' => defsite_line.id
53
- )
54
- callsite_analyzer.callsites(klass_method).each do |site, count|
55
- callsite = Callsite.create('method_id' => method.id, 'count' => count)
56
- site.backtrace.each_with_index do |frame, i|
57
- file_name = file_name = frame[2]
58
- next if IGNORE_FILES.any? { |pattern| file_name =~ pattern }
59
-
60
- file = File.create('name' => file_name)
61
- line = Line.create('file_id' => file.id, 'number' => frame[3])
62
- Frame.create('callsite_id' => callsite.id, 'line_id' => line.id, 'level' => i)
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
- def self.analyze_coverage(coverage_analyzer)
70
- coverage_analyzer.analyzed_files.each do |file_name|
71
- next if IGNORE_FILES.any? { |pattern| file_name =~ pattern }
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
- lines, marked, count = coverage_analyzer.data(file_name)
74
- lines.each_with_index do |body, i|
75
- file = File.create('name' => file_name)
76
- line = Line.create('file_id' => file.id, 'number' => i, 'body' => body, 'covered' => marked[i], 'times_called' => count[i])
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
@@ -4,7 +4,7 @@ module Rkid
4
4
  ActiveRecord::Base.connection.raw_connection
5
5
  end
6
6
 
7
- def insert(attributes)
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.except('covered').merge('id' => line.id)
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
@@ -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 == 8
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: "0.1"
7
- date: 2008-03-31 00:00:00 -07:00
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: