bf4-metric_fu 2.1.3.1

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 (127) hide show
  1. data/HISTORY +252 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +49 -0
  4. data/Rakefile +22 -0
  5. data/TODO +6 -0
  6. data/lib/base/base_template.rb +182 -0
  7. data/lib/base/churn_analyzer.rb +38 -0
  8. data/lib/base/code_issue.rb +100 -0
  9. data/lib/base/configuration.rb +219 -0
  10. data/lib/base/flay_analyzer.rb +50 -0
  11. data/lib/base/flog_analyzer.rb +43 -0
  12. data/lib/base/generator.rb +166 -0
  13. data/lib/base/graph.rb +44 -0
  14. data/lib/base/grouping.rb +42 -0
  15. data/lib/base/line_numbers.rb +79 -0
  16. data/lib/base/location.rb +87 -0
  17. data/lib/base/md5_tracker.rb +52 -0
  18. data/lib/base/metric_analyzer.rb +331 -0
  19. data/lib/base/ranking.rb +34 -0
  20. data/lib/base/rcov_analyzer.rb +43 -0
  21. data/lib/base/record.rb +43 -0
  22. data/lib/base/reek_analyzer.rb +164 -0
  23. data/lib/base/report.rb +110 -0
  24. data/lib/base/roodi_analyzer.rb +37 -0
  25. data/lib/base/saikuro_analyzer.rb +48 -0
  26. data/lib/base/scoring_strategies.rb +29 -0
  27. data/lib/base/stats_analyzer.rb +37 -0
  28. data/lib/base/table.rb +108 -0
  29. data/lib/generators/churn.rb +28 -0
  30. data/lib/generators/flay.rb +31 -0
  31. data/lib/generators/flog.rb +113 -0
  32. data/lib/generators/hotspots.rb +52 -0
  33. data/lib/generators/rails_best_practices.rb +53 -0
  34. data/lib/generators/rcov.rb +124 -0
  35. data/lib/generators/reek.rb +81 -0
  36. data/lib/generators/roodi.rb +35 -0
  37. data/lib/generators/saikuro.rb +259 -0
  38. data/lib/generators/stats.rb +58 -0
  39. data/lib/graphs/engines/bluff.rb +113 -0
  40. data/lib/graphs/engines/gchart.rb +157 -0
  41. data/lib/graphs/flay_grapher.rb +18 -0
  42. data/lib/graphs/flog_grapher.rb +57 -0
  43. data/lib/graphs/grapher.rb +11 -0
  44. data/lib/graphs/rails_best_practices_grapher.rb +19 -0
  45. data/lib/graphs/rcov_grapher.rb +18 -0
  46. data/lib/graphs/reek_grapher.rb +30 -0
  47. data/lib/graphs/roodi_grapher.rb +18 -0
  48. data/lib/graphs/stats_grapher.rb +20 -0
  49. data/lib/metric_fu.rb +80 -0
  50. data/lib/tasks/metric_fu.rake +36 -0
  51. data/lib/templates/awesome/awesome_template.rb +92 -0
  52. data/lib/templates/awesome/churn.html.erb +58 -0
  53. data/lib/templates/awesome/css/buttons.css +82 -0
  54. data/lib/templates/awesome/css/default.css +91 -0
  55. data/lib/templates/awesome/css/integrity.css +334 -0
  56. data/lib/templates/awesome/css/reset.css +7 -0
  57. data/lib/templates/awesome/css/syntax.css +19 -0
  58. data/lib/templates/awesome/flay.html.erb +34 -0
  59. data/lib/templates/awesome/flog.html.erb +55 -0
  60. data/lib/templates/awesome/hotspots.html.erb +62 -0
  61. data/lib/templates/awesome/index.html.erb +34 -0
  62. data/lib/templates/awesome/layout.html.erb +30 -0
  63. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  64. data/lib/templates/awesome/rcov.html.erb +42 -0
  65. data/lib/templates/awesome/reek.html.erb +40 -0
  66. data/lib/templates/awesome/roodi.html.erb +27 -0
  67. data/lib/templates/awesome/saikuro.html.erb +71 -0
  68. data/lib/templates/awesome/stats.html.erb +51 -0
  69. data/lib/templates/javascripts/bluff-min.js +1 -0
  70. data/lib/templates/javascripts/excanvas.js +35 -0
  71. data/lib/templates/javascripts/js-class.js +1 -0
  72. data/lib/templates/standard/churn.html.erb +31 -0
  73. data/lib/templates/standard/default.css +64 -0
  74. data/lib/templates/standard/flay.html.erb +34 -0
  75. data/lib/templates/standard/flog.html.erb +57 -0
  76. data/lib/templates/standard/hotspots.html.erb +54 -0
  77. data/lib/templates/standard/index.html.erb +41 -0
  78. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  79. data/lib/templates/standard/rcov.html.erb +43 -0
  80. data/lib/templates/standard/reek.html.erb +42 -0
  81. data/lib/templates/standard/roodi.html.erb +29 -0
  82. data/lib/templates/standard/saikuro.html.erb +84 -0
  83. data/lib/templates/standard/standard_template.rb +27 -0
  84. data/lib/templates/standard/stats.html.erb +55 -0
  85. data/spec/base/base_template_spec.rb +194 -0
  86. data/spec/base/configuration_spec.rb +277 -0
  87. data/spec/base/generator_spec.rb +223 -0
  88. data/spec/base/graph_spec.rb +61 -0
  89. data/spec/base/line_numbers_spec.rb +62 -0
  90. data/spec/base/location_spec.rb +127 -0
  91. data/spec/base/md5_tracker_spec.rb +57 -0
  92. data/spec/base/metric_analyzer_spec.rb +452 -0
  93. data/spec/base/ranking_spec.rb +42 -0
  94. data/spec/base/report_spec.rb +146 -0
  95. data/spec/base/table_spec.rb +36 -0
  96. data/spec/generators/churn_spec.rb +41 -0
  97. data/spec/generators/flay_spec.rb +105 -0
  98. data/spec/generators/flog_spec.rb +70 -0
  99. data/spec/generators/hotspots_spec.rb +88 -0
  100. data/spec/generators/rails_best_practices_spec.rb +52 -0
  101. data/spec/generators/rcov_spec.rb +180 -0
  102. data/spec/generators/reek_spec.rb +134 -0
  103. data/spec/generators/roodi_spec.rb +24 -0
  104. data/spec/generators/saikuro_spec.rb +74 -0
  105. data/spec/generators/stats_spec.rb +74 -0
  106. data/spec/graphs/engines/bluff_spec.rb +19 -0
  107. data/spec/graphs/engines/gchart_spec.rb +156 -0
  108. data/spec/graphs/flay_grapher_spec.rb +56 -0
  109. data/spec/graphs/flog_grapher_spec.rb +108 -0
  110. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  111. data/spec/graphs/rcov_grapher_spec.rb +56 -0
  112. data/spec/graphs/reek_grapher_spec.rb +65 -0
  113. data/spec/graphs/roodi_grapher_spec.rb +56 -0
  114. data/spec/graphs/stats_grapher_spec.rb +68 -0
  115. data/spec/resources/line_numbers/foo.rb +33 -0
  116. data/spec/resources/line_numbers/module.rb +11 -0
  117. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  118. data/spec/resources/line_numbers/two_classes.rb +11 -0
  119. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  120. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  121. data/spec/resources/saikuro/index_cyclo.html +155 -0
  122. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  123. data/spec/resources/yml/20090630.yml +7922 -0
  124. data/spec/resources/yml/metric_missing.yml +1 -0
  125. data/spec/spec.opts +6 -0
  126. data/spec/spec_helper.rb +7 -0
  127. metadata +560 -0
@@ -0,0 +1,38 @@
1
+ class ChurnAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{times_changed}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :churn
12
+ end
13
+
14
+ def map(row)
15
+ ScoringStrategies.present(row)
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.sum(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ flat_churn_score = 0.50
24
+ metric_ranking.scored?(item) ? flat_churn_score : 0
25
+ end
26
+
27
+ def generate_records(data, table)
28
+ return if data==nil
29
+ Array(data[:changes]).each do |change|
30
+ table << {
31
+ "metric" => :churn,
32
+ "times_changed" => change[:times_changed],
33
+ "file_path" => change[:file_path]
34
+ }
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,100 @@
1
+ require 'delegate'
2
+
3
+ [ '/base/metric_analyzer',
4
+ '/base/flog_analyzer',
5
+ '/base/saikuro_analyzer',
6
+ '/base/churn_analyzer',
7
+ '/base/reek_analyzer',
8
+ '/base/flay_analyzer'].each do |path|
9
+ require File.expand_path(File.join(MetricFu::LIB_ROOT,path))
10
+ end
11
+
12
+ module CarefulArray
13
+
14
+ def carefully_remove(elements)
15
+ missing_elements = elements - self
16
+ raise "Cannot delete missing elements: #{missing_elements.inspect}" unless missing_elements.empty?
17
+ (self - elements).extend(CarefulArray)
18
+ end
19
+
20
+ end
21
+
22
+ class CodeIssue < DelegateClass(MetricFu::Record) #DelegateClass(Ruport::Data::Record)
23
+ include Comparable
24
+
25
+ # TODO: Yuck! 'stat_value' is a column for StatAnalyzer
26
+ EXCLUDED_COLUMNS = FlogAnalyzer::COLUMNS + SaikuroAnalyzer::COLUMNS + ['stat_value'] + ChurnAnalyzer::COLUMNS + ReekAnalyzer.new.columns.extend(CarefulArray).carefully_remove(['reek__type_name', 'reek__comparable_message']) + FlayAnalyzer.new.columns.extend(CarefulArray).carefully_remove(['flay_matching_reason'])
27
+
28
+ def <=>(other)
29
+ spaceship_for_columns(self.attributes, other)
30
+ end
31
+
32
+ def ===(other)
33
+ self.hash_for(included_columns_hash, included_columns) == other.hash_for(included_columns_hash, included_columns)
34
+ end
35
+
36
+ def spaceship_for_columns(columns, other)
37
+ columns.each do |column|
38
+ equality = self[column].to_s <=> other[column].to_s
39
+ return equality if equality!=0
40
+ end
41
+ return 0
42
+ end
43
+
44
+ def hash_for(column_hash, columns)
45
+ @hashes ||= {}
46
+ # fetch would be cleaner, but slower
47
+ if @hashes.has_key?(column_hash)
48
+ @hashes[column_hash]
49
+ else
50
+ values = columns.map {|column| self[column]}
51
+ hash_for_columns = values.join('').hash
52
+ @hashes[column_hash]=hash_for_columns
53
+ hash_for_columns
54
+ end
55
+ end
56
+
57
+ def included_columns_hash
58
+ @included_columns_hash ||= included_columns.hash
59
+ end
60
+
61
+ def included_columns
62
+ @included_columns ||= self.attributes.extend(CarefulArray).carefully_remove(EXCLUDED_COLUMNS)
63
+ end
64
+
65
+ def find_counterpart_index_in(collection)
66
+ # each_with_index is cleaner, but it is slower and we
67
+ # spend a lot of time in this loop
68
+ index = 0
69
+ collection.each do |issue|
70
+ return index if self === issue
71
+ index += 1
72
+ end
73
+ return nil
74
+ end
75
+
76
+ def modifies?(other)
77
+ case self.metric
78
+ when :reek
79
+ #return false unless ["Large Class", "Long Method", "Long Parameter List"].include?(self.reek__type_name)
80
+ return false if self.reek__type_name != other.reek__type_name
81
+ self.reek__value != other.reek__value
82
+ when :flog
83
+ self.score != other.score
84
+ when :saikuro
85
+ self.complexity != other.complexity
86
+ when :stats
87
+ self.stat_value != other.stat_value
88
+ when :churn
89
+ self.times_changed != other.times_changed
90
+ when :flay
91
+ #self.flay_reason != other.flay_reason
92
+ # do nothing for now
93
+ when :roodi, :stats
94
+ # do nothing
95
+ else
96
+ raise ArgumentError, "Invalid metric type #{self.metric}"
97
+ end
98
+ end
99
+
100
+ end
@@ -0,0 +1,219 @@
1
+ module MetricFu
2
+
3
+ # A list of metrics which are available in the MetricFu system.
4
+ #
5
+ # These are metrics which have been developed for the system. Of
6
+ # course, in order to use these metrics, their respective gems must
7
+ # be installed on the system.
8
+ AVAILABLE_METRICS = [:churn,
9
+ :flog,
10
+ :flay,
11
+ :reek,
12
+ :roodi,
13
+ :rcov,
14
+ :hotspots,
15
+ :saikuro
16
+ ]
17
+
18
+ AVAILABLE_GRAPHS = [
19
+ :flog,
20
+ :flay,
21
+ :reek,
22
+ :roodi,
23
+ :rcov,
24
+ :rails_best_practices
25
+ ]
26
+ AVAILABLE_GRAPH_ENGINES = [:gchart, :bluff]
27
+
28
+ # The @@configuration class variable holds a global type configuration
29
+ # object for any parts of the system to use.
30
+ def self.configuration
31
+ @@configuration ||= Configuration.new
32
+ end
33
+
34
+ # = Configuration
35
+ #
36
+ # The Configuration class, as it sounds, provides methods for
37
+ # configuring the behaviour of MetricFu.
38
+ #
39
+ # == Customization for Rails
40
+ #
41
+ # The Configuration class checks for the presence of a
42
+ # 'config/environment.rb' file. If the file is present, it assumes
43
+ # it is running in a Rails project. If it is, it will:
44
+ #
45
+ # * Add 'app' to the @code_dirs directory to include the
46
+ # code in the app directory in the processing
47
+ # * Add :stats to the list of metrics to run to get the Rails stats
48
+ # task
49
+ #
50
+ # == Customization for CruiseControl.rb
51
+ #
52
+ # The Configuration class checks for the presence of a
53
+ # 'CC_BUILD_ARTIFACTS' environment variable. If it's found
54
+ # it will change the default output directory from the default
55
+ # "tmp/metric_fu to the directory represented by 'CC_BUILD_ARTIFACTS'
56
+ #
57
+ # == Deprications
58
+ #
59
+ # The Configuration class checks for several deprecated constants
60
+ # that were previously used to configure MetricFu. These include
61
+ # CHURN_OPTIONS, DIRECTORIES_TO_FLOG, SAIKURO_OPTIONS,
62
+ # and MetricFu::SAIKURO_OPTIONS.
63
+ #
64
+ # These have been replaced by config.churn, config.flog and
65
+ # config.saikuro respectively.
66
+ class Configuration
67
+
68
+ def initialize #:nodoc:#
69
+ reset
70
+ add_attr_accessors_to_self
71
+ add_class_methods_to_metric_fu
72
+ end
73
+
74
+ # Searches through the instance variables of the class and
75
+ # creates a class method on the MetricFu module to read the value
76
+ # of the instance variable from the Configuration class.
77
+ def add_class_methods_to_metric_fu
78
+ instance_variables.each do |name|
79
+ method_name = name[1..-1].to_sym
80
+ method = <<-EOF
81
+ def self.#{method_name}
82
+ configuration.send(:#{method_name})
83
+ end
84
+ EOF
85
+ MetricFu.module_eval(method)
86
+ end
87
+ end
88
+
89
+ # Searches through the instance variables of the class and creates
90
+ # an attribute accessor on this instance of the Configuration
91
+ # class for each instance variable.
92
+ def add_attr_accessors_to_self
93
+ instance_variables.each do |name|
94
+ method_name = name[1..-1].to_sym
95
+ MetricFu::Configuration.send(:attr_accessor, method_name)
96
+ end
97
+ end
98
+
99
+ # This allows us to have a nice syntax like:
100
+ #
101
+ # MetricFu.run do |config|
102
+ # config.base_directory = 'tmp/metric_fu'
103
+ # end
104
+ #
105
+ # See the README for more information on configuration options.
106
+ def self.run
107
+ yield MetricFu.configuration
108
+ end
109
+
110
+ # This does the real work of the Configuration class, by setting
111
+ # up a bunch of instance variables to represent the configuration
112
+ # of the MetricFu app.
113
+ def reset
114
+ @base_directory = ENV['CC_BUILD_ARTIFACTS'] || 'tmp/metric_fu'
115
+ @scratch_directory = File.join(@base_directory, 'scratch')
116
+ @output_directory = File.join(@base_directory, 'output')
117
+ @data_directory = File.join(@base_directory,'_data')
118
+ @metric_fu_root_directory = File.join(File.dirname(__FILE__),
119
+ '..', '..')
120
+ @template_directory = File.join(@metric_fu_root_directory,
121
+ 'lib', 'templates')
122
+ @template_class = AwesomeTemplate
123
+ set_metrics
124
+ set_graphs
125
+ set_code_dirs
126
+ @flay = { :dirs_to_flay => @code_dirs,
127
+ :minimum_score => 100,
128
+ :filetypes => ['rb'] }
129
+ @flog = { :dirs_to_flog => @code_dirs }
130
+ @reek = { :dirs_to_reek => @code_dirs,
131
+ :config_file_pattern => nil}
132
+ @roodi = { :dirs_to_roodi => @code_dirs,
133
+ :roodi_config => nil}
134
+ @saikuro = { :output_directory => @scratch_directory + '/saikuro',
135
+ :input_directory => @code_dirs,
136
+ :cyclo => "",
137
+ :filter_cyclo => "0",
138
+ :warn_cyclo => "5",
139
+ :error_cyclo => "7",
140
+ :formater => "text"}
141
+ @churn = {}
142
+ @stats = {}
143
+ @rcov = { :environment => 'test',
144
+ :test_files => ['test/**/*_test.rb',
145
+ 'spec/spec_helper.rb', # NOTE: ensure it is loaded before the specs
146
+ 'spec/**/*_spec.rb'],
147
+ :rcov_opts => ["--sort coverage",
148
+ "--no-html",
149
+ "--text-coverage",
150
+ "--no-color",
151
+ "--profile",
152
+ "--rails",
153
+ "--exclude /gems/,/Library/,/usr/,spec"],
154
+ :external => nil
155
+ }
156
+ @rails_best_practices = {}
157
+ @hotspots = {}
158
+ @file_globs_to_ignore = []
159
+ @link_prefix = nil
160
+
161
+
162
+ @verbose = false
163
+
164
+ @graph_engine = :bluff # can be :bluff or :gchart
165
+
166
+ @darwin_txmt_protocol_no_thanks = true
167
+ # uses the CodeRay gem (was syntax gem)
168
+ @syntax_highlighting = true #Can be set to false to avoid UTF-8 issues with Ruby 1.9.2 and Syntax 1.0
169
+ end
170
+
171
+ # Perform a simple check to try and guess if we're running
172
+ # against a rails app.
173
+ #
174
+ # @todo This should probably be made a bit more robust.
175
+ def rails?
176
+ @rails = File.exist?("config/environment.rb")
177
+ end
178
+
179
+ # Add the :stats task to the AVAILABLE_METRICS constant if we're
180
+ # running within rails.
181
+ def set_metrics
182
+ if rails?
183
+ @metrics = MetricFu::AVAILABLE_METRICS + [
184
+ :stats,
185
+ :rails_best_practices
186
+ ]
187
+ else
188
+ @metrics = MetricFu::AVAILABLE_METRICS
189
+ end
190
+ end
191
+
192
+ def set_graphs
193
+ if rails?
194
+ @graphs = MetricFu::AVAILABLE_GRAPHS + [
195
+ :stats
196
+ ]
197
+ else
198
+ @graphs = MetricFu::AVAILABLE_GRAPHS
199
+ end
200
+ end
201
+
202
+ # Add the 'app' directory if we're running within rails.
203
+ def set_code_dirs
204
+ if rails?
205
+ @code_dirs = ['app', 'lib']
206
+ else
207
+ @code_dirs = ['lib']
208
+ end
209
+ end
210
+
211
+ def platform #:nodoc:
212
+ return RUBY_PLATFORM
213
+ end
214
+
215
+ def is_cruise_control_rb?
216
+ !!ENV['CC_BUILD_ARTIFACTS']
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,50 @@
1
+ class FlayAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{flay_reason flay_matching_reason}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :flay
12
+ end
13
+
14
+ def map(row)
15
+ ScoringStrategies.present(row)
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.sum(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.percentile(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data==nil
28
+ Array(data[:matches]).each do |match|
29
+ problems = match[:reason]
30
+ matching_reason = problems.gsub(/^[0-9]+\) /,'').gsub(/\:[0-9]+/,'')
31
+ files = []
32
+ locations = []
33
+ match[:matches].each do |file_match|
34
+ file_path = file_match[:name].sub(%r{^/},'')
35
+ locations << "#{file_path}:#{file_match[:line]}"
36
+ files << file_path
37
+ end
38
+ files = files.uniq
39
+ files.each do |file|
40
+ table << {
41
+ "metric" => self.name,
42
+ "file_path" => file,
43
+ "flay_reason" => problems+" files: #{locations.join(', ')}",
44
+ "flay_matching_reason" => matching_reason
45
+ }
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,43 @@
1
+ class FlogAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{score}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :flog
12
+ end
13
+
14
+ def map(row)
15
+ row.score
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.average(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.identity(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data==nil
28
+ Array(data[:method_containers]).each do |method_container|
29
+ Array(method_container[:methods]).each do |entry|
30
+ file_path = entry[1][:path].sub(%r{^/},'') if entry[1][:path]
31
+ location = MetricFu::Location.for(entry.first)
32
+ table << {
33
+ "metric" => name,
34
+ "score" => entry[1][:score],
35
+ "file_path" => file_path,
36
+ "class_name" => location.class_name,
37
+ "method_name" => location.method_name
38
+ }
39
+ end
40
+ end
41
+ end
42
+
43
+ end