metric_fu 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/HISTORY +5 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README +8 -6
  4. data/Rakefile +5 -3
  5. data/TODO +0 -5
  6. data/lib/base/base_template.rb +16 -0
  7. data/lib/base/churn_analyzer.rb +52 -0
  8. data/lib/base/code_issue.rb +97 -0
  9. data/lib/base/configuration.rb +4 -2
  10. data/lib/base/flay_analyzer.rb +50 -0
  11. data/lib/base/flog_analyzer.rb +43 -0
  12. data/lib/base/line_numbers.rb +65 -0
  13. data/lib/base/location.rb +83 -0
  14. data/lib/base/metric_analyzer.rb +404 -0
  15. data/lib/base/ranking.rb +33 -0
  16. data/lib/base/rcov_analyzer.rb +43 -0
  17. data/lib/base/reek_analyzer.rb +114 -0
  18. data/lib/base/roodi_analyzer.rb +37 -0
  19. data/lib/base/saikuro_analyzer.rb +48 -0
  20. data/lib/base/scoring_strategies.rb +29 -0
  21. data/lib/base/stats_analyzer.rb +37 -0
  22. data/lib/base/table.rb +102 -0
  23. data/lib/generators/hotspots.rb +52 -0
  24. data/lib/generators/rcov.rb +41 -0
  25. data/lib/metric_fu.rb +5 -3
  26. data/lib/templates/awesome/hotspots.html.erb +54 -0
  27. data/lib/templates/awesome/index.html.erb +3 -0
  28. data/lib/templates/standard/hotspots.html.erb +54 -0
  29. data/spec/base/line_numbers_spec.rb +62 -0
  30. data/spec/generators/rails_best_practices_spec.rb +52 -0
  31. data/spec/generators/rcov_spec.rb +180 -0
  32. data/spec/generators/roodi_spec.rb +24 -0
  33. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  34. data/spec/graphs/stats_grapher_spec.rb +68 -0
  35. data/spec/resources/line_numbers/foo.rb +33 -0
  36. data/spec/resources/line_numbers/module.rb +11 -0
  37. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  38. data/spec/resources/line_numbers/two_classes.rb +11 -0
  39. data/spec/resources/yml/metric_missing.yml +1 -0
  40. metadata +51 -11
data/lib/metric_fu.rb CHANGED
@@ -1,16 +1,17 @@
1
1
  require 'rake'
2
2
  require 'yaml'
3
3
  begin
4
+ require 'active_support/core_ext/object/to_json'
5
+ require 'active_support/core_ext/object/blank'
6
+ rescue LoadError
4
7
  require 'activesupport'
5
- rescue LoadError
6
- require 'active_support'
7
8
  end
8
9
 
9
10
  # Load a few things to make our lives easier elsewhere.
10
11
  module MetricFu
11
12
  LIB_ROOT = File.dirname(__FILE__)
12
13
  end
13
- base_dir = File.join(MetricFu::LIB_ROOT, 'base')
14
+ base_dir = File.join(MetricFu::LIB_ROOT, 'base')
14
15
  generator_dir = File.join(MetricFu::LIB_ROOT, 'generators')
15
16
  template_dir = File.join(MetricFu::LIB_ROOT, 'templates')
16
17
  graph_dir = File.join(MetricFu::LIB_ROOT, 'graphs')
@@ -20,6 +21,7 @@ graph_dir = File.join(MetricFu::LIB_ROOT, 'graphs')
20
21
  require File.join(base_dir, 'report')
21
22
  require File.join(base_dir, 'generator')
22
23
  require File.join(base_dir, 'graph')
24
+ require File.join(base_dir, 'scoring_strategies')
23
25
 
24
26
  # prevent the task from being run multiple times.
25
27
  unless Rake::Task.task_defined? "metrics:all"
@@ -0,0 +1,54 @@
1
+ <h3>Hotspot Results</h3>
2
+ <p>Meta analysis of your metrics to find hotspots in your code.</p>
3
+ <br/>
4
+
5
+ <% if !@hotspots || @hotspots.size == 0 %>
6
+ No Hotspots were found.
7
+ <% elsif @hotspots && @hotspots.size > 0 %>
8
+
9
+ <% granularities = [:files, :classes, :methods] %>
10
+ <table>
11
+ <tr valign="top">
12
+ <% granularities.each do |granularity| %>
13
+ <th width='33%'>
14
+ <%= granularity.to_s.capitalize %></th>
15
+ <% end %>
16
+ </tr>
17
+
18
+ <% items = [] %>
19
+ <% granularities.each_index do |index| %>
20
+ <% granularity = granularities[index] %>
21
+ <% items << @hotspots[granularity] %>
22
+ <% end %>
23
+
24
+ <% items_length = 0 %>
25
+ <% columns = [0, 1, 2] %>
26
+ <% while (items_length < items[0].length || items_length < items[1].length || items_length < items[2].length) do %>
27
+ <tr valign="top">
28
+ <% columns.each do |column| %>
29
+ <% item = items[column].length >= items_length ? items[column][items_length] : nil %>
30
+ <% if item %>
31
+ <td>
32
+ <b>
33
+ <%= display_location(item[:location], nil) %>
34
+ </b>
35
+ <br/><br/>
36
+ <!-- TODO HOTSPOTS for metric fu nice metric_link method -->
37
+ <% item[:details].each do |metric, info| %>
38
+ <%#= metric_link(@stat, metric, h(metric.to_s.capitalize)) + ": " + h(info)%><br/>
39
+ <%= "#{metric.to_s.capitalize}: #{info}" %><br/>
40
+ <% end %>
41
+ </td>
42
+ <% else %>
43
+ <td> &nbsp; </td>
44
+ <% end %>
45
+ <% end %>
46
+ <% items_length += 1 %>
47
+ </tr>
48
+ <% end %>
49
+
50
+ </table>
51
+ <% end %>
52
+
53
+
54
+ <p>Generated on <%= Time.now.localtime %></p>
@@ -27,5 +27,8 @@
27
27
  <% if @rails_best_practices %>
28
28
  <li class='even failure'><a href="rails_best_practices.html">Rails Best Practices report</a></li>
29
29
  <% end %>
30
+ <% if @hotspots %>
31
+ <li class='even failure'><a href="hotspots.html">Hotspots</a></li>
32
+ <% end %>
30
33
  </ul>
31
34
  <p>Generated on <%= Time.now.localtime %></p>
@@ -0,0 +1,54 @@
1
+ <h3>Hotspot Results</h3>
2
+ <p>Meta analysis of your metrics to find hotspots in your code.</p>
3
+ <br/>
4
+
5
+ <% if !@hotspots || @hotspots.size == 0 %>
6
+ No Hotspots were found.
7
+ <% elsif @hotspots && @hotspots.size > 0 %>
8
+
9
+ <% granularities = [:files, :classes, :methods] %>
10
+ <table>
11
+ <tr valign="top">
12
+ <% granularities.each do |granularity| %>
13
+ <th width='33%'>
14
+ <%= granularity.to_s.capitalize %></th>
15
+ <% end %>
16
+ </tr>
17
+
18
+ <% items = [] %>
19
+ <% granularities.each_index do |index| %>
20
+ <% granularity = granularities[index] %>
21
+ <% items << @hotspots[granularity] %>
22
+ <% end %>
23
+
24
+ <% items_length = 0 %>
25
+ <% columns = [0, 1, 2] %>
26
+ <% while (items_length < items[0].length || items_length < items[1].length || items_length < items[2].length) do %>
27
+ <tr valign="top">
28
+ <% columns.each do |column| %>
29
+ <% item = items[column].length >= items_length ? items[column][items_length] : nil %>
30
+ <% if item %>
31
+ <td>
32
+ <b>
33
+ <%= display_location(item[:location], nil) %>
34
+ </b>
35
+ <br/><br/>
36
+ <!-- TODO HOTSPOTS for metric fu nice metric_link method -->
37
+ <% item[:details].each do |metric, info| %>
38
+ <%#= metric_link(@stat, metric, h(metric.to_s.capitalize)) + ": " + h(info)%><br/>
39
+ <%= "#{metric.to_s.capitalize}: #{info}" %><br/>
40
+ <% end %>
41
+ </td>
42
+ <% else %>
43
+ <td> &nbsp; </td>
44
+ <% end %>
45
+ <% end %>
46
+ <% items_length += 1 %>
47
+ </tr>
48
+ <% end %>
49
+
50
+ </table>
51
+ <% end %>
52
+
53
+
54
+ <p>Generated on <%= Time.now.localtime %></p>
@@ -0,0 +1,62 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe LineNumbers do
4
+
5
+ describe "in_method?" do
6
+ it "should know if a line is NOT in a method" do
7
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
8
+ ln.in_method?(2).should == false
9
+ end
10
+
11
+ it "should know if a line is in an instance method" do
12
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
13
+ ln.in_method?(8).should == true
14
+ end
15
+
16
+ it "should know if a line is in an class method" do
17
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
18
+ ln.in_method?(3).should == true
19
+ end
20
+ end
21
+
22
+ describe "method_at_line" do
23
+ it "should know the name of an instance method at a particular line" do
24
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
25
+ ln.method_at_line(8).should == "Foo#what"
26
+ end
27
+
28
+ it "should know the name of a class method at a particular line" do
29
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
30
+ ln.method_at_line(3).should == "Foo::awesome"
31
+ end
32
+
33
+ it "should know the name of a private method at a particular line" do
34
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
35
+ ln.method_at_line(28).should == "Foo#whoop"
36
+ end
37
+
38
+ it "should know the name of a class method defined in a 'class << self block at a particular line" do
39
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/foo.rb"))
40
+ ln.method_at_line(23).should == "Foo::neat"
41
+ end
42
+
43
+ it "should know the name of an instance method at a particular line in a file with two classes" do
44
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/two_classes.rb"))
45
+ ln.method_at_line(3).should == "Foo#stuff"
46
+ ln.method_at_line(9).should == "Bar#stuff"
47
+ end
48
+
49
+ it "should work with modules" do
50
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/module.rb"))
51
+ ln.method_at_line(4).should == 'KickAss#get_beat_up?'
52
+ end
53
+
54
+ it "should work with module surrounding class" do
55
+ ln = LineNumbers.new(File.read(File.dirname(__FILE__) + "/../resources/line_numbers/module_surrounds_class.rb"))
56
+ ln.method_at_line(5).should == "StuffModule::ThingClass#do_it"
57
+ # ln.method_at_line(12).should == "StuffModule#blah" #why no work?
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe RailsBestPractices do
4
+ describe "emit method" do
5
+ it "should gather the raw data" do
6
+ MetricFu::Configuration.run {}
7
+ practices = MetricFu::RailsBestPractices.new
8
+ practices.should_receive(:`).with("rails_best_practices .")
9
+ practices.emit
10
+ end
11
+ end
12
+
13
+ describe "analyze method" do
14
+ before :each do
15
+ output = <<-HERE.gsub(/^[^\S\n]*/, "")
16
+ ./app/views/admin/testimonials/_form.html.erb:17 - replace instance variable with local variable
17
+ ./app/controllers/admin/campaigns_controller.rb:24,45,68,85 - use before_filter for show,edit,update,destroy
18
+
19
+ go to http://wiki.github.com/flyerhzm/rails_best_practices to see how to solve these errors.
20
+
21
+ Found 2 errors.
22
+ HERE
23
+ MetricFu::Configuration.run {}
24
+ practices = MetricFu::RailsBestPractices.new
25
+ practices.instance_variable_set(:@output, output)
26
+ @results = practices.analyze
27
+ end
28
+
29
+ it "should get the total" do
30
+ @results[:total].should == ["Found 2 errors."]
31
+ end
32
+
33
+ it "should get the problems" do
34
+ @results[:problems].size.should == 2
35
+ @results[:problems].first.should == { :line => "17",
36
+ :problem => "replace instance variable with local variable",
37
+ :file => "./app/views/admin/testimonials/_form.html.erb" }
38
+ @results[:problems][1].should == { :line => "24,45,68,85",
39
+ :problem => "use before_filter for show,edit,update,destroy",
40
+ :file => "./app/controllers/admin/campaigns_controller.rb" }
41
+ end
42
+ end
43
+
44
+ describe "to_h method" do
45
+ it "should put things into a hash" do
46
+ MetricFu::Configuration.run {}
47
+ practices = MetricFu::RailsBestPractices.new
48
+ practices.instance_variable_set(:@rails_best_practices_results, "the_practices")
49
+ practices.to_h[:rails_best_practices].should == "the_practices"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,180 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe MetricFu::Rcov do
4
+
5
+ before :each do
6
+ MetricFu::Configuration.run {}
7
+ File.stub!(:directory?).and_return(true)
8
+ @rcov = MetricFu::Rcov.new('base_dir')
9
+ end
10
+
11
+ describe "emit" do
12
+ before :each do
13
+ @rcov.stub!(:puts)
14
+ MetricFu.rcov[:external] = nil
15
+ end
16
+
17
+ it "should clear out previous output and make output folder" do
18
+ @rcov.stub!(:`)
19
+ FileUtils.should_receive(:rm_rf).with(MetricFu::Rcov.metric_directory, :verbose => false)
20
+ Dir.should_receive(:mkdir).with(MetricFu::Rcov.metric_directory)
21
+ @rcov.emit
22
+ end
23
+
24
+ it "should set the RAILS_ENV" do
25
+ FileUtils.stub!(:rm_rf)
26
+ Dir.stub!(:mkdir)
27
+ MetricFu.rcov[:environment] = "metrics"
28
+ @rcov.should_receive(:`).with(/RAILS_ENV=metrics/)
29
+ @rcov.emit
30
+ end
31
+ end
32
+
33
+ describe "with RCOV_OUTPUT fed into" do
34
+ before :each do
35
+ MetricFu.rcov[:external] = nil
36
+ File.should_receive(:open).
37
+ with(MetricFu::Rcov.metric_directory + '/rcov.txt').
38
+ and_return(mock("io", :read => RCOV_OUTPUT))
39
+ @files = @rcov.analyze
40
+ end
41
+
42
+ describe "analyze" do
43
+ it "should compute percent of lines run" do
44
+ @files["lib/templates/awesome/awesome_template.rb"][:percent_run].should == 13
45
+ @files["lib/templates/standard/standard_template.rb"][:percent_run].should == 14
46
+ end
47
+
48
+ it "should know which lines were run" do
49
+ @files["lib/templates/awesome/awesome_template.rb"][:lines].
50
+ should include({:content=>" require 'fileutils'", :was_run=>true})
51
+ end
52
+
53
+ it "should know which lines NOT were run" do
54
+ @files["lib/templates/awesome/awesome_template.rb"][:lines].
55
+ should include({:content=>" if template_exists?(section)", :was_run=>false})
56
+ end
57
+ end
58
+
59
+ describe "to_h" do
60
+ it "should calculate total percentage for all files" do
61
+ @rcov.to_h[:rcov][:global_percent_run].should == 13.7
62
+ end
63
+ end
64
+ end
65
+ describe "with external configuration option set" do
66
+ before :each do
67
+ @rcov.stub!(:puts)
68
+ MetricFu.rcov[:external] = "coverage/rcov.txt"
69
+ end
70
+
71
+ it "should emit nothing if external configuration option is set" do
72
+ FileUtils.should_not_receive(:rm_rf)
73
+ @rcov.emit
74
+ end
75
+
76
+ it "should open the external rcov analysis file" do
77
+ File.should_receive(:open).
78
+ with(MetricFu.rcov[:external]).
79
+ and_return(mock("io", :read => RCOV_OUTPUT))
80
+ @files = @rcov.analyze
81
+ end
82
+
83
+ end
84
+
85
+
86
+ RCOV_OUTPUT = <<-HERE
87
+ Profiling enabled.
88
+ .............................................................................................................................................................................................
89
+
90
+
91
+ Top 10 slowest examples:
92
+ 0.2707830 MetricFu::RoodiGrapher responding to #get_metrics should push 13 to roodi_count
93
+ 0.1994550 MetricFu::RcovGrapher responding to #get_metrics should update labels with the date
94
+ 0.1985800 MetricFu::ReekGrapher responding to #get_metrics should set a hash of code smells to reek_count
95
+ 0.1919860 MetricFu::ReekGrapher responding to #get_metrics should update labels with the date
96
+ 0.1907400 MetricFu::RoodiGrapher responding to #get_metrics should update labels with the date
97
+ 0.1883000 MetricFu::FlogGrapher responding to #get_metrics should update labels with the date
98
+ 0.1882650 MetricFu::FlayGrapher responding to #get_metrics should push 476 to flay_score
99
+ 0.1868780 MetricFu::FlogGrapher responding to #get_metrics should push to top_five_percent_average
100
+ 0.1847730 MetricFu::FlogGrapher responding to #get_metrics should push 9.9 to flog_average
101
+ 0.1844090 MetricFu::FlayGrapher responding to #get_metrics should update labels with the date
102
+
103
+ Finished in 2.517686 seconds
104
+
105
+ 189 examples, 0 failures
106
+ ================================================================================
107
+ lib/templates/awesome/awesome_template.rb
108
+ ================================================================================
109
+ require 'fileutils'
110
+
111
+ class AwesomeTemplate < MetricFu::Template
112
+
113
+ def write
114
+ !! # Getting rid of the crap before and after the project name from integrity
115
+ !! @name = File.basename(Dir.pwd).gsub(/^\w+-|-\w+$/, "")
116
+ !!
117
+ !! # Copy Bluff javascripts to output directory
118
+ !! Dir[File.join(this_directory, '..', 'javascripts', '*')].each do |f|
119
+ !! FileUtils.copy(f, File.join(MetricFu.output_directory, File.basename(f)))
120
+ !! end
121
+ !!
122
+ !! report.each_pair do |section, contents|
123
+ !! if template_exists?(section)
124
+ !! create_instance_var(section, contents)
125
+ !! @html = erbify(section)
126
+ !! html = erbify('layout')
127
+ !! fn = output_filename(section)
128
+ !! MetricFu.report.save_output(html, MetricFu.output_directory, fn)
129
+ !! end
130
+ !! end
131
+ !!
132
+ !! # Instance variables we need should already be created from above
133
+ !! if template_exists?('index')
134
+ !! @html = erbify('index')
135
+ !! html = erbify('layout')
136
+ !! fn = output_filename('index')
137
+ !! MetricFu.report.save_output(html, MetricFu.output_directory, fn)
138
+ !! end
139
+ !! end
140
+
141
+ def this_directory
142
+ !! File.dirname(__FILE__)
143
+ !! end
144
+ !! end
145
+
146
+ ================================================================================
147
+ lib/templates/standard/standard_template.rb
148
+ ================================================================================
149
+ class StandardTemplate < MetricFu::Template
150
+
151
+
152
+ def write
153
+ !! report.each_pair do |section, contents|
154
+ !! if template_exists?(section)
155
+ !! create_instance_var(section, contents)
156
+ !! html = erbify(section)
157
+ !! fn = output_filename(section)
158
+ !! MetricFu.report.save_output(html, MetricFu.output_directory, fn)
159
+ !! end
160
+ !! end
161
+ !!
162
+ !! # Instance variables we need should already be created from above
163
+ !! if template_exists?('index')
164
+ !! html = erbify('index')
165
+ !! fn = output_filename('index')
166
+ !! MetricFu.report.save_output(html, MetricFu.output_directory, fn)
167
+ !! end
168
+ !! end
169
+
170
+ def this_directory
171
+ !! File.dirname(__FILE__)
172
+ !! end
173
+ !! end
174
+
175
+ HERE
176
+
177
+ end
178
+
179
+
180
+