jscruggs-metric_fu 0.8.0 → 0.8.9
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.
- data/{History.txt → HISTORY} +12 -0
- data/MIT-LICENSE +1 -1
- data/Manifest.txt +3 -6
- data/README +66 -16
- data/Rakefile +11 -0
- data/TODO +13 -0
- data/lib/metric_fu/base.rb +159 -0
- data/lib/metric_fu/churn.rb +88 -0
- data/lib/metric_fu/flay.rb +17 -0
- data/lib/metric_fu/flog.rb +129 -0
- data/lib/metric_fu/md5_tracker.rb +6 -6
- data/lib/metric_fu/reek.rb +17 -0
- data/lib/metric_fu/roodi.rb +17 -0
- data/lib/metric_fu.rb +3 -7
- data/lib/tasks/churn.rake +4 -106
- data/lib/tasks/coverage.rake +25 -9
- data/lib/tasks/flay.rake +6 -0
- data/lib/tasks/flog.rake +28 -21
- data/lib/tasks/metric_fu.rake +21 -8
- data/lib/tasks/metric_fu.rb +1 -1
- data/lib/tasks/railroad.rake +36 -0
- data/lib/tasks/reek.rake +6 -0
- data/lib/tasks/roodi.rake +7 -0
- data/lib/tasks/saikuro.rake +20 -19
- data/lib/tasks/stats.rake +3 -3
- data/lib/templates/churn.html.erb +22 -0
- data/lib/templates/default.css +45 -0
- data/lib/templates/flay.html.erb +30 -0
- data/lib/templates/flog.html.erb +31 -0
- data/lib/templates/flog_page.html.erb +25 -0
- data/lib/templates/reek.html.erb +30 -0
- data/lib/templates/roodi.html.erb +26 -0
- data/spec/base_spec.rb +57 -0
- data/spec/churn_spec.rb +117 -0
- data/spec/config_spec.rb +110 -0
- data/spec/flay_spec.rb +19 -0
- data/spec/flog_spec.rb +208 -0
- data/spec/md5_tracker_spec.rb +57 -0
- data/spec/reek_spec.rb +26 -0
- data/spec/spec_helper.rb +11 -0
- metadata +76 -27
- data/TODO.txt +0 -9
- data/lib/metric_fu/flog_reporter/base.rb +0 -58
- data/lib/metric_fu/flog_reporter/flog_reporter.css +0 -39
- data/lib/metric_fu/flog_reporter/generator.rb +0 -71
- data/lib/metric_fu/flog_reporter/operator.rb +0 -10
- data/lib/metric_fu/flog_reporter/page.rb +0 -36
- data/lib/metric_fu/flog_reporter/scanned_method.rb +0 -28
- data/lib/metric_fu/flog_reporter.rb +0 -5
- data/lib/metric_fu/saikuro/SAIKURO_README +0 -142
- data/metric_fu.gemspec +0 -16
- data/test/test_helper.rb +0 -4
- data/test/test_md5_tracker.rb +0 -59
metadata
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jscruggs-metric_fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jake Scruggs
|
8
8
|
- Sean Soper
|
9
|
+
- Andre Arko
|
10
|
+
- Petrik de Heus
|
9
11
|
autorequire:
|
10
12
|
bindir: bin
|
11
13
|
cert_chain: []
|
12
14
|
|
13
|
-
date:
|
15
|
+
date: 2009-01-11 00:00:00 -08:00
|
14
16
|
default_executable:
|
15
17
|
dependencies:
|
16
18
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
19
|
+
name: flay
|
18
20
|
version_requirement:
|
19
21
|
version_requirements: !ruby/object:Gem::Requirement
|
20
22
|
requirements:
|
@@ -22,56 +24,95 @@ dependencies:
|
|
22
24
|
- !ruby/object:Gem::Version
|
23
25
|
version: 0.0.0
|
24
26
|
version:
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: flog
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.2.0
|
35
|
+
version:
|
25
36
|
- !ruby/object:Gem::Dependency
|
26
37
|
name: rcov
|
27
38
|
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.8.1
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: railroad
|
47
|
+
version_requirement:
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.5.0
|
53
|
+
version:
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: reek
|
56
|
+
version_requirement:
|
28
57
|
version_requirements: !ruby/object:Gem::Requirement
|
29
58
|
requirements:
|
30
59
|
- - ">"
|
31
60
|
- !ruby/object:Gem::Version
|
32
61
|
version: 0.0.0
|
33
62
|
version:
|
34
|
-
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: roodi
|
65
|
+
version_requirement:
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 0.0.0
|
71
|
+
version:
|
72
|
+
description: Code metrics from Flog, Flay, RCov, Saikuro, Churn, Reek, Roodi and Rails' stats task
|
35
73
|
email: jake.scruggs@gmail.com
|
36
74
|
executables: []
|
37
75
|
|
38
76
|
extensions: []
|
39
77
|
|
40
78
|
extra_rdoc_files:
|
41
|
-
-
|
79
|
+
- HISTORY
|
42
80
|
- Manifest.txt
|
43
81
|
- README
|
44
82
|
files:
|
45
|
-
- History.txt
|
46
|
-
- Manifest.txt
|
47
|
-
- metric_fu.gemspec
|
48
|
-
- MIT-LICENSE
|
49
83
|
- README
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
- lib/metric_fu/
|
55
|
-
- lib/metric_fu/
|
56
|
-
- lib/metric_fu/
|
57
|
-
- lib/metric_fu/
|
58
|
-
- lib/metric_fu/
|
59
|
-
- lib/metric_fu/
|
84
|
+
- HISTORY
|
85
|
+
- TODO
|
86
|
+
- MIT-LICENSE
|
87
|
+
- Rakefile
|
88
|
+
- lib/metric_fu/base.rb
|
89
|
+
- lib/metric_fu/churn.rb
|
90
|
+
- lib/metric_fu/flay.rb
|
91
|
+
- lib/metric_fu/flog.rb
|
92
|
+
- lib/metric_fu/reek.rb
|
93
|
+
- lib/metric_fu/roodi.rb
|
60
94
|
- lib/metric_fu/md5_tracker.rb
|
61
|
-
- lib/metric_fu/saikuro
|
62
95
|
- lib/metric_fu/saikuro/saikuro.rb
|
63
|
-
- lib/metric_fu/saikuro/SAIKURO_README
|
64
96
|
- lib/metric_fu.rb
|
65
|
-
- lib/tasks
|
97
|
+
- lib/tasks/metric_fu.rb
|
66
98
|
- lib/tasks/churn.rake
|
67
99
|
- lib/tasks/coverage.rake
|
100
|
+
- lib/tasks/flay.rake
|
68
101
|
- lib/tasks/flog.rake
|
69
102
|
- lib/tasks/metric_fu.rake
|
70
|
-
- lib/tasks/
|
103
|
+
- lib/tasks/railroad.rake
|
104
|
+
- lib/tasks/reek.rake
|
105
|
+
- lib/tasks/roodi.rake
|
71
106
|
- lib/tasks/saikuro.rake
|
72
107
|
- lib/tasks/stats.rake
|
73
|
-
-
|
74
|
-
-
|
108
|
+
- lib/templates/churn.html.erb
|
109
|
+
- lib/templates/default.css
|
110
|
+
- lib/templates/flay.html.erb
|
111
|
+
- lib/templates/flog.html.erb
|
112
|
+
- lib/templates/flog_page.html.erb
|
113
|
+
- lib/templates/reek.html.erb
|
114
|
+
- lib/templates/roodi.html.erb
|
115
|
+
- Manifest.txt
|
75
116
|
has_rdoc: true
|
76
117
|
homepage: http://metric-fu.rubyforge.org/
|
77
118
|
post_install_message:
|
@@ -98,6 +139,14 @@ rubyforge_project:
|
|
98
139
|
rubygems_version: 1.2.0
|
99
140
|
signing_key:
|
100
141
|
specification_version: 2
|
101
|
-
summary:
|
142
|
+
summary: A fistful of code metrics
|
102
143
|
test_files:
|
103
|
-
-
|
144
|
+
- spec/base_spec.rb
|
145
|
+
- spec/churn_spec.rb
|
146
|
+
- spec/config_spec.rb
|
147
|
+
- spec/flay_spec.rb
|
148
|
+
- spec/flog_spec.rb
|
149
|
+
- spec/md5_tracker_spec.rb
|
150
|
+
- spec/reek_spec.rb
|
151
|
+
- spec/roodi_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
data/TODO.txt
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
== TODO list
|
2
|
-
|
3
|
-
* Color code flog results with scale from: http://jakescruggs.blogspot.com/2008/08/whats-good-flog-score.html
|
4
|
-
* Extract functionality from rake files and put under test
|
5
|
-
* Change churn start_date to not rely on Rails 1.day.ago functionality (so metric_fu can be used in non-Rails apps)
|
6
|
-
* Make integration with RSpec better (use SpecTask?)
|
7
|
-
* Integrate Flog, Saikuro, and Coverage into one report so you can see methods that have high complexity and low coverage (this is a big one)
|
8
|
-
* Integrate MD5 hashing with remainder of reports
|
9
|
-
|
@@ -1,58 +0,0 @@
|
|
1
|
-
module MetricFu::FlogReporter
|
2
|
-
|
3
|
-
SCORE_FORMAT = "%0.2f"
|
4
|
-
|
5
|
-
class InvalidFlog < RuntimeError
|
6
|
-
end
|
7
|
-
|
8
|
-
class Base
|
9
|
-
MODULE_NAME = "([A-Z][a-z]+)+"
|
10
|
-
METHOD_NAME = "#([a-z0-9]+_?)+\\??\\!?"
|
11
|
-
SCORE = "\\d+\\.\\d+"
|
12
|
-
|
13
|
-
METHOD_NAME_RE = Regexp.new("#{MODULE_NAME}#{METHOD_NAME}")
|
14
|
-
SCORE_RE = Regexp.new(SCORE)
|
15
|
-
|
16
|
-
METHOD_LINE_RE = Regexp.new("#{MODULE_NAME}#{METHOD_NAME}:\\s\\(#{SCORE}\\)")
|
17
|
-
OPERATOR_LINE_RE = Regexp.new("\\s+(#{SCORE}):\\s(.*)$")
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def cycle(first_value, second_value, iteration)
|
21
|
-
return first_value if iteration % 2 == 0
|
22
|
-
return second_value
|
23
|
-
end
|
24
|
-
|
25
|
-
def load_css(css_file = nil)
|
26
|
-
filepath = css_file || File.join(File.dirname(__FILE__), 'flog_reporter.css')
|
27
|
-
css = ""
|
28
|
-
file = File.open(filepath, "r")
|
29
|
-
file.each_line { |line| css << line }
|
30
|
-
file.close
|
31
|
-
css
|
32
|
-
end
|
33
|
-
|
34
|
-
def parse(text)
|
35
|
-
score = text[/score = (\d+\.\d+)/, 1]
|
36
|
-
return nil unless score
|
37
|
-
page = Page.new(score)
|
38
|
-
|
39
|
-
text.each_line do |method_line|
|
40
|
-
if METHOD_LINE_RE =~ method_line and
|
41
|
-
method_name = method_line[METHOD_NAME_RE] and
|
42
|
-
score = method_line[SCORE_RE]
|
43
|
-
page.scanned_methods << ScannedMethod.new(method_name, score)
|
44
|
-
end
|
45
|
-
|
46
|
-
if OPERATOR_LINE_RE =~ method_line and
|
47
|
-
operator = method_line[OPERATOR_LINE_RE, 2] and
|
48
|
-
score = method_line[SCORE_RE]
|
49
|
-
raise InvalidFlog if page.scanned_methods.empty?
|
50
|
-
page.scanned_methods.last.operators << Operator.new(score, operator)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
page
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
body {
|
2
|
-
background-color: rgb(240, 240, 245);
|
3
|
-
font-family: verdana, arial, helvetica;
|
4
|
-
}
|
5
|
-
|
6
|
-
table {
|
7
|
-
border-collapse: collapse;
|
8
|
-
}
|
9
|
-
|
10
|
-
table.report {
|
11
|
-
width: 100%;
|
12
|
-
}
|
13
|
-
|
14
|
-
table th {
|
15
|
-
text-align: center;
|
16
|
-
}
|
17
|
-
|
18
|
-
table td.score {
|
19
|
-
text-align: right;
|
20
|
-
}
|
21
|
-
|
22
|
-
table th {
|
23
|
-
background: #dcecff;
|
24
|
-
border: #d0d0d0 1px solid;
|
25
|
-
font-weight: bold;
|
26
|
-
}
|
27
|
-
|
28
|
-
table td {
|
29
|
-
border: #d0d0d0 1px solid;
|
30
|
-
}
|
31
|
-
|
32
|
-
table tr.light {
|
33
|
-
background-color: rgb(240, 240, 245);
|
34
|
-
}
|
35
|
-
|
36
|
-
table tr.dark {
|
37
|
-
background-color: rgb(230, 230, 235);
|
38
|
-
}
|
39
|
-
|
@@ -1,71 +0,0 @@
|
|
1
|
-
module MetricFu::FlogReporter
|
2
|
-
class Generator
|
3
|
-
class << self
|
4
|
-
def generate_report(base_dir)
|
5
|
-
flog_hashes = []
|
6
|
-
Dir.glob("#{base_dir}/**/*.txt").each do |filename|
|
7
|
-
content = ""
|
8
|
-
File.open(filename, "r").each_line do |file|
|
9
|
-
content << file
|
10
|
-
end
|
11
|
-
|
12
|
-
begin
|
13
|
-
page = Base.parse(content)
|
14
|
-
rescue InvalidFlog
|
15
|
-
puts "Invalid flog for #{filename}"
|
16
|
-
next
|
17
|
-
end
|
18
|
-
|
19
|
-
next unless page
|
20
|
-
|
21
|
-
if MetricFu::MD5Tracker.file_already_counted?(filename)
|
22
|
-
flog_hashes << {
|
23
|
-
:page => page,
|
24
|
-
:path => filename.sub('.txt', '.html').sub("#{base_dir}/", "")
|
25
|
-
}
|
26
|
-
else
|
27
|
-
flog_hashes << generate_page(filename, page, base_dir)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
generate_index(flog_hashes, base_dir)
|
32
|
-
end
|
33
|
-
|
34
|
-
def generate_page(filename, page, base_dir)
|
35
|
-
html_file = File.new(filename.gsub(/\.txt/, '.html'), "w")
|
36
|
-
html_file.puts page.to_html
|
37
|
-
html_file.close
|
38
|
-
return { :path => html_file.path.sub("#{base_dir}/", ''),
|
39
|
-
:page => page }
|
40
|
-
end
|
41
|
-
|
42
|
-
def generate_index(flog_hashes, base_dir)
|
43
|
-
html = "<html><head><title>Flog Reporter</title><style>"
|
44
|
-
html << Base.load_css
|
45
|
-
html << "</style></head><body>"
|
46
|
-
html << "<p><strong>Flogged files</strong></p>\n"
|
47
|
-
html << "<p>Generated on #{Time.now.localtime} with <a href='http://ruby.sadi.st/Flog.html'>flog</a></p>\n"
|
48
|
-
html << "<table class='report'>\n"
|
49
|
-
html << "<tr><th>File</th><th>Total score</th><th>Methods</th><th>Average score</th><th>Highest score</th></tr>"
|
50
|
-
count = 0
|
51
|
-
flog_hashes.sort {|x,y| y[:page].highest_score <=> x[:page].highest_score }.each do |flog_hash|
|
52
|
-
html << <<-EOF
|
53
|
-
<tr class='#{Base.cycle("light", "dark", count)}'>
|
54
|
-
<td><a href='#{flog_hash[:path]}'>#{flog_hash[:path].sub('.html', '.rb')}</a></td>
|
55
|
-
<td class='score'>#{sprintf(SCORE_FORMAT, flog_hash[:page].score)}</td>
|
56
|
-
<td class='score'>#{flog_hash[:page].scanned_methods.length}</td>
|
57
|
-
<td class='score'>#{sprintf(SCORE_FORMAT, flog_hash[:page].average_score)}</td>
|
58
|
-
<td class='score'>#{sprintf(SCORE_FORMAT, flog_hash[:page].highest_score)}</td>
|
59
|
-
</tr>
|
60
|
-
EOF
|
61
|
-
count += 1
|
62
|
-
end
|
63
|
-
html << "</table>\n"
|
64
|
-
html << "</body></html>\n"
|
65
|
-
index = File.new("#{base_dir}/index.html", "w")
|
66
|
-
index.puts html
|
67
|
-
index.close
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module MetricFu::FlogReporter
|
2
|
-
class Page
|
3
|
-
attr_accessor :score, :scanned_methods
|
4
|
-
|
5
|
-
def initialize(score, scanned_methods = [])
|
6
|
-
@score = score.to_f
|
7
|
-
@scanned_methods = scanned_methods
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_html
|
11
|
-
output = "<html><head><style>"
|
12
|
-
output << Base.load_css
|
13
|
-
output << "</style></head><body>"
|
14
|
-
output << "Score: #{score}\n"
|
15
|
-
scanned_methods.each do |sm|
|
16
|
-
output << sm.to_html
|
17
|
-
end
|
18
|
-
output << "</body></html>"
|
19
|
-
output
|
20
|
-
end
|
21
|
-
|
22
|
-
def average_score
|
23
|
-
sum = 0
|
24
|
-
scanned_methods.each do |m|
|
25
|
-
sum += m.score
|
26
|
-
end
|
27
|
-
sum / scanned_methods.length
|
28
|
-
end
|
29
|
-
|
30
|
-
def highest_score
|
31
|
-
scanned_methods.inject(0) do |highest, m|
|
32
|
-
m.score > highest ? m.score : highest
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module MetricFu::FlogReporter
|
2
|
-
class ScannedMethod
|
3
|
-
attr_accessor :name, :score, :operators
|
4
|
-
|
5
|
-
def initialize(name, score, operators = [])
|
6
|
-
@name = name
|
7
|
-
@score = score.to_f
|
8
|
-
@operators = operators
|
9
|
-
end
|
10
|
-
|
11
|
-
def to_html
|
12
|
-
output = "<p><strong>#{name} (#{score})</strong></p>\n"
|
13
|
-
output << "<table>\n"
|
14
|
-
output << "<tr><th>Score</th><th>Operator</th></tr>\n"
|
15
|
-
count = 0
|
16
|
-
operators.each do |operator|
|
17
|
-
output << <<-EOF
|
18
|
-
<tr class='#{Base.cycle("light", "dark", count)}'>
|
19
|
-
<td class='score'>#{sprintf(SCORE_FORMAT, operator.score)}</td>
|
20
|
-
<td class='score'>#{operator.operator}</td>
|
21
|
-
</tr>
|
22
|
-
EOF
|
23
|
-
count += 1
|
24
|
-
end
|
25
|
-
output << "</table>\n\n"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,5 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'flog_reporter', 'base')
|
2
|
-
require File.join(File.dirname(__FILE__), 'flog_reporter', 'page')
|
3
|
-
require File.join(File.dirname(__FILE__), 'flog_reporter', 'scanned_method')
|
4
|
-
require File.join(File.dirname(__FILE__), 'flog_reporter', 'operator')
|
5
|
-
require File.join(File.dirname(__FILE__), 'flog_reporter', 'generator')
|
@@ -1,142 +0,0 @@
|
|
1
|
-
Version 0.2
|
2
|
-
|
3
|
-
Saikuro:
|
4
|
-
Saikuro is a Ruby cyclomatic complexity analyzer. When given Ruby
|
5
|
-
source code Saikuro will generate a report listing the cyclomatic
|
6
|
-
complexity of each method found. In addition, Saikuro counts the
|
7
|
-
number of lines per method and can generate a listing of the number of
|
8
|
-
tokens on each line of code.
|
9
|
-
|
10
|
-
License:
|
11
|
-
Saikuro uses the BSD license.
|
12
|
-
|
13
|
-
Installation:
|
14
|
-
Option 1: Using setup.rb
|
15
|
-
* login as root
|
16
|
-
* run "ruby setup.rb all"
|
17
|
-
|
18
|
-
Option 2: The manual way
|
19
|
-
Saikuro is a single Ruby file that is executable. You can run it where
|
20
|
-
you unpacked it or you can move it your preferred location such as
|
21
|
-
"/usr/local/bin" or "~/bin".
|
22
|
-
|
23
|
-
Note:
|
24
|
-
Ruby 1.8.5 has a bug in ri_options that will prevent Saikuro from
|
25
|
-
running. If you are using 1.8.5 please apply this patch :
|
26
|
-
http://www.ruby-lang.org/cgi-bin/cvsweb.cgi/ruby/lib/rdoc/ri/ri_options.rb.diff?r1=1.2.2.13;r2=1.2.2.14
|
27
|
-
|
28
|
-
|
29
|
-
Usage:
|
30
|
-
Saikuro is a command line program.
|
31
|
-
Running "saikuro -h" will output a usage statement describing all
|
32
|
-
the various arguments you can pass to it.
|
33
|
-
|
34
|
-
"saikuro -c -p tests/samples.rb"
|
35
|
-
|
36
|
-
The above command is a simple example that generates a cyclomatic
|
37
|
-
complexity report on the samples.rb file, using the default filter,
|
38
|
-
warning and error settings. The report is saved in the current
|
39
|
-
directory.
|
40
|
-
|
41
|
-
|
42
|
-
A more detailed example is
|
43
|
-
"saikuro -c -t -i tests -y 0 -w 11 -e 16 -o out/"
|
44
|
-
|
45
|
-
This will analyze all Ruby files found in the "tests/" directory.
|
46
|
-
Saikuro will generate a token count report and a cyclomatic complexity
|
47
|
-
report in the "out" directory . The "-y 0" command will turn off
|
48
|
-
filtering and thus show the complexity of all methods. The "-w 11"
|
49
|
-
will mark all methods with a complexity of 11 or higher with a
|
50
|
-
warning. Finally, "-e 16" will flag all methods with a complexity of
|
51
|
-
16 or higher with an error.
|
52
|
-
|
53
|
-
|
54
|
-
About Cyclomatic Complexity:
|
55
|
-
|
56
|
-
The following document provides a very good and detailed description
|
57
|
-
by the author of cyclomatic complexity.
|
58
|
-
|
59
|
-
NIST Special Publication 500-235
|
60
|
-
Structured Testing: A Testing Methodology Using the Cyclomatic
|
61
|
-
Complexity Metric
|
62
|
-
|
63
|
-
By Arthur H. Watson and Thomas J. McCabe
|
64
|
-
HTML
|
65
|
-
http://hissa.nist.gov/HHRFdata/Artifacts/ITLdoc/235/title.htm
|
66
|
-
PDF
|
67
|
-
http://www.mccabe.com/iq_research_nist.htm
|
68
|
-
|
69
|
-
|
70
|
-
How and what Saikuro counts to calculate the cyclomatic complexity:
|
71
|
-
|
72
|
-
Saikuro uses the Simplified Complexity Calculation, which is just
|
73
|
-
adding up the number of branch points in a method.
|
74
|
-
|
75
|
-
Each method starts with a complexity of 1, because there is at least
|
76
|
-
one path through the code. Then each conditional or looping operator
|
77
|
-
(if, unless, while, until, for, elsif, when) adds one point to the
|
78
|
-
complexity. Each "when" in a case statement adds one point. Also each
|
79
|
-
"rescue" statement adds one.
|
80
|
-
|
81
|
-
Saikuro also regards blocks as an addition to a method's complexity
|
82
|
-
because in many cases a block does add a path that may be traversed.
|
83
|
-
For example, invoking the "each" method of an array with a block would
|
84
|
-
only traverse the give block if the array is not empty. Thus if you
|
85
|
-
want to find the basis set to get 100% coverage of your code then a
|
86
|
-
block should add one point to the method's complexity. It is not yet
|
87
|
-
for sure however to what level the accuracy is decreased through this
|
88
|
-
measurement, as normal Ruby code uses blocks quite heavily and new
|
89
|
-
paths are not necessarily introduced by every block.
|
90
|
-
|
91
|
-
In addition, the short-circuiting "and" operators (&& and "and")
|
92
|
-
currently do not contribute to a method's complexity, although
|
93
|
-
McCabe's paper listed above suggests doing so.
|
94
|
-
|
95
|
-
|
96
|
-
#Example for "and" operator handling:
|
97
|
-
|
98
|
-
# Starting values for case 1 and 2
|
99
|
-
x = false
|
100
|
-
y = 15
|
101
|
-
r, q = nil
|
102
|
-
|
103
|
-
# case 1
|
104
|
-
puts "W" if ((r = x) && (q = y))
|
105
|
-
puts r # => false
|
106
|
-
puts q # => nil
|
107
|
-
|
108
|
-
# case 2
|
109
|
-
puts "W" if ((q = y) && (r = x))
|
110
|
-
puts r # => false
|
111
|
-
puts q # => 15
|
112
|
-
|
113
|
-
Case 1 illustrates why "and" operators should add to a method's
|
114
|
-
complexity, because the result of ( r = x ) is false the if statement
|
115
|
-
stops and returns false without evaluating the ( q = y ) branch. Thus
|
116
|
-
if a total coverage of source code is desired, one point should be
|
117
|
-
added to the method's complexity.
|
118
|
-
|
119
|
-
So why is it not added?
|
120
|
-
Mainly, because we have not gotten around to it. We are wondering if
|
121
|
-
this would increase the noise more than it should.
|
122
|
-
|
123
|
-
|
124
|
-
Tests:
|
125
|
-
In the test directory is a sample file that has examples of the
|
126
|
-
various possible cases that we examined and documented the expected
|
127
|
-
cyclomatic complexity result. If you find mistakes or missing tests
|
128
|
-
please report them.
|
129
|
-
|
130
|
-
Contact:
|
131
|
-
Saikuro is written by
|
132
|
-
Zev Blut (zb at ubit dot com)
|
133
|
-
|
134
|
-
Acknowledgments:
|
135
|
-
Thanks to Elbert Corpuz for writing the CSS for the HTML output!
|
136
|
-
|
137
|
-
Other metric tools for Ruby:
|
138
|
-
Ryan Davis has an abc metric program as an example in his ParseTree
|
139
|
-
product: http://www.zenspider.com/ZSS/Products/ParseTree/
|
140
|
-
|
141
|
-
The PMD project has a tool called CPD that can scan Ruby source code
|
142
|
-
looking for source duplication: http://pmd.sourceforge.net/
|
data/metric_fu.gemspec
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
Gem::Specification.new do |s|
|
2
|
-
s.name = "metric_fu"
|
3
|
-
s.version = "0.8.0"
|
4
|
-
s.summary = "Generates project metrics using Flog, RCov, Saikuro and more"
|
5
|
-
s.email = "jake.scruggs@gmail.com"
|
6
|
-
s.homepage = "http://metric-fu.rubyforge.org/"
|
7
|
-
s.description = "Gives you a fist full of code metrics"
|
8
|
-
s.has_rdoc = true
|
9
|
-
s.authors = ["Jake Scruggs", "Sean Soper"]
|
10
|
-
s.files = ["History.txt", "Manifest.txt", "metric_fu.gemspec", "MIT-LICENSE", "README", "TODO.txt", "lib/metric_fu", "lib/metric_fu/flog_reporter", "lib/metric_fu/flog_reporter/base.rb", "lib/metric_fu/flog_reporter/flog_reporter.css", "lib/metric_fu/flog_reporter/generator.rb", "lib/metric_fu/flog_reporter/operator.rb", "lib/metric_fu/flog_reporter/page.rb", "lib/metric_fu/flog_reporter/scanned_method.rb", "lib/metric_fu/flog_reporter.rb", "lib/metric_fu/md5_tracker.rb", "lib/metric_fu/saikuro", "lib/metric_fu/saikuro/saikuro.rb", "lib/metric_fu/saikuro/SAIKURO_README", "lib/metric_fu.rb", "lib/tasks", "lib/tasks/churn.rake", "lib/tasks/coverage.rake", "lib/tasks/flog.rake", "lib/tasks/metric_fu.rake", "lib/tasks/metric_fu.rb", "lib/tasks/saikuro.rake", "lib/tasks/stats.rake", "test/test_helper.rb", "test/test_md5_tracker.rb"]
|
11
|
-
s.test_files = ["test/test_md5_tracker.rb"]
|
12
|
-
s.rdoc_options = ["--main", "README"]
|
13
|
-
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README"]
|
14
|
-
s.add_dependency("flog", ["> 0.0.0"])
|
15
|
-
s.add_dependency("rcov", ["> 0.0.0"])
|
16
|
-
end
|
data/test/test_helper.rb
DELETED
data/test/test_md5_tracker.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
-
|
3
|
-
class TestMD5Tracker < Test::Unit::TestCase
|
4
|
-
|
5
|
-
def setup
|
6
|
-
@tmp_dir = File.join(File.dirname(__FILE__), 'tmp')
|
7
|
-
FileUtils.mkdir_p(@tmp_dir, :verbose => false) unless File.directory?(@tmp_dir)
|
8
|
-
@file1 = File.new(File.join(@tmp_dir, 'file1.txt'), 'w')
|
9
|
-
@file2 = File.new(File.join(@tmp_dir, 'file2.txt'), 'w')
|
10
|
-
end
|
11
|
-
|
12
|
-
def teardown
|
13
|
-
FileUtils.rm_rf(@tmp_dir, :verbose => false)
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_identical_files_match
|
17
|
-
@file1.puts("Hello World")
|
18
|
-
@file1.close
|
19
|
-
file1_md5 = MetricFu::MD5Tracker.track(@file1.path, @tmp_dir)
|
20
|
-
|
21
|
-
@file2.puts("Hello World")
|
22
|
-
@file2.close
|
23
|
-
file2_md5 = MetricFu::MD5Tracker.track(@file2.path, @tmp_dir)
|
24
|
-
|
25
|
-
assert file1_md5 == file2_md5
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_different_files_dont_match
|
29
|
-
@file1.puts("Hello World")
|
30
|
-
@file1.close
|
31
|
-
file1_md5 = MetricFu::MD5Tracker.track(@file1.path, @tmp_dir)
|
32
|
-
|
33
|
-
@file2.puts("Goodbye World")
|
34
|
-
@file2.close
|
35
|
-
file2_md5 = MetricFu::MD5Tracker.track(@file2.path, @tmp_dir)
|
36
|
-
|
37
|
-
assert file1_md5 != file2_md5
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_file_changed
|
41
|
-
@file2.close
|
42
|
-
|
43
|
-
@file1.puts("Hello World")
|
44
|
-
@file1.close
|
45
|
-
file1_md5 = MetricFu::MD5Tracker.track(@file1.path, @tmp_dir)
|
46
|
-
|
47
|
-
@file1 = File.new(File.join(@tmp_dir, 'file1.txt'), 'w')
|
48
|
-
@file1.puts("Goodbye World")
|
49
|
-
@file1.close
|
50
|
-
assert MetricFu::MD5Tracker.file_changed?(@file1.path, @tmp_dir)
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_file_changed_if_not_tracking
|
54
|
-
@file2.close
|
55
|
-
|
56
|
-
assert MetricFu::MD5Tracker.file_changed?(@file1.path, @tmp_dir)
|
57
|
-
assert File.exist?(MetricFu::MD5Tracker.md5_file(@file1.path, @tmp_dir))
|
58
|
-
end
|
59
|
-
end
|