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 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: