metric_fu 4.4.1 → 4.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +14 -6
  2. data/CONTRIBUTORS +1 -0
  3. data/Gemfile +1 -1
  4. data/HISTORY.md +13 -0
  5. data/checksum/metric_fu-4.4.1.gem.sha512 +1 -0
  6. data/lib/metric_fu/cli/parser.rb +7 -0
  7. data/lib/metric_fu/data_structures/line_numbers.rb +70 -59
  8. data/lib/metric_fu/data_structures/location.rb +36 -20
  9. data/lib/metric_fu/data_structures/sexp_node.rb +89 -0
  10. data/lib/metric_fu/environment.rb +48 -0
  11. data/lib/metric_fu/io.rb +1 -7
  12. data/lib/metric_fu/metrics/base_template.rb +28 -9
  13. data/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb +8 -44
  14. data/lib/metric_fu/metrics/hotspots/analysis/analyzer_tables.rb +3 -0
  15. data/lib/metric_fu/metrics/hotspots/analysis/problems.rb +6 -2
  16. data/lib/metric_fu/metrics/hotspots/analysis/ranked_problem_location.rb +71 -0
  17. data/lib/metric_fu/metrics/hotspots/analysis/rankings.rb +8 -1
  18. data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +0 -1
  19. data/lib/metric_fu/metrics/hotspots/hotspots.rb +8 -3
  20. data/lib/metric_fu/metrics/hotspots/template_awesome/hotspots.html.erb +12 -12
  21. data/lib/metric_fu/metrics/saikuro/init.rb +3 -0
  22. data/lib/metric_fu/metrics/saikuro/parsing_element.rb +39 -0
  23. data/lib/metric_fu/metrics/saikuro/saikuro.rb +4 -145
  24. data/lib/metric_fu/metrics/saikuro/scratch_file.rb +114 -0
  25. data/lib/metric_fu/version.rb +1 -1
  26. data/spec/metric_fu/configuration_spec.rb +0 -7
  27. data/spec/metric_fu/formatter/html_spec.rb +0 -1
  28. data/spec/metric_fu/formatter/yaml_spec.rb +0 -1
  29. data/spec/metric_fu/metrics/base_template_spec.rb +1 -2
  30. data/spec/metric_fu/metrics/hotspots/analysis/analyzed_problems_spec.rb +8 -16
  31. data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +26 -69
  32. data/spec/metric_fu/metrics/saikuro/saikuro_spec.rb +5 -5
  33. data/spec/metric_fu/reporting/graphs/engines/gchart_spec.rb +1 -0
  34. data/spec/resources/yml/hotspots/generator.yml +47 -0
  35. data/spec/resources/yml/hotspots/generator_analysis.yml +53 -0
  36. data/spec/run_spec.rb +0 -1
  37. data/spec/support/helper_methods.rb +10 -0
  38. data/spec/support/suite.rb +13 -14
  39. metadata +35 -25
@@ -1,6 +1,8 @@
1
+ MetricFu.metrics_require { 'saikuro/scratch_file' }
2
+ MetricFu.metrics_require { 'saikuro/parsing_element' }
1
3
  module MetricFu
2
4
 
3
- class Saikuro < Generator
5
+ class SaikuroGenerator < MetricFu::Generator
4
6
 
5
7
  def self.metric
6
8
  :saikuro
@@ -102,152 +104,9 @@ module MetricFu
102
104
  end
103
105
 
104
106
  def assemble_files
105
- files = []
106
- Dir.glob("#{metric_directory}/**/*.html").each do |path|
107
- if Saikuro::SFile.is_valid_text_file?(path)
108
- file = Saikuro::SFile.new(path)
109
- if file
110
- files << file
111
- end
112
- end
113
- end
114
- files
107
+ SaikuroScratchFile.assemble_files( Dir.glob("#{metric_directory}/**/*.html") )
115
108
  end
116
109
 
117
110
  end
118
111
 
119
- class Saikuro::SFile
120
-
121
- attr_reader :elements
122
-
123
- def initialize(path)
124
- @path = path
125
- @file_handle = File.open(@path, "r")
126
- @elements = []
127
- get_elements
128
- ensure
129
- @file_handle.close if @file_handle
130
- end
131
-
132
- def self.is_valid_text_file?(path)
133
- File.open(path, "r") do |f|
134
- if f.eof? || !f.readline.match(/--/)
135
- return false
136
- else
137
- return true
138
- end
139
- end
140
- end
141
-
142
- def filename
143
- File.basename(@path, '_cyclo.html')
144
- end
145
-
146
- def filepath
147
- File.dirname(@path)
148
- end
149
-
150
- def to_h
151
- merge_classes
152
- {:classes => @elements}
153
- end
154
-
155
- def get_elements
156
- begin
157
- while (line = @file_handle.readline) do
158
- return [] if line.nil? || line !~ /\S/
159
- element ||= nil
160
- if line.match(/START/)
161
- unless element.nil?
162
- @elements << element
163
- element = nil
164
- end
165
- line = @file_handle.readline
166
- element = Saikuro::ParsingElement.new(line)
167
- elsif line.match(/END/)
168
- @elements << element if element
169
- element = nil
170
- else
171
- element << line if element
172
- end
173
- end
174
- rescue EOFError
175
- nil
176
- end
177
- end
178
-
179
-
180
- def merge_classes
181
- new_elements = []
182
- get_class_names.each do |target_class|
183
- elements = @elements.find_all {|el| el.name == target_class }
184
- complexity = 0
185
- lines = 0
186
- defns = []
187
- elements.each do |el|
188
- complexity += el.complexity.to_i
189
- lines += el.lines.to_i
190
- defns << el.defs
191
- end
192
-
193
- new_element = {:class_name => target_class,
194
- :complexity => complexity,
195
- :lines => lines,
196
- :methods => defns.flatten.map {|d| d.to_h}}
197
- new_element[:methods] = new_element[:methods].
198
- sort_by {|x| x[:complexity] }.
199
- reverse
200
-
201
- new_elements << new_element
202
- end
203
- @elements = new_elements if new_elements
204
- end
205
-
206
- def get_class_names
207
- class_names = []
208
- @elements.each do |element|
209
- unless class_names.include?(element.name)
210
- class_names << element.name
211
- end
212
- end
213
- class_names
214
- end
215
-
216
- end
217
-
218
- class Saikuro::ParsingElement
219
- TYPE_REGEX=/Type:(.*) Name/
220
- NAME_REGEX=/Name:(.*) Complexity/
221
- COMPLEXITY_REGEX=/Complexity:(.*) Lines/
222
- LINES_REGEX=/Lines:(.*)/
223
-
224
- attr_reader :complexity, :lines, :defs, :element_type
225
- attr_accessor :name
226
-
227
- def initialize(line)
228
- @line = line
229
- @element_type = line.match(TYPE_REGEX)[1].strip
230
- @name = line.match(NAME_REGEX)[1].strip
231
- @complexity = line.match(COMPLEXITY_REGEX)[1].strip
232
- @lines = line.match(LINES_REGEX)[1].strip
233
- @defs = []
234
- end
235
-
236
- def <<(line)
237
- @defs << Saikuro::ParsingElement.new(line)
238
- end
239
-
240
- def to_h
241
- base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
242
- unless @defs.empty?
243
- defs = @defs.map do |my_def|
244
- my_def = my_def.to_h
245
- my_def.delete(:defs)
246
- my_def
247
- end
248
- base[:defs] = defs
249
- end
250
- return base
251
- end
252
- end
253
112
  end
@@ -0,0 +1,114 @@
1
+ MetricFu.metrics_require { 'saikuro/parsing_element' }
2
+ module MetricFu
3
+ class SaikuroScratchFile
4
+
5
+ def self.assemble_files(glob)
6
+ files = []
7
+ glob.each do |path|
8
+ if is_valid_text_file?(path)
9
+ file = new(path)
10
+ if file
11
+ files << file
12
+ end
13
+ end
14
+ end
15
+ files
16
+ end
17
+
18
+ attr_reader :elements
19
+
20
+ def initialize(path)
21
+ @path = path
22
+ @file_handle = File.open(@path, "r")
23
+ @elements = []
24
+ get_elements
25
+ ensure
26
+ @file_handle.close if @file_handle
27
+ end
28
+
29
+ def self.is_valid_text_file?(path)
30
+ File.open(path, "r") do |f|
31
+ if f.eof? || !f.readline.match(/--/)
32
+ return false
33
+ else
34
+ return true
35
+ end
36
+ end
37
+ end
38
+
39
+ def filename
40
+ File.basename(@path, '_cyclo.html')
41
+ end
42
+
43
+ def filepath
44
+ File.dirname(@path)
45
+ end
46
+
47
+ def to_h
48
+ merge_classes
49
+ {:classes => @elements}
50
+ end
51
+
52
+ def get_elements
53
+ begin
54
+ while (line = @file_handle.readline) do
55
+ return [] if line.nil? || line !~ /\S/
56
+ element ||= nil
57
+ if line.match(/START/)
58
+ unless element.nil?
59
+ @elements << element
60
+ element = nil
61
+ end
62
+ line = @file_handle.readline
63
+ element = MetricFu::SaikuroParsingElement.new(line)
64
+ elsif line.match(/END/)
65
+ @elements << element if element
66
+ element = nil
67
+ else
68
+ element << line if element
69
+ end
70
+ end
71
+ rescue EOFError
72
+ nil
73
+ end
74
+ end
75
+
76
+
77
+ def merge_classes
78
+ new_elements = []
79
+ get_class_names.each do |target_class|
80
+ elements = @elements.find_all {|el| el.name == target_class }
81
+ complexity = 0
82
+ lines = 0
83
+ defns = []
84
+ elements.each do |el|
85
+ complexity += el.complexity.to_i
86
+ lines += el.lines.to_i
87
+ defns << el.defs
88
+ end
89
+
90
+ new_element = {:class_name => target_class,
91
+ :complexity => complexity,
92
+ :lines => lines,
93
+ :methods => defns.flatten.map {|d| d.to_h}}
94
+ new_element[:methods] = new_element[:methods].
95
+ sort_by {|x| x[:complexity] }.
96
+ reverse
97
+
98
+ new_elements << new_element
99
+ end
100
+ @elements = new_elements if new_elements
101
+ end
102
+
103
+ def get_class_names
104
+ class_names = []
105
+ @elements.each do |element|
106
+ unless class_names.include?(element.name)
107
+ class_names << element.name
108
+ end
109
+ end
110
+ class_names
111
+ end
112
+
113
+ end
114
+ end
@@ -1,3 +1,3 @@
1
1
  module MetricFu
2
- VERSION = "4.4.1"
2
+ VERSION = "4.4.2"
3
3
  end
@@ -225,13 +225,6 @@ describe MetricFu::Configuration do
225
225
  end
226
226
  end
227
227
 
228
- describe '#set_code_dirs ' do
229
- it 'should set the @code_dirs instance var to ["app", "lib"]' do
230
- directory('code_dirs').
231
- should == ['app','lib']
232
- end
233
- end
234
-
235
228
  it 'should set @rails_best_practices to {}' do
236
229
  load_metric 'rails_best_practices'
237
230
  expect(MetricFu::Metric.get_metric(:rails_best_practices).run_options).to eql({})
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require 'fakefs/safe'
3
2
 
4
3
  describe MetricFu::Formatter::HTML do
5
4
 
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require 'fakefs/safe'
3
2
 
4
3
  describe MetricFu::Formatter::YAML do
5
4
 
@@ -10,11 +10,10 @@ describe MetricFu::Template do
10
10
  describe "#erbify" do
11
11
  it 'should evaluate a erb doc' do
12
12
  section = 'section'
13
- File.stub(:read).and_return('foo')
14
13
  erb = double('erb')
15
14
  erb.should_receive(:result)
16
- ERB.should_receive(:new).with('foo').and_return(erb)
17
15
  @template.should_receive(:template).and_return('foo')
16
+ @template.should_receive(:erb_template_source).with('foo').and_return(erb)
18
17
  @template.send(:erbify, section)
19
18
  end
20
19
  end
@@ -37,16 +37,14 @@ describe MetricFu::HotspotAnalyzedProblems do
37
37
  :flog => "complexity is 37.9"
38
38
  }
39
39
  # TODO Unsure if we want to make problems_with and location public or private at this point
40
- @analyzed_problems.method(:problems_with).call(:class, "Client").should == expected
41
- expect(@worst_items[:classes].first[:details]).to eq(expected)
40
+ expect(@worst_items[:classes].first.problems).to eq(expected)
42
41
  end
43
42
 
44
43
  it "gives all issues for a method" do
45
44
  expected = {
46
45
  :reek => "found 1 code smells",
47
46
  :flog => "complexity is 37.9"}
48
- @analyzed_problems.method(:problems_with).call(:method, "Client#client_requested_sync").should == expected
49
- expect(@worst_items[:methods].first[:details]).to eq(expected)
47
+ expect(@worst_items[:methods].first.problems).to eq(expected)
50
48
  end
51
49
 
52
50
  it "gives all issues for a file" do
@@ -54,32 +52,28 @@ describe MetricFu::HotspotAnalyzedProblems do
54
52
  :reek => "found 2 code smells" ,
55
53
  :flog => "complexity is 37.9",
56
54
  :churn => "detected high level of churn (changed 54 times)"}
57
- @analyzed_problems.method(:problems_with).call(:file, "lib/client/client.rb").should == expected
58
- expect(@worst_items[:files].first[:details]).to eq(expected)
55
+ expect(@worst_items[:files].first.problems).to eq(expected)
59
56
  end
60
57
 
61
58
  it "provide location for a method" do
62
59
  expected = MetricFu::Location.new("lib/client/client.rb",
63
60
  "Client",
64
61
  "Client#client_requested_sync")
65
- @analyzed_problems.method(:location).call(:method, "Client#client_requested_sync").should == expected
66
- expect(@worst_items[:methods].first[:location]).to eq(expected)
62
+ expect(@worst_items[:methods].first.location).to eq(expected)
67
63
  end
68
64
 
69
65
  it "provides location for a class" do
70
66
  expected = MetricFu::Location.new("lib/client/client.rb",
71
67
  "Client",
72
68
  nil)
73
- @analyzed_problems.method(:location).call(:class, "Client").should == expected
74
- expect(@worst_items[:classes].first[:location]).to eq(expected)
69
+ expect(@worst_items[:classes].first.location).to eq(expected)
75
70
  end
76
71
 
77
72
  it "provides location for a file" do
78
73
  expected = MetricFu::Location.new("lib/client/client.rb",
79
74
  nil,
80
75
  nil)
81
- @analyzed_problems.method(:location).call(:file, "lib/client/client.rb").should == expected
82
- expect(@worst_items[:files].first[:location]).to eq(expected)
76
+ expect(@worst_items[:files].first.location).to eq(expected)
83
77
  end
84
78
 
85
79
  end
@@ -96,16 +90,14 @@ describe MetricFu::HotspotAnalyzedProblems do
96
90
  expected = {
97
91
  :saikuro => "complexity is 1.0"
98
92
  }
99
- @analyzed_problems.method(:problems_with).call(:method, "Supr#initialize").should == expected
100
- expect(@worst_items[:methods].last[:details]).to eq(expected)
93
+ expect(@worst_items[:methods].last.problems).to eq(expected)
101
94
  end
102
95
 
103
96
  it "gives average complexity for class" do
104
97
  expected = {
105
98
  :saikuro => "average complexity is 5.0"
106
99
  }
107
- @analyzed_problems.method(:problems_with).call(:class, "Supr").should == expected
108
- expect(@worst_items[:classes].last[:details]).to eq(expected)
100
+ expect(@worst_items[:classes].last.problems).to eq(expected)
109
101
  end
110
102
 
111
103
  end
@@ -1,4 +1,3 @@
1
- require 'multi_json'
2
1
  require "spec_helper"
3
2
 
4
3
  describe MetricFu::HotspotsGenerator do
@@ -7,90 +6,48 @@ describe MetricFu::HotspotsGenerator do
7
6
  before :each do
8
7
  MetricFu::Configuration.run {}
9
8
  File.stub(:directory?).and_return(true)
10
- @yaml =<<END
11
- ---
12
- :reek:
13
- :matches:
14
- - :file_path: lib/client/client.rb
15
- :code_smells:
16
- - :type: Large Class
17
- :message: has at least 27 methods
18
- :method: Devver::Client
19
- - :type: Long Method
20
- :message: has approx 6 statements
21
- :method: Devver::Client#client_requested_sync
22
- :flog:
23
- :method_containers:
24
- - :highest_score: 61.5870319141946
25
- :path: /lib/client/client.rb
26
- :methods:
27
- Client#client_requested_sync:
28
- :path: /lib/client/client.rb
29
- :score: 37.9270319141946
30
- :operators:
31
- :+: 1.70000000000001
32
- :/: 1.80000000000001
33
- :method_at_line: 1.90000000000001
34
- :puts: 1.70000000000001
35
- :assignment: 33.0000000000001
36
- :in_method?: 1.70000000000001
37
- :message: 1.70000000000001
38
- :branch: 12.6
39
- :<<: 3.40000000000001
40
- :each: 1.50000000000001
41
- :lit_fixnum: 1.45
42
- :raise: 1.80000000000001
43
- :each_pair: 1.3
44
- :*: 1.60000000000001
45
- :to_f: 2.00000000000001
46
- :each_with_index: 3.00000000000001
47
- :[]: 22.3000000000001
48
- :new: 1.60000000000001
49
- :average_score: 11.1209009055421
50
- :total_score: 1817.6
51
- :name: Client#client_requested_sync
52
- :churn:
53
- :changes:
54
- - :file_path: lib/client/client.rb
55
- :times_changed: 54
56
- - :file_path: lib/client/foo.rb
57
- :times_changed: 52
58
- END
9
+ @yaml = metric_data('hotspots/generator.yml')
59
10
  end
60
11
 
61
12
  it "should be empty on error" do
62
13
  hotspots = MetricFu::HotspotsGenerator.new
63
14
  hotspots.instance_variable_set(:@analyzer, nil)
64
15
  result = hotspots.analyze
65
- result.should == {}
66
- end
67
-
68
- it "should return yaml results" do
69
- hotspots = MetricFu::HotspotsGenerator.new
70
- analyzer = HotspotAnalyzer.new(@yaml)
71
- hotspots.instance_variable_set(:@analyzer, analyzer)
72
- result = hotspots.analyze
73
- expected = MultiJson.load("{\"methods\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":\"Client#client_requested_sync\",\"file_path\":\"lib/client/client.rb\",\"hash\":7919384682,\"simple_method_name\":\"#client_requested_sync\"},\"details\":{\"reek\":\"found 1 code smells\",\"flog\":\"complexity is 37.9\"}}],\"classes\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":7995629750},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\"}}],\"files\":[{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":-5738801681},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\",\"churn\":\"detected high level of churn (changed 54 times)\"}},{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/foo.rb\",\"hash\":-7081271905},\"details\":{\"churn\":\"detected high level of churn (changed 52 times)\"}}]}")
74
- compare_hashes(MultiJson.load(MultiJson.dump(hotspots.to_h[:hotspots])), expected)
16
+ result.should == {:files => [], :classes => [], :methods => []}
75
17
  end
76
18
 
77
19
  it "should put the changes into a hash" do
20
+ MetricFu.result.should_receive(:result_hash).and_return(@yaml)
78
21
  hotspots = MetricFu::HotspotsGenerator.new
79
- analyzer = HotspotAnalyzer.new(@yaml)
80
- hotspots.instance_variable_set(:@analyzer, analyzer)
81
22
  hotspots.analyze
82
- expected = MultiJson.load("{\"methods\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":\"Client#client_requested_sync\",\"file_path\":\"lib/client/client.rb\",\"hash\":7919384682,\"simple_method_name\":\"#client_requested_sync\"},\"details\":{\"reek\":\"found 1 code smells\",\"flog\":\"complexity is 37.9\"}}],\"classes\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":7995629750},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\"}}],\"files\":[{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":-5738801681},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\",\"churn\":\"detected high level of churn (changed 54 times)\"}},{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/foo.rb\",\"hash\":-7081271905},\"details\":{\"churn\":\"detected high level of churn (changed 52 times)\"}}]}")
83
- compare_hashes(MultiJson.load(MultiJson.dump(hotspots.to_h[:hotspots])), expected)
23
+ result = hotspots.to_h[:hotspots]
24
+ expected = metric_data('hotspots/generator_analysis.yml')
25
+ # ensure expected granularities
26
+ expect(result.keys).to eq(expected.keys)
27
+
28
+ # for each granularity's location details
29
+ result.each do |granularity,location_details|
30
+ # map 2d array for this granularity of [details, location]
31
+ expected_result = expected.fetch(granularity).map {|ld| [ld.fetch('details'), ld.fetch('location')] }
32
+ # verify all the location details for this granularity match elements of expected_result
33
+ location_details.each do |location_detail|
34
+ location = location_detail.fetch('location')
35
+ details = location_detail.fetch('details')
36
+ # get the location_detail array where the where the locations (second element) match
37
+ expected_location_details = expected_result.rassoc(location)
38
+ # get the details (first element) from the expected location_details array
39
+ expected_details = expected_location_details[0]
40
+ expect(details).to eq(expected_details)
41
+ end
42
+ end
84
43
  end
44
+
85
45
  # really testing the output of analyzed_problems#worst_items
86
46
  it "should return the worst item granularities: files, classes, methods" do
87
47
  hotspots = MetricFu::HotspotsGenerator.new
88
48
  analyzer = HotspotAnalyzer.new(@yaml)
89
- hotspots.instance_variable_set(:@analyzer, analyzer)
90
- hotspots.analyze.keys.should =~ [:files, :classes, :methods]
49
+ expect(hotspots.analyze.keys).to eq([:files, :classes, :methods])
91
50
  end
92
51
  end
93
- end
94
- def compare_hashes(got,expected)
95
- got.hash == expected.hash
52
+
96
53
  end