metric_fu 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+