p8-metric_fu 0.9.0.1 → 0.9.0.2
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/README +1 -1
- data/Rakefile +2 -19
- data/TODO +1 -2
- data/lib/metric_fu.rb +3 -17
- 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 +52 -0
- data/lib/metric_fu/saikuro/saikuro.rb +1214 -0
- data/lib/tasks/churn.rake +9 -0
- data/lib/tasks/coverage.rake +54 -0
- data/lib/tasks/flay.rake +6 -0
- data/lib/tasks/flog.rake +69 -0
- data/lib/tasks/metric_fu.rake +24 -0
- data/lib/tasks/metric_fu.rb +6 -0
- data/lib/tasks/railroad.rake +39 -0
- data/lib/tasks/reek.rake +6 -0
- data/lib/tasks/roodi.rake +7 -0
- data/lib/tasks/saikuro.rake +35 -0
- data/lib/tasks/stats.rake +14 -0
- 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 +38 -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 +80 -0
- data/spec/churn_spec.rb +114 -114
- data/spec/config_spec.rb +103 -103
- data/spec/flay_spec.rb +16 -16
- data/spec/flog_spec.rb +205 -204
- data/spec/reek_spec.rb +23 -23
- data/spec/spec_helper.rb +7 -24
- metadata +1 -1
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rcov'
|
5
|
+
require 'rcov/rcovtask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
namespace :metrics do
|
9
|
+
|
10
|
+
COVERAGE_DIR = File.join(MetricFu::BASE_DIRECTORY, 'coverage')
|
11
|
+
COVERAGE_DATA_FILE = File.join(MetricFu::BASE_DIRECTORY, 'coverage.data')
|
12
|
+
SPEC_HTML_FILE = File.join(MetricFu::BASE_DIRECTORY, 'specs.html')
|
13
|
+
|
14
|
+
namespace :coverage do
|
15
|
+
rcov_output = COVERAGE_DIR
|
16
|
+
|
17
|
+
desc "Delete aggregate coverage data."
|
18
|
+
task(:clean) { rm_f("rcov_tmp", :verbose => false) }
|
19
|
+
|
20
|
+
desc "RCov task to generate report"
|
21
|
+
Rcov::RcovTask.new(:do => :clean) do |t|
|
22
|
+
FileUtils.mkdir_p(MetricFu::BASE_DIRECTORY) unless File.directory?(MetricFu::BASE_DIRECTORY)
|
23
|
+
t.test_files = FileList[*MetricFu.coverage[:test_files]]
|
24
|
+
t.rcov_opts = MetricFu.coverage[:rcov_opts]
|
25
|
+
t.output_dir = COVERAGE_DIR
|
26
|
+
# this line is a fix for Rails 2.1 relative loading issues
|
27
|
+
t.libs << 'test'
|
28
|
+
end
|
29
|
+
# TODO not sure what this improves but it requires the diff-lcs gem
|
30
|
+
# http://github.com/indirect/metric_fu/commit/b9c1cf75f09d5b531b388cd01661eb16b5126968#diff-1
|
31
|
+
# Spec::Rake::SpecTask.new(:do => :clean) do |t|
|
32
|
+
# FileUtils.mkdir_p(MetricFu::BASE_DIRECTORY) unless File.directory?(MetricFu::BASE_DIRECTORY)
|
33
|
+
# t.ruby_opts = ['-rtest/unit']
|
34
|
+
# t.spec_files = FileList['test/**/*_test.rb', 'spec/**/*spec.rb']
|
35
|
+
# t.spec_opts = ["--format", "html:#{SPEC_HTML_FILE}", "--diff"]
|
36
|
+
# t.rcov = true
|
37
|
+
# t.rcov_opts = ["--sort coverage", "--html", "--rails", "--exclude /gems/,/Library/"]
|
38
|
+
# t.rcov_dir = COVERAGE_DIR
|
39
|
+
# end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Generate and open coverage report"
|
43
|
+
task :coverage => ['coverage:do'] do
|
44
|
+
system("open #{COVERAGE_DIR}/index.html") if MetricFu.open_in_browser?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue LoadError
|
48
|
+
if RUBY_PLATFORM =~ /java/
|
49
|
+
puts 'running in jruby - rcov tasks not available'
|
50
|
+
else
|
51
|
+
puts 'sudo gem install rcov # if you want the rcov tasks'
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/tasks/flay.rake
ADDED
data/lib/tasks/flog.rake
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
begin
|
2
|
+
|
3
|
+
def flog(output, directory)
|
4
|
+
metric_dir = MetricFu::Flog::Generator.metric_dir
|
5
|
+
Dir.glob("#{directory}/**/*.rb").each do |filename|
|
6
|
+
output_dir = "#{metric_dir}/#{filename.split("/")[0..-2].join("/")}"
|
7
|
+
mkdir_p(output_dir, :verbose => false) unless File.directory?(output_dir)
|
8
|
+
if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
|
9
|
+
`flog #{filename} > #{metric_dir}/#{filename.split('.')[0]}.txt`
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :metrics do
|
15
|
+
|
16
|
+
task :flog => ['flog:all'] do
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :flog do
|
20
|
+
desc "Delete aggregate flog data."
|
21
|
+
task(:clean) { rm_rf(MetricFu::Flog.metric_dir, :verbose => false) }
|
22
|
+
|
23
|
+
desc "Flog code in app/models"
|
24
|
+
task :models do
|
25
|
+
flog "models", "app/models"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Flog code in app/controllers"
|
29
|
+
task :controllers do
|
30
|
+
flog "controllers", "app/controllers"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Flog code in app/helpers"
|
34
|
+
task :helpers do
|
35
|
+
flog "helpers", "app/helpers"
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Flog code in lib"
|
39
|
+
task :lib do
|
40
|
+
flog "lib", "lib"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Generate a flog report from specified directories"
|
44
|
+
task :custom do
|
45
|
+
MetricFu::flog[:dirs_to_flog].each { |directory| flog(directory, directory) }
|
46
|
+
MetricFu.generate_flog_report
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Generate and open flog report"
|
50
|
+
if MetricFu::RAILS
|
51
|
+
task :all => [:models, :controllers, :helpers, :lib] do
|
52
|
+
MetricFu.generate_flog_report
|
53
|
+
end
|
54
|
+
else
|
55
|
+
task :all => [:custom] do
|
56
|
+
MetricFu.generate_flog_report
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
rescue LoadError
|
64
|
+
if RUBY_PLATFORM =~ /java/
|
65
|
+
puts 'running in jruby - flog tasks not available'
|
66
|
+
else
|
67
|
+
puts 'sudo gem install flog # if you want the flog tasks'
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# only load configured metrics
|
2
|
+
MetricFu.metrics.each { |task| import "#{File.dirname(__FILE__)}/#{task}.rake" }
|
3
|
+
|
4
|
+
namespace :metrics do
|
5
|
+
if MetricFu::RAILS
|
6
|
+
|
7
|
+
desc "Generate coverage, cyclomatic complexity, flog, flay, railroad, reek, roodi, stats and churn reports"
|
8
|
+
task :all => MetricFu.metrics
|
9
|
+
|
10
|
+
task :set_testing_env do
|
11
|
+
RAILS_ENV = 'test'
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Generate metrics after migrating (for continuous integration)"
|
15
|
+
task :all_with_migrate => [:set_testing_env, "db:migrate", :all]
|
16
|
+
|
17
|
+
else
|
18
|
+
|
19
|
+
desc "Generate coverage, cyclomatic complexity, flog, flay, railroad and churn reports"
|
20
|
+
task :all => MetricFu.metrics
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
namespace :metrics do
|
2
|
+
|
3
|
+
RAILROAD_DIR = File.join(MetricFu::BASE_DIRECTORY, 'railroad')
|
4
|
+
RAILROAD_FILE = File.join(RAILROAD_DIR, 'index.html')
|
5
|
+
|
6
|
+
task :railroad => ['railroad:all'] do
|
7
|
+
end
|
8
|
+
|
9
|
+
namespace :railroad do
|
10
|
+
|
11
|
+
desc "Create all railroad reports"
|
12
|
+
task :all => [:models, :controllers, :aasm] do
|
13
|
+
#system("open #{RAILROAD_INDEX}") if PLATFORM['darwin']
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Create a railroad models report"
|
17
|
+
task :models do
|
18
|
+
#mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
|
19
|
+
`railroad -M -a -m -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'model-diagram.png')}`
|
20
|
+
#`echo "<a href=\"railroad/models.png\">Model diagram</a><br />" >> #{RAILROAD_FILE}`
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Create a railroad controllers report"
|
24
|
+
task :controllers do
|
25
|
+
#mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
|
26
|
+
`railroad -C -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'controller-diagram.png')}`
|
27
|
+
#`echo "<a href=\"railroad/controllers.png\">Controller diagram</a><br />" >> #{RAILROAD_FILE}`
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Create a railroad acts_as_state_machine report"
|
31
|
+
task :aasm do
|
32
|
+
#mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
|
33
|
+
`railroad -A -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'aasm-diagram.png')}`
|
34
|
+
#`echo "<a href=\"railroad/aasm.png\">State machine diagram</a><br />" >> #{RAILROAD_FILE}`
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/tasks/reek.rake
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
namespace :metrics do
|
4
|
+
|
5
|
+
desc "A cyclomatic complexity report using Saikuro"
|
6
|
+
task :saikuro do
|
7
|
+
SAIKURO_DIR = File.join(MetricFu::BASE_DIRECTORY, 'saikuro')
|
8
|
+
SAIKURO = File.expand_path(File.join(File.dirname(__FILE__), '..', 'metric_fu', 'saikuro', 'saikuro.rb'))
|
9
|
+
|
10
|
+
raise "SAIKURO_OPTIONS is now MetricFu::SAIKURO_OPTIONS" if defined?(SAIKURO_OPTIONS)
|
11
|
+
options = { :output_directory => SAIKURO_DIR,
|
12
|
+
:input_directory => MetricFu::CODE_DIRS,
|
13
|
+
:cyclo => "",
|
14
|
+
:filter_cyclo => "0",
|
15
|
+
:warn_cyclo => "5",
|
16
|
+
:error_cyclo => "7"}
|
17
|
+
|
18
|
+
options.merge!(MetricFu::SAIKURO_OPTIONS) if defined?(MetricFu::SAIKURO_OPTIONS)
|
19
|
+
options_string = options.inject(""){ |o, h| o + "--#{h.join(' ')} " }
|
20
|
+
|
21
|
+
sh %{ruby "#{SAIKURO}" #{options_string}} do |ok, response|
|
22
|
+
unless ok
|
23
|
+
puts "Saikuro failed with exit status: #{response.exitstatus}"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if File.exist? "#{SAIKURO_DIR}/index_cyclo.html"
|
29
|
+
mv "#{SAIKURO_DIR}/index_cyclo.html",
|
30
|
+
"#{SAIKURO_DIR}/index.html"
|
31
|
+
end
|
32
|
+
|
33
|
+
system("open #{SAIKURO_DIR}/index.html") if MetricFu.open_in_browser?
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
namespace :metrics do
|
2
|
+
|
3
|
+
STATS_DIR = File.join(MetricFu::BASE_DIRECTORY, 'stats')
|
4
|
+
STATS_FILE = File.join(STATS_DIR, 'index.html')
|
5
|
+
|
6
|
+
desc "A stats report"
|
7
|
+
task :stats do
|
8
|
+
mkdir_p(STATS_DIR) unless File.directory?(STATS_DIR)
|
9
|
+
`echo '<pre>' > #{STATS_FILE}`
|
10
|
+
`rake stats >> #{STATS_FILE}`
|
11
|
+
`echo '</pre>' >> #{STATS_FILE}`
|
12
|
+
system("open #{STATS_FILE}") if MetricFu.open_in_browser?
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Source Control Churn Results</title>
|
4
|
+
<style>
|
5
|
+
<%= inline_css("default.css") %>
|
6
|
+
</style>
|
7
|
+
</head>
|
8
|
+
|
9
|
+
<body>
|
10
|
+
<h1>Source Control Churn Results</h1>
|
11
|
+
<p>Files that change a lot in your project may be bad a sign.
|
12
|
+
This task uses your source control log to identify those files.
|
13
|
+
</p>
|
14
|
+
<table>
|
15
|
+
<tr><th>File Path</th><th>Times Changed</th></tr>
|
16
|
+
<% @changes.to_a.sort {|x,y| y[1] <=> x[1]}.each do |change| %>
|
17
|
+
<tr><td><%= link_to_filename(change[0]) %></td><td class='warning'><%= change[1] %></td></tr>
|
18
|
+
<% end %>
|
19
|
+
</table>
|
20
|
+
<p>Generated on <%= Time.now.localtime %></p>
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
body {
|
2
|
+
background-color: #efefef;
|
3
|
+
margin: 20px;
|
4
|
+
padding: 0;
|
5
|
+
font: 12px verdana, arial, helvetica;
|
6
|
+
}
|
7
|
+
|
8
|
+
table {
|
9
|
+
border-collapse: collapse;
|
10
|
+
border: 1px solid #666;
|
11
|
+
background: #fff;
|
12
|
+
margin-bottom: 20px;
|
13
|
+
}
|
14
|
+
|
15
|
+
table tr.light {
|
16
|
+
background: #fff;
|
17
|
+
}
|
18
|
+
|
19
|
+
table tr.dark {
|
20
|
+
background: #f9f9f9;
|
21
|
+
}
|
22
|
+
|
23
|
+
table td, table th {
|
24
|
+
padding: 4px 10px;
|
25
|
+
font-size: 13px;
|
26
|
+
}
|
27
|
+
table th {
|
28
|
+
text-align: center;
|
29
|
+
color: #fc0;
|
30
|
+
background: #336;
|
31
|
+
font-weight: bold;
|
32
|
+
border: #d0d0d0 1px solid;
|
33
|
+
}
|
34
|
+
|
35
|
+
table td {
|
36
|
+
border: #d0d0d0 1px solid;
|
37
|
+
}
|
38
|
+
|
39
|
+
table td.score {
|
40
|
+
text-align: right;
|
41
|
+
}
|
42
|
+
|
43
|
+
.warning {
|
44
|
+
background: yellow;
|
45
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Flay Results</title>
|
4
|
+
<style>
|
5
|
+
<%= inline_css("default.css") %>
|
6
|
+
</style>
|
7
|
+
</head>
|
8
|
+
|
9
|
+
<body>
|
10
|
+
<h1>Flay Results</h1>
|
11
|
+
<p><a href='http://ruby.sadi.st/Flay.html'>Flay</a> analyzes ruby code for structural similarities.</p>
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<th>Files</th>
|
15
|
+
<th>Matches</th>
|
16
|
+
</tr>
|
17
|
+
<% @matches.each_with_index do |match, count| %>
|
18
|
+
<tr class='<%= cycle("light", "dark", count) %>'>
|
19
|
+
<td>
|
20
|
+
<% match[1..-1].each do |filename| %>
|
21
|
+
<%= link_to_filename(*filename.split(":")) %><br>
|
22
|
+
<% end %>
|
23
|
+
</td>
|
24
|
+
<td><%= match.first %></td>
|
25
|
+
</tr>
|
26
|
+
<% end %>
|
27
|
+
</table>
|
28
|
+
<p>Generated on <%= Time.now.localtime %></p>
|
29
|
+
</body>
|
30
|
+
</html>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Flog Reporter</title>
|
4
|
+
<style>
|
5
|
+
<%= inline_css("default.css") %>
|
6
|
+
</style>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<h1>Flog Results</h1>
|
10
|
+
<p><a href='http://ruby.sadi.st/Flog.html'>Flog</a> measures code complexity.</p>
|
11
|
+
<table>
|
12
|
+
<tr>
|
13
|
+
<th>File</th>
|
14
|
+
<th>Total score</th>
|
15
|
+
<th>Methods</th>
|
16
|
+
<th>Average score</th>
|
17
|
+
<th>Highest score</th>
|
18
|
+
</tr>
|
19
|
+
<% pages.sort {|x,y| y.highest_score <=> x.highest_score }.each_with_index do |page, count| %>
|
20
|
+
<tr class='<%= cycle("light", "dark", count) %>'>
|
21
|
+
<td><a href='<%= page.path %>'><%= page.path.sub('.html', '.rb') %></a></td>
|
22
|
+
<td class='score'><%= sprintf(SCORE_FORMAT, page.score) %></td>
|
23
|
+
<td class='score'><%= page.scanned_methods.length %></td>
|
24
|
+
<td class='score'><%= sprintf(SCORE_FORMAT, page.average_score) %></td>
|
25
|
+
<td class='score'><%= sprintf(SCORE_FORMAT, page.highest_score) %></td>
|
26
|
+
</tr>
|
27
|
+
<% end %>
|
28
|
+
<tr>
|
29
|
+
<td><strong>Totals</strong></td>
|
30
|
+
<td class='score'><strong><%= sprintf(SCORE_FORMAT, pages.inject(0){|sum, page| sum + page.score }) %></strong></td>
|
31
|
+
<td class='score'><strong><%= pages.inject(0){|sum, page| sum + page.scanned_methods.length } %></strong></td>
|
32
|
+
<td class='score'></td>
|
33
|
+
<td class='score'></td>
|
34
|
+
</tr>
|
35
|
+
</table>
|
36
|
+
<p>Generated on <%= Time.now.localtime %></p>
|
37
|
+
</body>
|
38
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style>
|
4
|
+
<%= inline_css("default.css") %>
|
5
|
+
</style>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<p>Score: <%= score %></p>
|
9
|
+
<% scanned_methods.each do |sm| %>
|
10
|
+
<p><strong><%= sm.name %> (<%= sm.score %>)</strong></p>
|
11
|
+
<table>
|
12
|
+
<tr>
|
13
|
+
<th>Score</th>
|
14
|
+
<th>Operator</th>
|
15
|
+
</tr>
|
16
|
+
<% sm.operators.each_with_index do |operator, count| %>
|
17
|
+
<tr class='<%= cycle("light", "dark", count) %>'>
|
18
|
+
<td class='score'><%= sprintf(SCORE_FORMAT, operator.score) %></td>
|
19
|
+
<td class='score'><%= operator.operator %></td>
|
20
|
+
</tr>
|
21
|
+
<% end %>
|
22
|
+
</table>
|
23
|
+
<% end %>
|
24
|
+
</body>
|
25
|
+
</html>
|