autometric 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autometric.example +60 -0
- data/History.txt +6 -0
- data/Manifest.txt +23 -0
- data/README.txt +75 -0
- data/Rakefile +21 -0
- data/bin/autocoverage +5 -0
- data/bin/autocyclo +5 -0
- data/bin/autoflog +5 -0
- data/bin/autometric +5 -0
- data/bin/autotoken +5 -0
- data/lib/auto.rb +149 -0
- data/lib/autocoverage.rb +58 -0
- data/lib/autocyclo.rb +82 -0
- data/lib/autoflog.rb +38 -0
- data/lib/autometric.rb +120 -0
- data/lib/autometric/discover.rb +7 -0
- data/lib/autometric/rails.rb +19 -0
- data/lib/autometric/rspec.rb +13 -0
- data/lib/metaid.rb +17 -0
- data/lib/util.rb +89 -0
- data/spec/autocoverage_rspec_spec.rb +15 -0
- data/spec/autocoverage_spec.rb +146 -0
- data/spec/spec_helper.rb +10 -0
- metadata +83 -0
data/.autometric.example
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
def growl(title, msg, pri=0, stick="", image="")
|
2
|
+
image_arg = (!image.empty?) ? "--image #{image}" : ""
|
3
|
+
system "growlnotify -n autometric #{image_arg} -p #{pri} -m \"#{msg}\" #{title} #{stick}"
|
4
|
+
end
|
5
|
+
|
6
|
+
Autocoverage.add_hook :initialize do |at|
|
7
|
+
at.threshold = 90.0
|
8
|
+
end
|
9
|
+
|
10
|
+
Autocoverage.add_hook :failed do |at|
|
11
|
+
growl("Code Coverage Failed","#{at.coverage}% code coverage", 2, "", "~/.autometric_images/coverage_failed.png")
|
12
|
+
end
|
13
|
+
|
14
|
+
Autocoverage.add_hook :passed do |at|
|
15
|
+
growl("Code Coverage Passed", "#{at.coverage}% code coverage", -2, "", "~/.autometric_images/coverage_passed.png") #if at.tainted
|
16
|
+
end
|
17
|
+
|
18
|
+
Autocoverage.add_hook :increased do |at|
|
19
|
+
growl("Code Coverage Increased","#{"%.3f" % (at.coverage - at.previous_coverage)}% code coverage increase", 2, "", "~/.autometric_images/increase.png")
|
20
|
+
end
|
21
|
+
|
22
|
+
Autocoverage.add_hook :decreased do |at|
|
23
|
+
growl("Code Coverage Decreased", "#{"%.3f" % (at.previous_coverage - at.coverage)}% code coverage decrease", -2, "", "~/.autometric_images/decrease.png") #if at.tainted
|
24
|
+
end
|
25
|
+
|
26
|
+
Autocyclo.add_hook :erred do |at|
|
27
|
+
lines = at.errors.collect {|e| "#{e[:rating]} #{e[:id]}" }
|
28
|
+
message = lines.length > 5 ? lines[0...5].join("\n") + "\n..." : lines.join("\n")
|
29
|
+
growl("Cyclomatic Complexity Errors", message, 1, "", "~/.autometric_images/cyclo_error.png")
|
30
|
+
end
|
31
|
+
|
32
|
+
Autocyclo.add_hook :warned do |at|
|
33
|
+
lines = at.warnings.collect {|w| "#{w[:rating]} #{w[:id]}" }
|
34
|
+
message = lines.length > 5 ? lines[0...5].join("\n") + "\n..." : lines.join("\n")
|
35
|
+
growl("Cyclomatic Complexity Warnings", "#{message}", 2, "", "~/.autometric_images/cyclo_warn.png")
|
36
|
+
end
|
37
|
+
|
38
|
+
Autotoken.add_hook :erred do |at|
|
39
|
+
lines = at.errors.collect {|e| "#{e[:rating]} #{e[:id]}" }
|
40
|
+
message = lines.length > 5 ? lines[0...5].join("\n") + "\n..." : lines.join("\n")
|
41
|
+
growl("Token Complexity Errors", message, 1, "", "~/.autometric_images/cyclo_error.png")
|
42
|
+
end
|
43
|
+
|
44
|
+
Autotoken.add_hook :warned do |at|
|
45
|
+
lines = at.warnings.collect {|w| "#{w[:rating]} #{w[:id]}" }
|
46
|
+
message = lines.length > 5 ? lines[0...5].join("\n") + "\n..." : lines.join("\n")
|
47
|
+
growl("Token Complexity Warnings", "#{message}", 2, "", "~/.autometric_images/cyclo_warn.png")
|
48
|
+
end
|
49
|
+
|
50
|
+
Autoflog.add_hook :flogged do |at|
|
51
|
+
growl("Flog Score", "#{"%.3f" % at.score}", 2, "", "~/.autometric_images/flog.png")
|
52
|
+
end
|
53
|
+
|
54
|
+
Autoflog.add_hook :increased do |at|
|
55
|
+
growl("Flog Score Increased", "#{"%.3f" % (at.score - at.previous_score)}", -2, "", "~/.autosuite_images/flog_increase.png")
|
56
|
+
end
|
57
|
+
|
58
|
+
Autoflog.add_hook :decreased do |at|
|
59
|
+
growl("Flog Score Decreased", "#{"%.3f" % (at.previous_score - at.score)}", -2, "", "~/.autosuite_images/flog_decrease.png")
|
60
|
+
end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
.autometric.example
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/autocoverage
|
7
|
+
bin/autocyclo
|
8
|
+
bin/autometric
|
9
|
+
bin/autoflog
|
10
|
+
bin/autotoken
|
11
|
+
lib/auto.rb
|
12
|
+
lib/autocoverage.rb
|
13
|
+
lib/autocyclo.rb
|
14
|
+
lib/autoflog.rb
|
15
|
+
lib/autometric.rb
|
16
|
+
lib/metaid.rb
|
17
|
+
lib/util.rb
|
18
|
+
lib/autometric/discover.rb
|
19
|
+
lib/autometric/rails.rb
|
20
|
+
lib/autometric/rspec.rb
|
21
|
+
spec/autocoverage_rspec_spec.rb
|
22
|
+
spec/autocoverage_spec.rb
|
23
|
+
spec/spec_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
autometric
|
2
|
+
by Ben Burkert
|
3
|
+
http://autometric.rubyforge.org/
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
autometric is comprised of 5 tools: autometric, autocyclo, autotoken, autoflog, and autocoverage.
|
8
|
+
|
9
|
+
autocoverage runs rcov code coverage on your code whenever your library or test code changes.
|
10
|
+
|
11
|
+
autoflog runs flog code analysis on your library code.
|
12
|
+
|
13
|
+
autotoken runs saikuro's token complexity on your library code.
|
14
|
+
|
15
|
+
autocyclo runs saikuro's cyclomatic complexity on your library code.
|
16
|
+
|
17
|
+
autometric manages the multiple automatons that monitor your code.
|
18
|
+
|
19
|
+
== FEATURES/PROBLEMS:
|
20
|
+
|
21
|
+
* Initial version with support for:
|
22
|
+
* rcov
|
23
|
+
* saikuro (cyclomatic and token complexity)
|
24
|
+
* flog
|
25
|
+
|
26
|
+
== SYNOPSIS:
|
27
|
+
|
28
|
+
autometric -a
|
29
|
+
|
30
|
+
== REQUIREMENTS:
|
31
|
+
|
32
|
+
For autocoverage:
|
33
|
+
* rcov
|
34
|
+
|
35
|
+
For autotoken & autocyclo
|
36
|
+
* saikuro
|
37
|
+
|
38
|
+
For autoflog
|
39
|
+
* flog
|
40
|
+
* Hpricot
|
41
|
+
|
42
|
+
== INSTALL:
|
43
|
+
|
44
|
+
To install with Rubygems:
|
45
|
+
* sudo gem install autometric
|
46
|
+
* (optional) sudo gem install flog
|
47
|
+
* (optional) sudo gem install rcov
|
48
|
+
|
49
|
+
To install saikuro, follow the instructions at:
|
50
|
+
* http://saikuro.rubyforge.org
|
51
|
+
|
52
|
+
== LICENSE:
|
53
|
+
|
54
|
+
(The MIT License)
|
55
|
+
|
56
|
+
Copyright (c) 2007 Ben Burkert
|
57
|
+
|
58
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
59
|
+
a copy of this software and associated documentation files (the
|
60
|
+
'Software'), to deal in the Software without restriction, including
|
61
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
62
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
63
|
+
permit persons to whom the Software is furnished to do so, subject to
|
64
|
+
the following conditions:
|
65
|
+
|
66
|
+
The above copyright notice and this permission notice shall be
|
67
|
+
included in all copies or substantial portions of the Software.
|
68
|
+
|
69
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
70
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
71
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
72
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
73
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
74
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
75
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/lib")
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/autometric.rb'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
Hoe.new('autometric', Autometric::VERSION) do |p|
|
9
|
+
p.rubyforge_name = 'autometric'
|
10
|
+
p.author = 'Ben Burkert'
|
11
|
+
p.email = 'ben@benburkert.com'
|
12
|
+
p.summary = 'Continuously analyzes your code with rcov, saikuro, and flog'
|
13
|
+
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
14
|
+
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
15
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run all specs"
|
19
|
+
Spec::Rake::SpecTask.new do |t|
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
data/bin/autocoverage
ADDED
data/bin/autocyclo
ADDED
data/bin/autoflog
ADDED
data/bin/autometric
ADDED
data/bin/autotoken
ADDED
data/lib/auto.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
class CommandError < StandardError; end
|
5
|
+
|
6
|
+
class Auto
|
7
|
+
|
8
|
+
unless defined? WINDOZE then
|
9
|
+
WINDOZE = /win32/ =~ RUBY_PLATFORM
|
10
|
+
SEP = WINDOZE ? '&' : ';'
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
attr_accessor :hooks, :discoveries
|
16
|
+
|
17
|
+
def hooks
|
18
|
+
@hooks ||= Hash.new { |h,k| h[k] = [] }
|
19
|
+
end
|
20
|
+
|
21
|
+
def discoveries
|
22
|
+
@discoveries ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_hook name, &blk
|
26
|
+
hooks[name] << blk
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_discovery &proc
|
30
|
+
discoveries << proc
|
31
|
+
end
|
32
|
+
|
33
|
+
def autodiscover
|
34
|
+
style = []
|
35
|
+
|
36
|
+
$:.push(*Dir["vendor/plugins/*/lib"])
|
37
|
+
paths = $:.dup
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'rubygems'
|
41
|
+
paths.push(*Gem.latest_load_paths)
|
42
|
+
rescue LoadError => e
|
43
|
+
# do nothing
|
44
|
+
end
|
45
|
+
|
46
|
+
paths.each do |d|
|
47
|
+
f = File.join(d, 'autometric', 'discover.rb')
|
48
|
+
load f if File.exist? f
|
49
|
+
end
|
50
|
+
|
51
|
+
discoveries.map { |proc| proc.call }.flatten.compact.sort.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
def run *args
|
55
|
+
new(*args).run
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :output, :sleep, :wants_to_quit, :modified, :working_dirs, :file_masks
|
60
|
+
|
61
|
+
def initialize(output = $stdout)
|
62
|
+
@output = output
|
63
|
+
@sleep = 2
|
64
|
+
@modified = nil
|
65
|
+
@interrupted = @wants_to_quit = false
|
66
|
+
@working_dirs = %w( lib )
|
67
|
+
|
68
|
+
@file_masks = {
|
69
|
+
:lib => /^lib\/.*\.rb$/
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def hook(name)
|
74
|
+
self.class.hooks[name].inject(false) do |handled,plugin|
|
75
|
+
plugin[self] || handled
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def reset
|
80
|
+
@modified = last_modified_time
|
81
|
+
end
|
82
|
+
|
83
|
+
def wait_for_changes
|
84
|
+
loop do
|
85
|
+
mtime = last_modified_time
|
86
|
+
if @modified.nil? || mtime > @modified
|
87
|
+
@modified = mtime
|
88
|
+
break
|
89
|
+
end
|
90
|
+
Kernel::sleep(@sleep)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def last_modified_time
|
95
|
+
find_files.collect {|f| File::mtime(f) }.max
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_files(masks = nil)
|
99
|
+
masks ||= @file_masks.collect{|k,v| v if @working_dirs.include? k.to_s}.compact
|
100
|
+
files = []
|
101
|
+
|
102
|
+
Find.find '.' do |f|
|
103
|
+
filename = f.sub(/^\.\//, '')
|
104
|
+
files << f if [masks].flatten.any? { |mask| filename =~ mask }
|
105
|
+
end
|
106
|
+
|
107
|
+
return files
|
108
|
+
end
|
109
|
+
|
110
|
+
def run
|
111
|
+
hook :run
|
112
|
+
|
113
|
+
reset
|
114
|
+
loop do # ^c handler
|
115
|
+
run_metric
|
116
|
+
wait_for_changes
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def run_metric
|
121
|
+
@wants_to_quit ||= false
|
122
|
+
cmd = make_cmd
|
123
|
+
|
124
|
+
hook :run_command
|
125
|
+
@output.puts cmd
|
126
|
+
|
127
|
+
@results = []
|
128
|
+
line = []
|
129
|
+
|
130
|
+
IO.popen(cmd) do |stdout|
|
131
|
+
@results = stdout.read
|
132
|
+
end
|
133
|
+
|
134
|
+
@output << @results
|
135
|
+
hook :ran_command
|
136
|
+
|
137
|
+
handle_results(@results)
|
138
|
+
end
|
139
|
+
|
140
|
+
alias_method :old_method_missing, :method_missing
|
141
|
+
|
142
|
+
def method_missing(id, *args)
|
143
|
+
if match = id.to_s.match(/_files$/)
|
144
|
+
find_files(@file_masks[match.pre_match.to_sym])
|
145
|
+
else
|
146
|
+
old_method_missing(id, *args)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/autocoverage.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
class CommandError < StandardError; end
|
4
|
+
|
5
|
+
class Autocoverage < Auto
|
6
|
+
requires_command :ruby, :rcov, :testrb
|
7
|
+
|
8
|
+
attr_accessor :threshold, :coverage, :previous_coverage
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super(*args)
|
12
|
+
|
13
|
+
@threshold = 100.0
|
14
|
+
@opts = ["--no-html", "--text-report", "--exclude", "#{test_runner},test,examples"]
|
15
|
+
@previous_coverage = nil
|
16
|
+
@working_dirs = %w( lib test )
|
17
|
+
|
18
|
+
@file_masks = {
|
19
|
+
:lib => /^lib\/.*\.rb$/,
|
20
|
+
:test => /^test\/(.*\/)?test_.*rb$/
|
21
|
+
}
|
22
|
+
|
23
|
+
hook :initialize
|
24
|
+
end
|
25
|
+
|
26
|
+
def make_cmd
|
27
|
+
"#{ruby} -S \"#{rcov}\" #{@opts.join(' ')} #{test_runner} -- #{test_files.join(' ')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :test_runner, :testrb
|
31
|
+
|
32
|
+
def handle_results(results)
|
33
|
+
coverage_result = coverage_results(results).first
|
34
|
+
return if coverage_result.nil?
|
35
|
+
|
36
|
+
@coverage = coverage_result.scan(/^\d{0,3}\.\d+/).first.to_f
|
37
|
+
|
38
|
+
if @threshold > @coverage
|
39
|
+
hook :failed
|
40
|
+
else
|
41
|
+
hook :passed
|
42
|
+
end
|
43
|
+
|
44
|
+
if @previous_coverage
|
45
|
+
if @previous_coverage < @coverage
|
46
|
+
hook :increased
|
47
|
+
elsif @previous_coverage > @coverage
|
48
|
+
hook :decreased
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@previous_coverage = @coverage
|
53
|
+
end
|
54
|
+
|
55
|
+
def coverage_results(results)
|
56
|
+
results.scan(/^\d{0,3}\.\d+%\s+\d+ file\(s\)\s+\d+ Lines\s+\d+ LOC$/)
|
57
|
+
end
|
58
|
+
end
|
data/lib/autocyclo.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
class Autocyclo < Auto
|
2
|
+
requires_command :ruby, :saikuro
|
3
|
+
|
4
|
+
class << self
|
5
|
+
alias_method :old_meets_requirements?, :meets_requirements?
|
6
|
+
|
7
|
+
def meets_requirements?
|
8
|
+
begin
|
9
|
+
require 'rubygems'
|
10
|
+
require 'Hpricot'
|
11
|
+
require 'fileutils'
|
12
|
+
old_meets_requirements?
|
13
|
+
rescue
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :errors, :warnings, :output_dir, :index_file, :run_type
|
20
|
+
|
21
|
+
def initialize(*args)
|
22
|
+
args, options = args_and_options(*args)
|
23
|
+
super(*args)
|
24
|
+
|
25
|
+
@warning_level = options[:token_run] ? 20 : 10
|
26
|
+
@error_level = options[:token_run] ? 30 : 20
|
27
|
+
@run_type = options[:token_run] ? "-t" : "-c"
|
28
|
+
@opts = "-y 0"
|
29
|
+
@output_dir = options[:token_run] ? "token" : "cyclo"
|
30
|
+
@index_file = options[:token_run] ? "index_token.html" : "index_cyclo.html"
|
31
|
+
@errors, @warnings = [], []
|
32
|
+
@working_dirs = %w( lib )
|
33
|
+
|
34
|
+
hook :initialize
|
35
|
+
end
|
36
|
+
|
37
|
+
def errors_and_warnings(doc)
|
38
|
+
errors, warnings = [], []
|
39
|
+
|
40
|
+
complexity_table = doc / :table
|
41
|
+
(complexity_table / :tr).each do |tr|
|
42
|
+
tds = tr / :td
|
43
|
+
|
44
|
+
unless tds.empty? || tds[2][:class].nil?
|
45
|
+
sep = tds[0].inner_text =~ /::$/ ? "" : "#"
|
46
|
+
if tds[2][:class] == "error"
|
47
|
+
errors << { :id => "#{tds[0].inner_text}#{sep}#{tds[1].inner_text}", :rating => tds[2].inner_text.to_i }
|
48
|
+
elsif tds[2][:class] == "warning"
|
49
|
+
warnings << { :id => "#{tds[0].inner_text}#{sep}#{tds[1].inner_text}", :rating => tds[2].inner_text.to_i }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return errors, warnings
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_results(stdout)
|
58
|
+
doc = Hpricot(open("#{@output_dir}/#{@index_file}"))
|
59
|
+
|
60
|
+
@errors, @warnings = errors_and_warnings(doc)
|
61
|
+
|
62
|
+
@errors.sort_by {|e| e[:rating] }
|
63
|
+
@warnings.sort_by {|w| w[:rating] }
|
64
|
+
|
65
|
+
hook :erred unless @errors.empty?
|
66
|
+
hook :warned unless @warnings.empty?
|
67
|
+
|
68
|
+
FileUtils.rm_rf "./#{@output_dir}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def make_cmd
|
72
|
+
"#{saikuro} #{@run_type} -i #{@working_dirs.join(' -i ')} -w #{@warning_level} -e #{@error_level} #{@opts} -f html -o ./#{@output_dir}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Autotoken < Autocyclo
|
77
|
+
def initialize(*args)
|
78
|
+
args, options = args_and_options(*args)
|
79
|
+
|
80
|
+
super(*args.push(options.merge(:token_run => true)))
|
81
|
+
end
|
82
|
+
end
|
data/lib/autoflog.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class Autoflog < Auto
|
2
|
+
requires_command :flog
|
3
|
+
|
4
|
+
attr_accessor :score, :previous_score
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
|
9
|
+
@coverage = @previous_score = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def make_cmd
|
13
|
+
"#{flog} #{@working_dirs.join(' ')}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_results(results)
|
17
|
+
flog_result = flog_results(results).first
|
18
|
+
return if flog_result.nil?
|
19
|
+
|
20
|
+
@score = flog_result.scan(/\d*\.?\d*$/).first.to_f
|
21
|
+
|
22
|
+
hook :flogged
|
23
|
+
|
24
|
+
if @previous_score
|
25
|
+
if @previous_score < @score
|
26
|
+
hook :increased
|
27
|
+
elsif @previous_score > @score
|
28
|
+
hook :decreased
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@previous_score = @score
|
33
|
+
end
|
34
|
+
|
35
|
+
def flog_results(results)
|
36
|
+
results.scan(/Total score = \d*\.?\d*$/)
|
37
|
+
end
|
38
|
+
end
|
data/lib/autometric.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rbconfig'
|
3
|
+
require 'find'
|
4
|
+
|
5
|
+
require 'metaid'
|
6
|
+
require 'util'
|
7
|
+
require 'auto'
|
8
|
+
require 'autocoverage'
|
9
|
+
require 'autocyclo'
|
10
|
+
require 'autoflog'
|
11
|
+
|
12
|
+
class Autometric < Auto
|
13
|
+
VERSION = '0.0.1'
|
14
|
+
|
15
|
+
RUNNERS = {
|
16
|
+
'c' => Autocoverage,
|
17
|
+
'y' => Autocyclo,
|
18
|
+
't' => Autotoken,
|
19
|
+
'f' => Autoflog
|
20
|
+
}
|
21
|
+
|
22
|
+
attr_accessor :argv
|
23
|
+
|
24
|
+
def initialize(argv = [])
|
25
|
+
@argv = argv
|
26
|
+
@wants_to_quit = @interrupted = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def run()
|
30
|
+
targets = runners.collect { |klass| find_target klass }
|
31
|
+
targets = targets.find_all {|target| target.meets_requirements? }
|
32
|
+
|
33
|
+
return if targets.empty?
|
34
|
+
|
35
|
+
@thread_map = targets.to_h do |target|
|
36
|
+
Thread.start(target) do |target|
|
37
|
+
target.run
|
38
|
+
target.hook :thread_terminated
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
hook :run
|
43
|
+
add_sigint_handler
|
44
|
+
|
45
|
+
loop do # ^c handler
|
46
|
+
begin
|
47
|
+
Kernel::sleep
|
48
|
+
rescue Interrupt
|
49
|
+
if @wants_to_quit
|
50
|
+
break
|
51
|
+
else
|
52
|
+
reset
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
hook :quit
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_sigint_handler
|
61
|
+
trap 'INT' do
|
62
|
+
if @interrupted then
|
63
|
+
@wants_to_quit = true
|
64
|
+
else
|
65
|
+
unless hook :interrupt then
|
66
|
+
puts "Interrupt a second time to quit"
|
67
|
+
@interrupted = true
|
68
|
+
Kernel::sleep(1.5)
|
69
|
+
end
|
70
|
+
raise Interrupt, nil # let the run loop catch it
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def runners
|
76
|
+
return RUNNERS.values if @argv.any? {|arg| arg =~ /a/ } || @argv.empty?
|
77
|
+
|
78
|
+
klasses = @argv.collect do |arg|
|
79
|
+
RUNNERS.collect { |flag, klass| klass if arg =~ Regexp.compile(flag) }.compact
|
80
|
+
end
|
81
|
+
|
82
|
+
klasses.flatten
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_target(klass)
|
86
|
+
style = klass.autodiscover
|
87
|
+
target = klass
|
88
|
+
unless style.empty? then
|
89
|
+
mod = "autometric/#{style.join("_")}"
|
90
|
+
puts "loading #{mod}"
|
91
|
+
|
92
|
+
begin
|
93
|
+
require mod
|
94
|
+
rescue LoadError
|
95
|
+
abort "#{klass.name} style #{mod} doesn't seem to exist. Aborting."
|
96
|
+
end
|
97
|
+
target = klass.const_get(style.map{|s| s.capitalize}.join)
|
98
|
+
end
|
99
|
+
target
|
100
|
+
end
|
101
|
+
|
102
|
+
def reset
|
103
|
+
@interrupted = @wants_to_quit = false
|
104
|
+
@thread_map.each do |target, thread|
|
105
|
+
thread.kill if thread.status
|
106
|
+
@thread_map[target] = Thread.start(target) do |target|
|
107
|
+
target.run
|
108
|
+
target.hook :thread_terminated
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
unless defined? $debug
|
115
|
+
if test ?f, '.autometric' then
|
116
|
+
load '.autometric'
|
117
|
+
elsif test ?f, File.expand_path('~/.autometric') then
|
118
|
+
load File.expand_path('~/.autometric')
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Autocyclo::Rails < Autocyclo
|
2
|
+
alias_hooks_to Autocyclo
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super(*args)
|
6
|
+
|
7
|
+
@working_dirs = %w( lib app )
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Autoflog::Rails < Autoflog
|
12
|
+
alias_hooks_to Autoflog
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super(*args)
|
16
|
+
|
17
|
+
@working_dirs = %( lib app )
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Autocoverage::Rspec < Autocoverage
|
2
|
+
requires_command :spec
|
3
|
+
alias_hooks_to Autocoverage
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
super(*args)
|
7
|
+
|
8
|
+
@file_masks[:test] = /^spec\/.*_spec\.rb$/
|
9
|
+
@opts = ["--no-html", "--text-report", "--exclude", "examples,spec,#{test_runner}"]
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :test_runner, :spec
|
13
|
+
end
|
data/lib/metaid.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Object
|
2
|
+
# The hidden singleton lurks behind everyone
|
3
|
+
def metaclass; class << self; self; end; end
|
4
|
+
|
5
|
+
def meta_eval(*args, &blk); metaclass.instance_eval(*args, &blk); end
|
6
|
+
|
7
|
+
|
8
|
+
# Adds methods to a metaclass
|
9
|
+
def meta_def name, &blk
|
10
|
+
meta_eval { define_method name, &blk }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Defines an instance method within a class
|
14
|
+
def class_def name, &blk
|
15
|
+
class_eval { define_method name, &blk }
|
16
|
+
end
|
17
|
+
end
|
data/lib/util.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
class Object
|
2
|
+
def args_and_options(*args)
|
3
|
+
options = Hash === args.last ? args.pop : {}
|
4
|
+
return args, options
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Array
|
9
|
+
def to_h(&block)
|
10
|
+
Hash[*self.collect { |v|
|
11
|
+
[v, block.call(v)]
|
12
|
+
}.flatten]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module RubyCommands
|
17
|
+
module Methods
|
18
|
+
def ruby_command(command_name)
|
19
|
+
command_name = command_name.to_s
|
20
|
+
commands = [File.join('bin', command_name), File.join(Config::CONFIG['bindir'], command_name)]
|
21
|
+
|
22
|
+
commands.each do |command|
|
23
|
+
if File.exists?(command)
|
24
|
+
unless File::ALT_SEPARATOR.nil? then
|
25
|
+
command.gsub! File::SEPARATOR, File::ALT_SEPARATOR
|
26
|
+
end
|
27
|
+
return command
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
raise CommandError, "No #{command_name} command could be found!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.included(receiver)
|
36
|
+
receiver.extend Methods
|
37
|
+
receiver.send :include, Methods
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Module
|
42
|
+
def requires_command(*args)
|
43
|
+
include RubyCommands
|
44
|
+
|
45
|
+
args.each do |command|
|
46
|
+
class_def command do
|
47
|
+
ruby_command(command)
|
48
|
+
end
|
49
|
+
|
50
|
+
meta_def command do
|
51
|
+
ruby_command(command)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
meta_def :meets_requirements? do
|
56
|
+
begin
|
57
|
+
args.each {|command| ruby_command(command.to_s) }
|
58
|
+
true
|
59
|
+
rescue
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def alias_hooks_to(klass)
|
66
|
+
class_eval do
|
67
|
+
class << self
|
68
|
+
attr_accessor :super_klass
|
69
|
+
|
70
|
+
def super_klass=(klass)
|
71
|
+
@super_klass = klass
|
72
|
+
end
|
73
|
+
|
74
|
+
undef hooks
|
75
|
+
def hooks
|
76
|
+
@super_klass.hooks
|
77
|
+
end
|
78
|
+
|
79
|
+
undef add_hook
|
80
|
+
def add_hook(name, &blk)
|
81
|
+
@super_klass.add_hook(name, &blk)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
self.super_klass = klass
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Autocoverage::Rspec do
|
4
|
+
before(:each) do
|
5
|
+
@auto = Autocoverage::Rspec.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have a spec mask for tests" do
|
9
|
+
@auto.file_masks[:test].should == /^spec\/.*_spec\.rb$/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return the spec command for test_runner" do
|
13
|
+
@auto.test_runner.should =~ /spec$/
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
describe Autocoverage, "defaults" do
|
3
|
+
before(:each) do
|
4
|
+
@auto = Autocoverage.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should have a threshold of 100.0" do
|
8
|
+
@auto.threshold.should == 100.0
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not have a coverage value" do
|
12
|
+
@auto.coverage.should be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not have a previous coverage value" do
|
16
|
+
@auto.previous_coverage.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should look for rb files in the 'lib' dir" do
|
20
|
+
@auto.file_masks.values.should include(/^lib\/.*\.rb$/)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should look for test files in the 'test' dir" do
|
24
|
+
@auto.file_masks.values.should include(/^test\/(.*\/)?test_.*rb$/)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Autocoverage, "coverage command" do
|
29
|
+
before(:each) do
|
30
|
+
@auto = Autocoverage.new
|
31
|
+
@alt_separator = File::ALT_SEPARATOR
|
32
|
+
end
|
33
|
+
|
34
|
+
after(:each) do
|
35
|
+
File::ALT_SEPARATOR = @alt_separator unless File::ALT_SEPARATOR == @alt_separator
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should exclude the test runner" do
|
39
|
+
@auto.make_cmd.should =~ Regexp.compile("--exclude #{@auto.test_runner}")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should exclude the test directory" do
|
43
|
+
@auto.make_cmd.should =~ Regexp.compile(/--exclude .*,test/)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should work if File::ALT_SEPARATOR has a value" do
|
47
|
+
original = @auto.make_cmd
|
48
|
+
File::ALT_SEPARATOR = File::SEPARATOR
|
49
|
+
@auto.make_cmd.should == original
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise an exception if it cannot find rcov" do
|
53
|
+
File.should_receive(:exists?).at_least(:once).and_return { |arg| true unless arg =~ /rcov/ }
|
54
|
+
lambda { @auto.make_cmd }.should raise_error(CommandError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise an exception if it cannot find testrb" do
|
58
|
+
File.should_receive(:exists?).at_least(:once).and_return { |arg| true unless arg =~ /testrb/ }
|
59
|
+
lambda { @auto.make_cmd }.should raise_error(CommandError)
|
60
|
+
end
|
61
|
+
|
62
|
+
#it "should halt running if an interupt is detected" do
|
63
|
+
# @auto.should_receive(:add_sigint_handler)
|
64
|
+
# @auto.should_receive(:run_metric).and_return { raise Interrupt, "test interrupt" }
|
65
|
+
# @auto.wants_to_quit = true
|
66
|
+
# @auto.run
|
67
|
+
#end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe Autocoverage, "results" do
|
71
|
+
before(:each) do
|
72
|
+
Autocoverage.hooks.clear
|
73
|
+
@auto = Autocoverage.new
|
74
|
+
@full_coverage, @half_coverage = <<-FULL, <<-HALF
|
75
|
+
1 tests, 1 assertions, 0 failures, 0 errors
|
76
|
+
+----------------------------------------------------+-------+-------+--------+
|
77
|
+
| File | Lines | LOC | COV |
|
78
|
+
+----------------------------------------------------+-------+-------+--------+
|
79
|
+
|lib/full_coverage.rb | 5 | 5 | 100.0% |
|
80
|
+
+----------------------------------------------------+-------+-------+--------+
|
81
|
+
|Total | 5 | 5 | 100.0% |
|
82
|
+
+----------------------------------------------------+-------+-------+--------+
|
83
|
+
100.0% 1 file(s) 5 Lines 5 LOC
|
84
|
+
FULL
|
85
|
+
1 tests, 1 assertions, 0 failures, 0 errors
|
86
|
+
+----------------------------------------------------+-------+-------+--------+
|
87
|
+
| File | Lines | LOC | COV |
|
88
|
+
+----------------------------------------------------+-------+-------+--------+
|
89
|
+
|lib/half_coverage.rb | 11 | 10 | 50.0% |
|
90
|
+
+----------------------------------------------------+-------+-------+--------+
|
91
|
+
|Total | 11 | 10 | 50.0% |
|
92
|
+
+----------------------------------------------------+-------+-------+--------+
|
93
|
+
50.0% 1 file(s) 11 Lines 10 LOC
|
94
|
+
HALF
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should update the coverage value" do
|
98
|
+
lambda{ @auto.handle_results(@full_coverage) }.should change{ @auto.coverage }.to(100.0)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should update the previous_coverage" do
|
102
|
+
lambda{ @auto.handle_results(@full_coverage) }.should change{ @auto.previous_coverage }.to(100.0)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should call failed if the coverage value is less than the threshold" do
|
106
|
+
Autocoverage.add_hook :failed do |at|
|
107
|
+
@fail_called = true
|
108
|
+
end
|
109
|
+
|
110
|
+
lambda { @auto.handle_results(@half_coverage) }.should change{ @fail_called }
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should call passed if the coverage value is greater than the threshold" do
|
114
|
+
Autocoverage.add_hook :passed do |at|
|
115
|
+
@passed_called = true
|
116
|
+
end
|
117
|
+
|
118
|
+
@auto.threshold = 50.0
|
119
|
+
lambda { @auto.handle_results(@full_coverage) }.should change{ @passed_called }
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should call passed if the coverage value is equal to the threshold" do
|
123
|
+
Autocoverage.add_hook :passed do |at|
|
124
|
+
@passed_called = true
|
125
|
+
end
|
126
|
+
|
127
|
+
lambda { @auto.handle_results(@full_coverage) }.should change{ @passed_called }
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should call increased if the coverage value is greater than the previous coverage value" do
|
131
|
+
Autocoverage.add_hook :increased do |at|
|
132
|
+
@increased_called = true
|
133
|
+
end
|
134
|
+
|
135
|
+
@auto.previous_coverage = 50.0
|
136
|
+
lambda { @auto.handle_results(@full_coverage) }.should change{ @increased_called }
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should call decreased if the coverage value is les than the previous coverage value" do
|
140
|
+
Autocoverage.add_hook :decreased do |at|
|
141
|
+
@decreased_called = true
|
142
|
+
end
|
143
|
+
@auto.previous_coverage = 100.0
|
144
|
+
lambda { @auto.handle_results(@half_coverage) }.should change{ @decreased_called }
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
lib_path = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
2
|
+
$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
|
3
|
+
|
4
|
+
$debug = true
|
5
|
+
|
6
|
+
require 'autometric'
|
7
|
+
require 'autometric/rspec'
|
8
|
+
require 'stringio'
|
9
|
+
|
10
|
+
$examples_path = File.expand_path("#{File.dirname(__FILE__)}/../examples")
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: autometric
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2007-11-03 00:00:00 -05:00
|
8
|
+
summary: Continuously analyzes your code with rcov, saikuro, and flog
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ben@benburkert.com
|
12
|
+
homepage: " by Ben Burkert"
|
13
|
+
rubyforge_project: autometric
|
14
|
+
description: autocoverage runs rcov code coverage on your code whenever your library or test code changes. autoflog runs flog code analysis on your library code. autotoken runs saikuro's token complexity on your library code. autocyclo runs saikuro's cyclomatic complexity on your library code.
|
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
|
+
- Ben Burkert
|
31
|
+
files:
|
32
|
+
- .autometric.example
|
33
|
+
- History.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
- Rakefile
|
37
|
+
- bin/autocoverage
|
38
|
+
- bin/autocyclo
|
39
|
+
- bin/autometric
|
40
|
+
- bin/autoflog
|
41
|
+
- bin/autotoken
|
42
|
+
- lib/auto.rb
|
43
|
+
- lib/autocoverage.rb
|
44
|
+
- lib/autocyclo.rb
|
45
|
+
- lib/autoflog.rb
|
46
|
+
- lib/autometric.rb
|
47
|
+
- lib/metaid.rb
|
48
|
+
- lib/util.rb
|
49
|
+
- lib/autometric/discover.rb
|
50
|
+
- lib/autometric/rails.rb
|
51
|
+
- lib/autometric/rspec.rb
|
52
|
+
- spec/autocoverage_rspec_spec.rb
|
53
|
+
- spec/autocoverage_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
test_files: []
|
56
|
+
|
57
|
+
rdoc_options:
|
58
|
+
- --main
|
59
|
+
- README.txt
|
60
|
+
extra_rdoc_files:
|
61
|
+
- History.txt
|
62
|
+
- Manifest.txt
|
63
|
+
- README.txt
|
64
|
+
executables:
|
65
|
+
- autocoverage
|
66
|
+
- autocyclo
|
67
|
+
- autometric
|
68
|
+
- autoflog
|
69
|
+
- autotoken
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
dependencies:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: hoe
|
77
|
+
version_requirement:
|
78
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.3.0
|
83
|
+
version:
|