metric_fu 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/Gemfile +1 -1
  2. data/HISTORY.md +12 -1
  3. data/README.md +3 -0
  4. data/TODO.md +0 -41
  5. data/lib/metric_fu.rb +9 -1
  6. data/lib/metric_fu/configuration.rb +9 -1
  7. data/lib/metric_fu/initial_requires.rb +11 -11
  8. data/lib/metric_fu/load_files.rb +1 -0
  9. data/lib/metric_fu/metrics/cane/cane.rb +5 -4
  10. data/lib/metric_fu/metrics/cane/cane_bluff_grapher.rb +15 -0
  11. data/lib/metric_fu/metrics/cane/cane_grapher.rb +1 -0
  12. data/lib/metric_fu/metrics/cane/init.rb +1 -0
  13. data/lib/metric_fu/metrics/churn/churn_hotspot.rb +9 -1
  14. data/lib/metric_fu/metrics/flay/flay.rb +0 -2
  15. data/lib/metric_fu/metrics/flay/flay_bluff_grapher.rb +15 -0
  16. data/lib/metric_fu/metrics/flay/flay_gchart_grapher.rb +17 -0
  17. data/lib/metric_fu/metrics/flay/flay_grapher.rb +1 -0
  18. data/lib/metric_fu/metrics/flay/flay_hotspot.rb +18 -1
  19. data/lib/metric_fu/metrics/flay/init.rb +1 -0
  20. data/lib/metric_fu/metrics/flog/flog.rb +6 -4
  21. data/lib/metric_fu/metrics/flog/flog_bluff_grapher.rb +16 -0
  22. data/lib/metric_fu/metrics/flog/flog_gchart_grapher.rb +21 -0
  23. data/lib/metric_fu/metrics/flog/flog_grapher.rb +1 -0
  24. data/lib/metric_fu/metrics/flog/flog_hotspot.rb +13 -1
  25. data/lib/metric_fu/metrics/flog/init.rb +1 -0
  26. data/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb +73 -0
  27. data/lib/metric_fu/metrics/hotspots/analysis/analyzer_tables.rb +116 -0
  28. data/lib/metric_fu/metrics/hotspots/analysis/groupings.rb +21 -0
  29. data/lib/metric_fu/metrics/hotspots/analysis/problems.rb +21 -0
  30. data/lib/metric_fu/metrics/hotspots/analysis/rankings.rb +81 -0
  31. data/lib/metric_fu/metrics/hotspots/hotspot.rb +29 -0
  32. data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +62 -308
  33. data/lib/metric_fu/metrics/hotspots/hotspots.rb +1 -27
  34. data/lib/metric_fu/metrics/rails_best_practices/rails_best_practices_bluff_grapher.rb +15 -0
  35. data/lib/metric_fu/metrics/rails_best_practices/rails_best_practices_gchart_grapher.rb +21 -0
  36. data/lib/metric_fu/metrics/rails_best_practices/rails_best_practices_grapher.rb +1 -0
  37. data/lib/metric_fu/metrics/rcov/rcov_bluff_grapher.rb +15 -0
  38. data/lib/metric_fu/metrics/rcov/rcov_gchart_grapher.rb +17 -0
  39. data/lib/metric_fu/metrics/rcov/rcov_grapher.rb +1 -0
  40. data/lib/metric_fu/metrics/rcov/rcov_hotspot.rb +28 -16
  41. data/lib/metric_fu/metrics/reek/reek_bluff_grapher.rb +20 -0
  42. data/lib/metric_fu/metrics/reek/reek_gchart_grapher.rb +25 -0
  43. data/lib/metric_fu/metrics/reek/reek_grapher.rb +1 -0
  44. data/lib/metric_fu/metrics/reek/reek_hotspot.rb +16 -1
  45. data/lib/metric_fu/metrics/roodi/roodi_bluff_grapher.rb +15 -0
  46. data/lib/metric_fu/metrics/roodi/roodi_gchart_grapher.rb +17 -0
  47. data/lib/metric_fu/metrics/roodi/roodi_grapher.rb +1 -0
  48. data/lib/metric_fu/metrics/roodi/roodi_hotspot.rb +16 -1
  49. data/lib/metric_fu/metrics/saikuro/saikuro_hotspot.rb +13 -1
  50. data/lib/metric_fu/metrics/stats/stats_bluff_grapher.rb +16 -0
  51. data/lib/metric_fu/metrics/stats/stats_gchart_grapher.rb +20 -0
  52. data/lib/metric_fu/metrics/stats/stats_grapher.rb +1 -0
  53. data/lib/metric_fu/metrics/stats/stats_hotspot.rb +1 -1
  54. data/lib/metric_fu/reporting/graphs/engines/bluff.rb +0 -114
  55. data/lib/metric_fu/reporting/graphs/engines/gchart.rb +0 -123
  56. data/lib/metric_fu/run.rb +1 -0
  57. data/lib/metric_fu/version.rb +1 -1
  58. data/metric_fu.gemspec +1 -0
  59. data/spec/metric_fu/metrics/cane/cane_spec.rb +17 -0
  60. data/spec/metric_fu/metrics/hotspots/hotspot_spec.rb +11 -0
  61. data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +7 -0
  62. metadata +180 -134
@@ -0,0 +1,21 @@
1
+ MetricFu.metrics_require { 'flog/flog_grapher' }
2
+ module MetricFu
3
+ class FlogGchartGrapher < FlogGrapher
4
+ def graph!
5
+ determine_y_axis_scale(@top_five_percent_average + @flog_average)
6
+ url = Gchart.line(
7
+ :size => GCHART_GRAPH_SIZE,
8
+ :title => URI.escape("Flog: code complexity"),
9
+ :data => [@flog_average, @top_five_percent_average],
10
+ :stacked => false,
11
+ :bar_colors => COLORS[0..1],
12
+ :legend => ['average', 'top 5% average'],
13
+ :custom => "chdlp=t",
14
+ :max_value => @max_value,
15
+ :axis_with_labels => 'x,y',
16
+ :axis_labels => [@labels.values, @yaxis],
17
+ :format => 'file',
18
+ :filename => File.join(MetricFu.output_directory, 'flog.png'))
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,4 @@
1
+ MetricFu.reporting_require { 'graphs/grapher' }
1
2
  module MetricFu
2
3
  class FlogGrapher < Grapher
3
4
  attr_accessor :flog_average, :labels, :top_five_percent_average
@@ -1,4 +1,4 @@
1
- class FlogHotspot
1
+ class FlogHotspot < MetricFu::Hotspot
2
2
  include MetricFu::HotspotScoringStrategies
3
3
 
4
4
  COLUMNS = %w{score}
@@ -40,4 +40,16 @@ class FlogHotspot
40
40
  end
41
41
  end
42
42
 
43
+ def present_group(group)
44
+ occurences = group.size
45
+ complexity = get_mean(group.column("score"))
46
+ "#{"average " if occurences > 1}complexity is %.1f" % complexity
47
+ end
48
+
49
+ def present_group_details(group)
50
+ occurences = group.size
51
+ complexity = get_mean(group.column("score"))
52
+ "#{"average " if occurences > 1}complexity is %.1f" % complexity
53
+ end
54
+
43
55
  end
@@ -1,4 +1,5 @@
1
1
  MetricFu::Configuration.run do |config|
2
+ require 'flog'
2
3
  config.add_metric(:flog)
3
4
  config.add_graph(:flog)
4
5
  config.configure_metric(:flog,
@@ -0,0 +1,73 @@
1
+ module MetricFu
2
+ class HotspotAnalyzedProblems
3
+
4
+
5
+ def initialize(hotspot_rankings, analyzer_tables)
6
+ @hotspot_rankings = hotspot_rankings
7
+ @analyzer_tables = analyzer_tables
8
+ end
9
+ def worst_items
10
+ num = nil
11
+ worst_items = {}
12
+ worst_items[:files] =
13
+ @hotspot_rankings.worst_files(num).inject([]) do |array, worst_file|
14
+ array <<
15
+ {:location => self.location(:file, worst_file),
16
+ :details => self.problems_with(:file, worst_file)}
17
+ array
18
+ end
19
+ worst_items[:classes] = @hotspot_rankings.worst_classes(num).inject([]) do |array, class_name|
20
+ location = self.location(:class, class_name)
21
+ array <<
22
+ {:location => location,
23
+ :details => self.problems_with(:class, class_name)}
24
+ array
25
+ end
26
+ worst_items[:methods] = @hotspot_rankings.worst_methods(num).inject([]) do |array, method_name|
27
+ location = self.location(:method, method_name)
28
+ array <<
29
+ {:location => location,
30
+ :details => self.problems_with(:method, method_name)}
31
+ array
32
+ end
33
+ worst_items
34
+ end
35
+ private
36
+ #todo redo as item,value, options = {}
37
+ # Note that the other option for 'details' is :detailed (this isn't
38
+ # at all clear from this method itself
39
+ def problems_with(item, value, details = :summary, exclude_details = [])
40
+ sub_table = get_sub_table(item, value)
41
+ #grouping = Ruport::Data::Grouping.new(sub_table, :by => 'metric')
42
+ grouping = get_grouping(sub_table, :by => 'metric')
43
+ MetricFu::HotspotProblems.new(grouping, details, exclude_details).problems
44
+ end
45
+ def location(item, value)
46
+ sub_table = get_sub_table(item, value)
47
+ if(sub_table.length==0)
48
+ raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
49
+ else
50
+ first_row = sub_table[0]
51
+ case item
52
+ when :class
53
+ MetricFu::Location.get(first_row.file_path, first_row.class_name, nil)
54
+ when :method
55
+ MetricFu::Location.get(first_row.file_path, first_row.class_name, first_row.method_name)
56
+ when :file
57
+ MetricFu::Location.get(first_row.file_path, nil, nil)
58
+ else
59
+ raise ArgumentError, "Item must be :class, :method, or :file"
60
+ end
61
+ end
62
+ end
63
+ # just for testing
64
+ public :location, :problems_with
65
+ def get_sub_table(item, value)
66
+ tables = @analyzer_tables.tables_for(item)
67
+ tables[value]
68
+ end
69
+ def get_grouping(table, opts)
70
+ MetricFu::HotspotGroupings.new(table, opts).get_grouping
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,116 @@
1
+ module MetricFu
2
+ class AnalyzerTables
3
+ %w(table).each do |path|
4
+ MetricFu.metrics_require { "hotspots/analysis/#{path}" }
5
+ end
6
+
7
+ def initialize(analyzer_columns)
8
+ @columns = analyzer_columns
9
+ end
10
+
11
+ def generate_records
12
+ build_lookups!
13
+ process_rows!
14
+ end
15
+
16
+ def tool_tables
17
+ @tool_tables ||= make_table_hash(@columns)
18
+ end
19
+
20
+ def table
21
+ @table ||= make_table(@columns)
22
+ end
23
+
24
+ def tables_for(item)
25
+ {
26
+ :class => @class_tables,
27
+ :method => @method_tables,
28
+ :file => @file_tables,
29
+ :tool => @tool_tables
30
+ }.fetch(item) do
31
+ raise ArgumentError, "Item must be :class, :method, or :file, but was #{item}"
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def make_table(columns)
38
+ MetricFu::Table.new(:column_names => columns)
39
+ end
40
+
41
+ def make_table_hash(columns)
42
+ Hash.new { |hash, key|
43
+ hash[key] = make_table(columns)
44
+ }
45
+ end
46
+
47
+ def build_lookups!
48
+ @class_and_method_to_file ||= {}
49
+ # Build a mapping from [class,method] => filename
50
+ # (and make sure the mapping is unique)
51
+ table.each do |row|
52
+ # We know that Saikuro provides the wrong data
53
+ # TODO inject Saikuro reference
54
+ next if row['metric'] == :saikuro
55
+ key = [row['class_name'], row['method_name']]
56
+ file_path = row['file_path']
57
+ @class_and_method_to_file[key] ||= file_path
58
+ end
59
+ end
60
+
61
+ def process_rows!
62
+ # Correct incorrect rows in the table
63
+ table.each do |row|
64
+ row_metric = row['metric'] #perf optimization
65
+ # TODO inject Saikuro reference
66
+ if row_metric == :saikuro
67
+ fix_row_file_path!(row)
68
+ end
69
+ tool_tables[row_metric] << row
70
+ file_tables[row["file_path"]] << row
71
+ class_tables[row["class_name"]] << row
72
+ method_tables[row["method_name"]] << row
73
+ end
74
+ end
75
+
76
+
77
+ def fix_row_file_path!(row)
78
+ # We know that Saikuro rows are broken
79
+ # next unless row['metric'] == :saikuro
80
+ key = [row['class_name'], row['method_name']]
81
+ current_file_path = row['file_path'].to_s
82
+ correct_file_path = @class_and_method_to_file[key]
83
+ if(correct_file_path!=nil && correct_file_path.include?(current_file_path))
84
+ row['file_path'] = correct_file_path
85
+ else
86
+ # There wasn't an exact match, so we can do a substring match
87
+ matching_file_path = file_paths.detect {|file_path|
88
+ file_path!=nil && file_path.include?(current_file_path)
89
+ }
90
+ if(matching_file_path)
91
+ row['file_path'] = matching_file_path
92
+ end
93
+ end
94
+ end
95
+
96
+ def file_paths
97
+ @file_paths ||= @table.column('file_path').uniq
98
+ end
99
+
100
+ # These tables are an optimization. They contain subsets of the master table.
101
+ # TODO - these should be pushed into the Table class now
102
+ def optimized_tables
103
+ @optimized_tables ||= make_table_hash(@columns)
104
+ end
105
+
106
+ def file_tables
107
+ @file_tables ||= make_table_hash(@columns)
108
+ end
109
+ def class_tables
110
+ @class_tables ||= make_table_hash(@columns)
111
+ end
112
+ def method_tables
113
+ @method_tables ||= make_table_hash(@columns)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,21 @@
1
+ module MetricFu
2
+ class HotspotGroupings
3
+
4
+ def initialize(table, opts)
5
+ @table, @opts = table, opts
6
+ end
7
+ def get_grouping
8
+ #Ruport::Data::Grouping.new(table, opts)
9
+ MetricFu::Grouping.new(@table, @opts)
10
+ #@grouping_cache ||= {}
11
+ #@grouping_cache.fetch(grouping_key(table,opts)) do
12
+ # @grouping_cache[grouping_key(table,opts)] = Ruport::Data::Grouping.new(table, opts)
13
+ #end
14
+ end
15
+
16
+ # UNUSED
17
+ # def grouping_key(table, opts)
18
+ # "table #{table.object_id} opts #{opts.inspect}"
19
+ # end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module MetricFu
2
+ class HotspotProblems
3
+
4
+ def initialize(grouping, details, exclude_details)
5
+ @grouping, @details, @exclude_details = grouping, details, exclude_details
6
+ end
7
+
8
+ def problems
9
+ problems = {}
10
+ @grouping.each do |metric, table|
11
+ if @details == :summary || @exclude_details.include?(metric)
12
+ problems[metric] = MetricFu::Hotspot.analyzer_for_metric(metric).present_group(table)
13
+ else
14
+ problems[metric] = MetricFu::Hotspot.analyzer_for_metric(metric).present_group_details(table)
15
+ end
16
+ end
17
+ problems
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,81 @@
1
+ module MetricFu
2
+ class HotspotRankings
3
+
4
+ def initialize(tool_tables)
5
+ @tool_tables = tool_tables
6
+ @file_ranking = MetricFu::Ranking.new
7
+ @class_ranking = MetricFu::Ranking.new
8
+ @method_ranking = MetricFu::Ranking.new
9
+ end
10
+
11
+ def calculate_scores(tool_analyzers, granularities)
12
+ tool_analyzers.each do |analyzer|
13
+ calculate_scores_by_granularities(analyzer, granularities)
14
+ end
15
+ end
16
+
17
+ def worst_methods(size = nil)
18
+ @method_ranking.delete(nil)
19
+ @method_ranking.top(size)
20
+ end
21
+
22
+ def worst_classes(size = nil)
23
+ @class_ranking.delete(nil)
24
+ @class_ranking.top(size)
25
+ end
26
+
27
+ def worst_files(size = nil)
28
+ @file_ranking.delete(nil)
29
+ @file_ranking.top(size)
30
+ end
31
+
32
+ private
33
+
34
+ def calculate_scores_by_granularities(analyzer, granularities)
35
+ granularities.each do |granularity|
36
+ calculate_score_for_granularity(analyzer, granularity)
37
+ end
38
+ end
39
+
40
+ def calculate_score_for_granularity(analyzer, granularity)
41
+ metric_ranking = calculate_metric_scores(granularity, analyzer)
42
+ add_to_master_ranking(ranking(granularity), metric_ranking, analyzer)
43
+ end
44
+ def calculate_metric_scores(granularity, analyzer)
45
+ metric_ranking = MetricFu::Ranking.new
46
+ metric_violations = @tool_tables[analyzer.name]
47
+ metric_violations.each do |row|
48
+ location = row[granularity]
49
+ metric_ranking[location] ||= []
50
+ metric_ranking[location] << analyzer.map(row)
51
+ end
52
+
53
+ metric_ranking.each do |item, scores|
54
+ metric_ranking[item] = analyzer.reduce(scores)
55
+ end
56
+
57
+ metric_ranking
58
+ end
59
+
60
+ def ranking(column_name)
61
+ case column_name
62
+ when "file_path"
63
+ @file_ranking
64
+ when "class_name"
65
+ @class_ranking
66
+ when "method_name"
67
+ @method_ranking
68
+ else
69
+ raise ArgumentError, "Invalid column name #{column_name}"
70
+ end
71
+ end
72
+
73
+ def add_to_master_ranking(master_ranking, metric_ranking, analyzer)
74
+ metric_ranking.each do |item, _|
75
+ master_ranking[item] ||= 0
76
+ master_ranking[item] += analyzer.score(metric_ranking, item) # scaling? Do we just add in the raw score?
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ module MetricFu
2
+ class Hotspot
3
+ def self.metric
4
+ self.name.split('Hotspot')[0].downcase.to_sym
5
+ end
6
+ @analyzers = {}
7
+ def self.analyzers
8
+ @analyzers.values
9
+ end
10
+ def self.analyzer_for_metric(metric)
11
+ mf_debug "Getting analyzer for #{metric}"
12
+ @analyzers.fetch(metric.to_sym) {
13
+ raise MetricFu::AnalysisError, "Unknown metric #{metric}. We only know #{@analyzers.keys.inspect}"
14
+ }
15
+ end
16
+ def self.inherited(subclass)
17
+ mf_debug "Adding #{subclass} to #{@analyzers.inspect}"
18
+ @analyzers[subclass.metric] = subclass.new
19
+ end
20
+
21
+ # TODO simplify calculation
22
+ def get_mean(collection)
23
+ collection_length = collection.length
24
+ sum = 0
25
+ sum = collection.inject( nil ) { |sum,x| sum ? sum+x : x }
26
+ (sum.to_f / collection_length.to_f)
27
+ end
28
+ end
29
+ end
@@ -1,11 +1,9 @@
1
1
  require File.expand_path('analysis_error', MetricFu.errors_dir)
2
2
  MetricFu.data_structures_require { 'location' }
3
- %w(table record grouping ranking).each do |path|
3
+ %w(table record grouping ranking problems).each do |path|
4
4
  MetricFu.metrics_require { "hotspots/analysis/#{path}" }
5
5
  end
6
- %w(reek roodi flog churn saikuro flay stats rcov).each do |path|
7
- MetricFu.metrics_require { "#{path}/#{path}_hotspot" }
8
- end
6
+ MetricFu.metrics_require { 'hotspots/hotspot' }
9
7
 
10
8
  module MetricFu
11
9
  class HotspotAnalyzer
@@ -13,321 +11,77 @@ module MetricFu
13
11
  COMMON_COLUMNS = %w{metric}
14
12
  GRANULARITIES = %w{file_path class_name method_name}
15
13
 
16
- attr_accessor :table
17
-
18
- def initialize(yaml)
19
- if(yaml.is_a?(String))
20
- @yaml = YAML.load(yaml)
21
- else
22
- @yaml = yaml
23
- end
24
- @file_ranking = MetricFu::Ranking.new
25
- @class_ranking = MetricFu::Ranking.new
26
- @method_ranking = MetricFu::Ranking.new
27
- rankings = [@file_ranking, @class_ranking, @method_ranking]
28
-
29
- tool_analyzers = [ReekHotspot.new, RoodiHotspot.new,
30
- FlogHotspot.new, ChurnHotspot.new, SaikuroHotspot.new,
31
- FlayHotspot.new, StatsHotspot.new, RcovHotspot.new]
32
- # TODO There is likely a clash that will happen between
33
- # column names eventually. We should probably auto-prefix
34
- # them (e.g. "roodi_problem")
35
- columns = COMMON_COLUMNS + GRANULARITIES + tool_analyzers.map{|analyzer| analyzer.columns}.flatten
36
-
37
- @table = make_table(columns)
38
-
39
- # These tables are an optimization. They contain subsets of the master table.
40
- # TODO - these should be pushed into the Table class now
41
- @tool_tables = make_table_hash(columns)
42
- @file_tables = make_table_hash(columns)
43
- @class_tables = make_table_hash(columns)
44
- @method_tables = make_table_hash(columns)
45
-
46
- tool_analyzers.each do |analyzer|
47
- analyzer.generate_records(@yaml[analyzer.name], @table)
48
- end
49
-
50
- build_lookups!(table)
51
- process_rows!(table)
52
-
53
- tool_analyzers.each do |analyzer|
54
- GRANULARITIES.each do |granularity|
55
- metric_ranking = calculate_metric_scores(granularity, analyzer)
56
- add_to_master_ranking(ranking(granularity), metric_ranking, analyzer)
57
- end
58
- end
59
-
60
- rankings.each do |ranking|
61
- ranking.delete(nil)
62
- end
63
- end
64
-
65
- def location(item, value)
66
- sub_table = get_sub_table(item, value)
67
- if(sub_table.length==0)
68
- raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
69
- else
70
- first_row = sub_table[0]
71
- case item
72
- when :class
73
- MetricFu::Location.get(first_row.file_path, first_row.class_name, nil)
74
- when :method
75
- MetricFu::Location.get(first_row.file_path, first_row.class_name, first_row.method_name)
76
- when :file
77
- MetricFu::Location.get(first_row.file_path, nil, nil)
78
- else
79
- raise ArgumentError, "Item must be :class, :method, or :file"
80
- end
81
- end
82
- end
14
+ # UNUSED
15
+ # attr_accessor :table
83
16
 
84
- #todo redo as item,value, options = {}
85
- # Note that the other option for 'details' is :detailed (this isn't
86
- # at all clear from this method itself
87
- def problems_with(item, value, details = :summary, exclude_details = [])
88
- sub_table = get_sub_table(item, value)
89
- #grouping = Ruport::Data::Grouping.new(sub_table, :by => 'metric')
90
- grouping = get_grouping(sub_table, :by => 'metric')
91
- problems = {}
92
- grouping.each do |metric, table|
93
- if details == :summary || exclude_details.include?(metric)
94
- problems[metric] = present_group(metric,table)
95
- else
96
- problems[metric] = present_group_details(metric,table)
97
- end
98
- end
99
- problems
17
+ def tool_analyzers
18
+ MetricFu::Hotspot.analyzers
100
19
  end
101
20
 
102
- def worst_methods(size = nil)
103
- @method_ranking.top(size)
21
+ def initialize(report_hash)
22
+ # we can't depend on the Report
23
+ # returning a parsed yaml file as a hash?
24
+ report_hash = YAML::load(report_hash) if report_hash.is_a?(String)
25
+ setup(report_hash)
104
26
  end
105
27
 
106
- def worst_classes(size = nil)
107
- @class_ranking.top(size)
28
+ # def worst_items
29
+ def hotspots
30
+ analyzed_problems.worst_items
108
31
  end
109
-
110
- def worst_files(size = nil)
111
- @file_ranking.top(size)
32
+ # just for testing
33
+ def analyzed_problems
34
+ @analyzed_problems = MetricFu::HotspotAnalyzedProblems.new(@rankings, @analyzer_tables)
112
35
  end
113
-
36
+ alias_method :worst_items, :hotspots
37
+ extend Forwardable
38
+ def_delegators :@analyzer_tables, :table
39
+ def_delegators :@analyzed_problems, :problems_with, :location
40
+ def_delegators :@rankings, :worst_files, :worst_methods, :worst_classes
114
41
  private
115
42
 
116
- def get_grouping(table, opts)
117
- #Ruport::Data::Grouping.new(table, opts)
118
- MetricFu::Grouping.new(table, opts)
119
- #@grouping_cache ||= {}
120
- #@grouping_cache.fetch(grouping_key(table,opts)) do
121
- # @grouping_cache[grouping_key(table,opts)] = Ruport::Data::Grouping.new(table, opts)
122
- #end
123
- end
124
-
125
- def grouping_key(table, opts)
126
- "table #{table.object_id} opts #{opts.inspect}"
127
- end
128
-
129
- def build_lookups!(table)
130
- @class_and_method_to_file ||= {}
131
- # Build a mapping from [class,method] => filename
132
- # (and make sure the mapping is unique)
133
- table.each do |row|
134
- # We know that Saikuro provides the wrong data
135
- next if row['metric'] == :saikuro
136
- key = [row['class_name'], row['method_name']]
137
- file_path = row['file_path']
138
- @class_and_method_to_file[key] ||= file_path
139
- end
140
- end
141
-
142
- def process_rows!(table)
143
- # Correct incorrect rows in the table
144
- table.each do |row|
145
- row_metric = row['metric'] #perf optimization
146
- if row_metric == :saikuro
147
- fix_row_file_path!(row)
148
- end
149
- @tool_tables[row_metric] << row
150
- @file_tables[row["file_path"]] << row
151
- @class_tables[row["class_name"]] << row
152
- @method_tables[row["method_name"]] << row
153
- end
154
- end
155
-
156
- def fix_row_file_path!(row)
157
- # We know that Saikuro rows are broken
158
- # next unless row['metric'] == :saikuro
159
- key = [row['class_name'], row['method_name']]
160
- current_file_path = row['file_path'].to_s
161
- correct_file_path = @class_and_method_to_file[key]
162
- if(correct_file_path!=nil && correct_file_path.include?(current_file_path))
163
- row['file_path'] = correct_file_path
164
- else
165
- # There wasn't an exact match, so we can do a substring match
166
- matching_file_path = file_paths.detect {|file_path|
167
- file_path!=nil && file_path.include?(current_file_path)
168
- }
169
- if(matching_file_path)
170
- row['file_path'] = matching_file_path
171
- end
172
- end
173
- end
174
-
175
- def file_paths
176
- @file_paths ||= @table.column('file_path').uniq
177
- end
178
-
179
- def ranking(column_name)
180
- case column_name
181
- when "file_path"
182
- @file_ranking
183
- when "class_name"
184
- @class_ranking
185
- when "method_name"
186
- @method_ranking
187
- else
188
- raise ArgumentError, "Invalid column name #{column_name}"
189
- end
190
- end
191
-
192
- def calculate_metric_scores(granularity, analyzer)
193
- metric_ranking = MetricFu::Ranking.new
194
- metric_violations = @tool_tables[analyzer.name]
195
- metric_violations.each do |row|
196
- location = row[granularity]
197
- metric_ranking[location] ||= []
198
- metric_ranking[location] << analyzer.map(row)
199
- end
200
-
201
- metric_ranking.each do |item, scores|
202
- metric_ranking[item] = analyzer.reduce(scores)
203
- end
204
-
205
- metric_ranking
206
- end
207
-
208
- def add_to_master_ranking(master_ranking, metric_ranking, analyzer)
209
- metric_ranking.each do |item, _|
210
- master_ranking[item] ||= 0
211
- master_ranking[item] += analyzer.score(metric_ranking, item) # scaling? Do we just add in the raw score?
212
- end
213
- end
214
-
215
- def most_common_column(column_name, size)
216
- #grouping = Ruport::Data::Grouping.new(@table,
217
- # :by => column_name,
218
- # :order => lambda { |g| -g.size})
219
- get_grouping(@table, :by => column_name, :order => lambda {|g| -g.size})
220
- values = []
221
- grouping.each do |value, _|
222
- values << value if value!=nil
223
- if(values.size==size)
224
- break
225
- end
226
- end
227
- return nil if values.empty?
228
- if(values.size == 1)
229
- return values.first
230
- else
231
- return values
232
- end
233
- end
234
-
235
- # TODO: As we get fancier, the presenter should
236
- # be its own class, not just a method with a long
237
- # case statement
238
- def present_group(metric, group)
239
- occurences = group.size
240
- case(metric)
241
- when :reek
242
- "found #{occurences} code smells"
243
- when :roodi
244
- "found #{occurences} design problems"
245
- when :churn
246
- "detected high level of churn (changed #{group[0].times_changed} times)"
247
- when :flog
248
- complexity = get_mean(group.column("score"))
249
- "#{"average " if occurences > 1}complexity is %.1f" % complexity
250
- when :saikuro
251
- complexity = get_mean(group.column("complexity"))
252
- "#{"average " if occurences > 1}complexity is %.1f" % complexity
253
- when :flay
254
- "found #{occurences} code duplications"
255
- when :rcov
256
- average_code_uncoverage = get_mean(group.column("percentage_uncovered"))
257
- "#{"average " if occurences > 1}uncovered code is %.1f%" % average_code_uncoverage
258
- else
259
- raise MetricFu::AnalysisError, "Unknown metric #{metric}"
260
- end
261
- end
262
-
263
- def present_group_details(metric, group)
264
- occurences = group.size
265
- case(metric)
266
- when :reek
267
- message = "found #{occurences} code smells<br/>"
268
- group.each do |item|
269
- type = item.data["reek__type_name"]
270
- reek_message = item.data["reek__message"]
271
- message << "* #{type}: #{reek_message}<br/>"
272
- end
273
- message
274
- when :roodi
275
- message = "found #{occurences} design problems<br/>"
276
- group.each do |item|
277
- problem = item.data["problems"]
278
- message << "* #{problem}<br/>"
279
- end
280
- message
281
- when :churn
282
- "detected high level of churn (changed #{group[0].times_changed} times)"
283
- when :flog
284
- complexity = get_mean(group.column("score"))
285
- "#{"average " if occurences > 1}complexity is %.1f" % complexity
286
- when :saikuro
287
- complexity = get_mean(group.column("complexity"))
288
- "#{"average " if occurences > 1}complexity is %.1f" % complexity
289
- when :flay
290
- message = "found #{occurences} code duplications<br/>"
291
- group.each do |item|
292
- problem = item.data["flay_reason"]
293
- problem = problem.gsub(/^[0-9]*\)/,'')
294
- problem = problem.gsub(/files\:/,' <br>&nbsp;&nbsp;&nbsp;files:')
295
- message << "* #{problem}<br/>"
296
- end
297
- message
298
- else
299
- raise MetricFu::AnalysisError, "Unknown metric #{metric}"
300
- end
301
- end
302
-
303
- def make_table_hash(columns)
304
- Hash.new { |hash, key|
305
- hash[key] = make_table(columns)
306
- }
307
- end
308
-
309
- def make_table(columns)
310
- MetricFu::Table.new(:column_names => columns)
311
- end
43
+ def setup(report_hash)
44
+ # TODO There is likely a clash that will happen between
45
+ # column names eventually. We should probably auto-prefix
46
+ # them (e.g. "roodi_problem")
47
+ analyzer_columns = COMMON_COLUMNS + GRANULARITIES + tool_analyzers.map{|analyzer| analyzer.columns}.flatten
48
+ # though the tool_analyzers aren't returned, they are processed in
49
+ # various places here, then by the analyzer tables
50
+ # then by the rankings
51
+ # to ultimately generate the hotspots
52
+ @analyzer_tables = MetricFu::AnalyzerTables.new(analyzer_columns)
53
+ tool_analyzers.each do |analyzer|
54
+ analyzer.generate_records(report_hash[analyzer.name], @analyzer_tables.table)
55
+ end
56
+ @analyzer_tables.generate_records
57
+ @rankings = MetricFu::HotspotRankings.new(@analyzer_tables.tool_tables)
58
+ @rankings.calculate_scores(tool_analyzers, GRANULARITIES)
59
+ # just for testing
60
+ analyzed_problems
61
+ end
62
+
63
+ # UNUSED
64
+ # def most_common_column(column_name, size)
65
+ # #grouping = Ruport::Data::Grouping.new(@table,
66
+ # # :by => column_name,
67
+ # # :order => lambda { |g| -g.size})
68
+ # get_grouping(@table, :by => column_name, :order => lambda {|g| -g.size})
69
+ # values = []
70
+ # grouping.each do |value, _|
71
+ # values << value if value!=nil
72
+ # if(values.size==size)
73
+ # break
74
+ # end
75
+ # end
76
+ # return nil if values.empty?
77
+ # if(values.size == 1)
78
+ # return values.first
79
+ # else
80
+ # return values
81
+ # end
82
+ # end
312
83
 
313
- def get_sub_table(item, value)
314
- tables = {
315
- :class => @class_tables,
316
- :method => @method_tables,
317
- :file => @file_tables,
318
- :tool => @tool_tables
319
- }.fetch(item) do
320
- raise ArgumentError, "Item must be :class, :method, or :file"
321
- end
322
- tables[value]
323
- end
324
84
 
325
- def get_mean(collection)
326
- collection_length = collection.length
327
- sum = 0
328
- sum = collection.inject( nil ) { |sum,x| sum ? sum+x : x }
329
- (sum.to_f / collection_length.to_f)
330
- end
331
85
 
332
86
  end
333
87
  end