rkid 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ Manifest.txt
2
+ README.txt
3
+ Rakefile
4
+ TODO
5
+ bin/rkid
6
+ db/schema.rb
7
+ lib/rkid.rb
8
+ lib/rkid/analyzer.rb
9
+ lib/rkid/models.rb
10
+ lib/rkid/rake/task.rb
11
+ rkid.db
12
+ spec/fixtures/test_class.rb
13
+ spec/rkid/analyzer_spec.rb
14
+ spec/rkid/sample.db
15
+ spec/spec_helper.rb
16
+ test.db
17
+ test.html
@@ -0,0 +1,5 @@
1
+ Rkid is Rcov In Database
2
+
3
+ Until there is better packaging (see the todo), customize the following line for your needs:
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"
@@ -0,0 +1,24 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/lib")
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'hoe'
6
+ require 'activerecord'
7
+ require 'spec/rake/spectask'
8
+ require 'rkid'
9
+ require 'rkid/rake/task'
10
+
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ Hoe.new('Rkid', Rkid::VERSION) do |p|
16
+ p.name = "rkid"
17
+ p.author = "Nick Kallen"
18
+ p.description = "Rcov in Database"
19
+ p.email = 'nick@pivotallabs.com'
20
+ p.summary = "Rcov in Database"
21
+ p.url = ""
22
+ p.extra_deps << ['rspec']
23
+ p.extra_deps << ['rcov']
24
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ 1. Create rake task factory a la Rspec/HOE and/or patch Rspec's factory
2
+ 2. Build primitive browser
3
+ 3. Improve performance by moving code to C
4
+ 4. Fancify browser
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'rkid'
3
+ Rkid.env = 'production'
4
+
5
+ $ORIGINAL_ARGV = ARGV.clone
6
+ if idx = ARGV.index("--")
7
+ extra_args = ARGV[idx+1..-1]
8
+ ARGV.replace(ARGV[0,idx])
9
+ else
10
+ extra_args = []
11
+ end
12
+
13
+ at_exit {
14
+ Rkid::report
15
+ }
16
+
17
+ pending_scripts = ARGV.clone
18
+ ARGV.replace extra_args
19
+ until pending_scripts.empty?
20
+ prog = pending_scripts.shift
21
+ Rkid::prepare
22
+ load prog
23
+ end
@@ -0,0 +1,34 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :klasses, :force => true do |t|
3
+ t.string :name
4
+ end
5
+ add_index :klasses, :name, :unique => true
6
+
7
+ create_table :methods, :force => true do |t|
8
+ t.string :name
9
+ t.integer :klass_id, :defsite_id
10
+ end
11
+ add_index :methods, [:klass_id, :name, :defsite_id], :unique => true
12
+
13
+ create_table :files, :force => true do |t|
14
+ t.string :name
15
+ end
16
+ add_index :files, [:name], :unique => true
17
+
18
+ create_table :callsites, :force => true do |t|
19
+ t.integer :method_id, :count
20
+ end
21
+
22
+ create_table :frames, :force => true do |t|
23
+ t.integer :line_id, :callsite_id, :level
24
+ end
25
+ add_index :frames, [:line_id, :callsite_id, :level], :unique => true
26
+
27
+ create_table :lines, :force => true do |t|
28
+ t.integer :times_called, :method_id, :file_id, :number
29
+ t.boolean :covered
30
+ t.string :body
31
+ end
32
+ add_index :lines, [:file_id, :number], :unique => true
33
+
34
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ module Rkid
4
+ VERSION = '0.1'
5
+ end
6
+
7
+ require 'rubygems'
8
+ require 'activesupport'
9
+ require 'activerecord'
10
+ require 'rcov'
11
+ require 'sqlite3'
12
+ require 'rbconfig'
13
+
14
+ require 'rkid/analyzer'
15
+ require 'rkid/models'
16
+
17
+ Rkid.root = File.join(File.dirname(__FILE__), '..')
@@ -0,0 +1,80 @@
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
+ ]
11
+
12
+ def self.analyze(&block)
13
+ prepare
14
+ yield
15
+ report
16
+ end
17
+
18
+ def self.prepare
19
+ self.coverage_analyzer, self.callsite_analyzer = Rcov::CodeCoverageAnalyzer.new, Rcov::CallSiteAnalyzer.new
20
+ callsite_analyzer.install_hook; coverage_analyzer.install_hook
21
+ end
22
+
23
+ def self.report
24
+ callsite_analyzer.remove_hook; coverage_analyzer.remove_hook
25
+ prepare_connection_to_database
26
+ analyze_callsite(callsite_analyzer); analyze_coverage(coverage_analyzer)
27
+ end
28
+
29
+ private
30
+
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')
34
+ end
35
+
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
41
+
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 }
45
+
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)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
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 }
72
+
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])
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,94 @@
1
+ module Rkid
2
+ module FastCreate
3
+ def db
4
+ ActiveRecord::Base.connection.raw_connection
5
+ end
6
+
7
+ def insert(attributes)
8
+ insert = db.prepare <<-SQL
9
+ INSERT INTO #{table_name}
10
+ (#{attributes.keys.join(', ')}) VALUES (#{attributes.keys.collect { |c| ":#{c}" }.join(', ')})
11
+ SQL
12
+ insert.execute attributes
13
+ instantiate(attributes.merge('id' => db.last_insert_row_id))
14
+ end
15
+
16
+ def create(attributes)
17
+ begin
18
+ insert(attributes)
19
+ rescue SQLite3::SQLException
20
+ update(attributes)
21
+ end
22
+ end
23
+ end
24
+
25
+ class Klass < ::ActiveRecord::Base
26
+ extend FastCreate
27
+
28
+ has_many :methods, :class_name => 'Method', :dependent => :destroy
29
+
30
+ def self.update(attributes)
31
+ find_by_name(attributes['name'])
32
+ end
33
+ end
34
+
35
+ class Method < ::ActiveRecord::Base
36
+ extend FastCreate
37
+
38
+ belongs_to :klass
39
+ has_many :callsites, :dependent => :destroy
40
+ has_many :lines, :dependent => :destroy
41
+ 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
+ end
47
+
48
+ class File < ::ActiveRecord::Base
49
+ extend FastCreate
50
+
51
+ has_many :lines, :order => :number, :dependent => :destroy
52
+
53
+ def self.update(attributes)
54
+ find_by_name(attributes['name'])
55
+ end
56
+ end
57
+
58
+ class Callsite < ::ActiveRecord::Base
59
+ extend FastCreate
60
+
61
+ belongs_to :method
62
+ has_many :frames, :order => :level, :dependent => :destroy
63
+ has_many :lines, :through => :frames, :dependent => :destroy
64
+ end
65
+
66
+ class Frame < ::ActiveRecord::Base
67
+ extend FastCreate
68
+
69
+ belongs_to :callsite
70
+ belongs_to :line
71
+ belongs_to :callsite
72
+ end
73
+
74
+ class Line < ::ActiveRecord::Base
75
+ extend FastCreate
76
+
77
+ belongs_to :method
78
+ belongs_to :file
79
+
80
+ def self.update(attributes)
81
+ line = find_by_number_and_file_id(attributes['number'], attributes['file_id'])
82
+ assignments = attributes.keys.collect do |key|
83
+ "#{key} = :#{key}"
84
+ end.join ', '
85
+ update = db.prepare <<-SQL
86
+ UPDATE #{table_name} SET
87
+ #{assignments}
88
+ WHERE id = :id
89
+ SQL
90
+ update.execute attributes.except('covered').merge('id' => line.id)
91
+ line
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module Rkid
5
+ class Task < Rake::TaskLib
6
+ def initialize
7
+ desc "Run specs with rkid"
8
+ task :rkid do
9
+ rkid_script = ::File.expand_path(::File.dirname(__FILE__) + '/../../../bin/rkid')
10
+ specs = FileList['spec/**/*_spec.rb'].collect { |fn| %["#{fn}"] }.join(' ')
11
+
12
+ system %(ruby #{rkid_script} `which spec` -- #{specs})
13
+ end
14
+ end
15
+ end
16
+ end
data/rkid.db ADDED
Binary file
@@ -0,0 +1,9 @@
1
+ class TestedClass
2
+ def tested_method
3
+ 'tested_method'
4
+ end
5
+
6
+ def untested_method
7
+ 'untested_method'
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Rkid do
4
+
5
+ before(:all) do
6
+ require 'spec/fixtures/test_class.rb'
7
+ Rkid.env = 'test'
8
+ Rkid.analyze { TestedClass.new.tested_method }
9
+ end
10
+
11
+ describe '::analyze' do
12
+ before do
13
+ @file = Rkid::File.find_by_name("./spec/fixtures/test_class.rb")
14
+ end
15
+
16
+ it 'records classes and methods' do
17
+ methods = Rkid::Klass.find_by_name('TestedClass').methods
18
+ methods.find_by_name('tested_method').should_not be_nil
19
+ methods.find_by_name('untested_method').should be_nil
20
+ end
21
+
22
+ it 'records callsites of methods' do
23
+ method = Rkid::Klass.find_by_name('TestedClass').methods.find_by_name('tested_method')
24
+ callsite = method.callsites.first
25
+ callsite.count.should == 1
26
+ callsite.frames.first.line.number.should == 8
27
+ callsite.frames.first.line.file.name.should == "./spec/rkid/analyzer_spec.rb"
28
+ end
29
+
30
+ it 'records the source of parsed files' do
31
+ @file.lines.first.body.should == File.open("./spec/fixtures/test_class.rb").readline
32
+ end
33
+
34
+ it 'indicates whether lines are covered' do
35
+ @file.lines.first.should_not be_covered
36
+ @file.lines[1].should be_covered
37
+ @file.lines[-1].should_not be_covered
38
+ end
39
+ end
40
+ end
File without changes
@@ -0,0 +1,12 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift "#{dir}/../lib"
3
+
4
+ require 'rubygems'
5
+ require 'spec'
6
+ require 'pp'
7
+ require 'rkid'
8
+ Dir["#{dir}/matchers/*"].each { |m| require "#{dir}/matchers/#{File.basename(m)}" }
9
+
10
+ Spec::Runner.configure do |config|
11
+ config.mock_with :rr
12
+ end
data/test.db ADDED
File without changes
File without changes
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: rkid
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2008-03-31 00:00:00 -07:00
8
+ summary: Rcov in Database
9
+ require_paths:
10
+ - lib
11
+ email: nick@pivotallabs.com
12
+ homepage:
13
+ rubyforge_project: rkid
14
+ description: Rcov in Database
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
+ - Nick Kallen
31
+ files:
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
35
+ - TODO
36
+ - bin/rkid
37
+ - db/schema.rb
38
+ - lib/rkid.rb
39
+ - lib/rkid/analyzer.rb
40
+ - lib/rkid/models.rb
41
+ - lib/rkid/rake/task.rb
42
+ - rkid.db
43
+ - spec/fixtures/test_class.rb
44
+ - spec/rkid/analyzer_spec.rb
45
+ - spec/rkid/sample.db
46
+ - spec/spec_helper.rb
47
+ - test.db
48
+ - test.html
49
+ test_files: []
50
+
51
+ rdoc_options:
52
+ - --main
53
+ - README.txt
54
+ extra_rdoc_files:
55
+ - Manifest.txt
56
+ - README.txt
57
+ executables:
58
+ - rkid
59
+ extensions: []
60
+
61
+ requirements: []
62
+
63
+ dependencies:
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec
66
+ version_requirement:
67
+ version_requirements: !ruby/object:Gem::Version::Requirement
68
+ requirements:
69
+ - - ">"
70
+ - !ruby/object:Gem::Version
71
+ version: 0.0.0
72
+ version:
73
+ - !ruby/object:Gem::Dependency
74
+ name: rcov
75
+ version_requirement:
76
+ version_requirements: !ruby/object:Gem::Version::Requirement
77
+ requirements:
78
+ - - ">"
79
+ - !ruby/object:Gem::Version
80
+ version: 0.0.0
81
+ version:
82
+ - !ruby/object:Gem::Dependency
83
+ name: hoe
84
+ version_requirement:
85
+ version_requirements: !ruby/object:Gem::Version::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 1.5.1
90
+ version: