autometric 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,6 @@
1
+ == 0.0.1 / 2007-11-3
2
+
3
+ * Initial version with support for:
4
+ * rcov
5
+ * saikuro (cyclomatic and token complexity)
6
+ * flog
@@ -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
@@ -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.
@@ -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
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'autometric'
4
+
5
+ Autometric.run(['-c'])
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'autometric'
4
+
5
+ Autometric.run(['-y'])
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'autometric'
4
+
5
+ Autometric.run(['-f'])
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'autometric'
4
+
5
+ Autometric.run(ARGV)
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'autometric'
4
+
5
+ Autometric.run(['-t'])
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,7 @@
1
+ Autocoverage.add_discovery do
2
+ "rspec" if File.exist?('spec')
3
+ end
4
+
5
+ Autocyclo.add_discovery do
6
+ "rails" if File.exist?('config/environment.rb')
7
+ 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
@@ -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
@@ -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
@@ -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: