kwala 0.9.0
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 +62 -0
- data/bin/kwala +39 -0
- data/lib/kwala.rb +59 -0
- data/lib/kwala/actions/code_change.rb +128 -0
- data/lib/kwala/actions/code_coverage.rb +303 -0
- data/lib/kwala/actions/code_duplication.rb +222 -0
- data/lib/kwala/actions/code_formatting.rb +358 -0
- data/lib/kwala/actions/cycle_detection.rb +118 -0
- data/lib/kwala/actions/cyclomatic_complexity.rb +159 -0
- data/lib/kwala/actions/dead_code.rb +68 -0
- data/lib/kwala/actions/executable_files_check.rb +111 -0
- data/lib/kwala/actions/flagged_comments.rb +128 -0
- data/lib/kwala/actions/gem_plugin.rb +31 -0
- data/lib/kwala/actions/loc_count.rb +153 -0
- data/lib/kwala/actions/multi_ruby_unit_test.rb +62 -0
- data/lib/kwala/actions/outside_links.rb +85 -0
- data/lib/kwala/actions/rails_migrate.rb +62 -0
- data/lib/kwala/actions/strange_requires.rb +106 -0
- data/lib/kwala/actions/syntax_check.rb +130 -0
- data/lib/kwala/actions/unit_test.rb +368 -0
- data/lib/kwala/build_action.rb +88 -0
- data/lib/kwala/build_context.rb +49 -0
- data/lib/kwala/command_line_runner.rb +184 -0
- data/lib/kwala/ext/demos.jar +0 -0
- data/lib/kwala/ext/pmd.jar +0 -0
- data/lib/kwala/ext/prefuse.jar +0 -0
- data/lib/kwala/extensions.rb +36 -0
- data/lib/kwala/lib/code_analyzer.rb +678 -0
- data/lib/kwala/lib/cycle_detector.rb +793 -0
- data/lib/kwala/lib/strange_requires_detector.rb +145 -0
- data/lib/kwala/notification.rb +45 -0
- data/lib/kwala/notifications/email.rb +54 -0
- data/lib/kwala/notifications/rss.rb +151 -0
- data/lib/kwala/project_builder_utils.rb +178 -0
- data/lib/kwala/templates/build_template.html +33 -0
- data/lib/kwala/templates/code_change_summary.html +27 -0
- data/lib/kwala/templates/code_coverage_detailed.html +25 -0
- data/lib/kwala/templates/code_coverage_summary.html +30 -0
- data/lib/kwala/templates/code_duplication_detailed.html +45 -0
- data/lib/kwala/templates/code_duplication_summary.html +9 -0
- data/lib/kwala/templates/code_formatting_detailed.html +27 -0
- data/lib/kwala/templates/code_formatting_summary.html +11 -0
- data/lib/kwala/templates/cycle_detection_detailed.html +20 -0
- data/lib/kwala/templates/cycle_detection_summary.html +7 -0
- data/lib/kwala/templates/cyclomatic_complexity_summary.html +27 -0
- data/lib/kwala/templates/executable_files_check_detailed.html +31 -0
- data/lib/kwala/templates/executable_files_check_summary.html +20 -0
- data/lib/kwala/templates/flagged_comments_detailed.html +22 -0
- data/lib/kwala/templates/flagged_comments_summary.html +10 -0
- data/lib/kwala/templates/loc_count_detailed.html +24 -0
- data/lib/kwala/templates/loc_count_summary.html +14 -0
- data/lib/kwala/templates/mdd/1.png +0 -0
- data/lib/kwala/templates/mdd/2.png +0 -0
- data/lib/kwala/templates/mdd/3.png +0 -0
- data/lib/kwala/templates/mdd/4.png +0 -0
- data/lib/kwala/templates/mdd/5.png +0 -0
- data/lib/kwala/templates/outside_links_summary.html +4 -0
- data/lib/kwala/templates/strange_requires_detailed.html +23 -0
- data/lib/kwala/templates/strange_requires_summary.html +13 -0
- data/lib/kwala/templates/style.css +95 -0
- data/lib/kwala/templates/syntax_check_detailed.html +21 -0
- data/lib/kwala/templates/syntax_check_summary.html +7 -0
- data/lib/kwala/templates/unit_test_detailed.html +66 -0
- data/lib/kwala/templates/unit_test_summary.html +70 -0
- data/test/tc_build_action.rb +32 -0
- data/test/tc_templates.rb +24 -0
- metadata +118 -0
data/README
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Kwala
|
2
|
+
|
3
|
+
Description:
|
4
|
+
Kwala is a build tool that contains a number of actions to check the "quality"
|
5
|
+
of your ruby source code. Some of the included actions are checks for code
|
6
|
+
duplication, cyclomatic complexity, lines of code, unit tests, various
|
7
|
+
formatting checks, and require cycles. In addition, Kwala is designed to
|
8
|
+
allow users to add and create new actions to check against the code. It
|
9
|
+
generates both an HTML summary page and detail pages for the various actions.
|
10
|
+
|
11
|
+
License: BSD
|
12
|
+
|
13
|
+
Usage:
|
14
|
+
|
15
|
+
Kwala is a command line tool that allows you to specify which actions to run
|
16
|
+
on your project. Because Kwala is a command line tool it is easy to setup as
|
17
|
+
a cron job to perform periodic checks against your code base.
|
18
|
+
|
19
|
+
An example for hourly builds:
|
20
|
+
|
21
|
+
kwala -p MyProject -d projects/MyProject -o builds -a syntax_check -a formatting -a unit_test -a cyclomatic -a cycles
|
22
|
+
|
23
|
+
For more details on how to use it, please see the usage notes by running
|
24
|
+
"kwala -?""
|
25
|
+
|
26
|
+
Install:
|
27
|
+
|
28
|
+
As root run
|
29
|
+
# ruby setup.rb all
|
30
|
+
|
31
|
+
Dependencies:
|
32
|
+
Kwala uses Amrita 1 to generate the HTML results pages.
|
33
|
+
You can get it from here: http://amrita.sourceforge.jp/
|
34
|
+
At the moment Amrita 1 is not available as a RubyGem.
|
35
|
+
|
36
|
+
External Tools Used:
|
37
|
+
Some actions use external tools to perform the actual check or present
|
38
|
+
the results. If an action cannot find its required tools, that action
|
39
|
+
will simply not generate any output. For your convenience, we already
|
40
|
+
bundled a few jar files (see in the ext directory) that some actions use.
|
41
|
+
Here is a complete list of the projects from which those tools originate:
|
42
|
+
|
43
|
+
PMD's CPD for code duplication.
|
44
|
+
http://pmd.sourceforge.net/
|
45
|
+
|
46
|
+
Prefuse for require graph visualization.
|
47
|
+
http://www.prefuse.org/
|
48
|
+
|
49
|
+
Saikuro for cyclomatic complexity checks
|
50
|
+
http://saikuro.rubyforge.org/
|
51
|
+
|
52
|
+
rcov for code coverage
|
53
|
+
http://eigenclass.org/hiki.rb?rcov
|
54
|
+
|
55
|
+
MultiRuby for running unit tests against various versions of Ruby.
|
56
|
+
http://www.zenspider.com/ZSS/Products/ZenTest/
|
57
|
+
|
58
|
+
Authors and Contributors:
|
59
|
+
Zev Blut
|
60
|
+
Pierre Baumard
|
61
|
+
Michael Reinsch
|
62
|
+
Paul McMahon
|
data/bin/kwala
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#! /usr/bin/ruby1.8
|
2
|
+
|
3
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are
|
8
|
+
# met:
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright
|
12
|
+
# notice, this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# * Redistributions in binary form must reproduce the above
|
15
|
+
# copyright notice, this list of conditions and the following
|
16
|
+
# disclaimer in the documentation and/or other materials provided
|
17
|
+
# with the distribution.
|
18
|
+
#
|
19
|
+
# * Neither the name of Ubit nor the names of its
|
20
|
+
# contributors may be used to endorse or promote products derived
|
21
|
+
# from this software without specific prior written permission.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
25
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
26
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
27
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
28
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
29
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
30
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
31
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
32
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
33
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
34
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
#
|
36
|
+
# == Author
|
37
|
+
# Zev Blut (zb@ubit.com)
|
38
|
+
|
39
|
+
require 'kwala/command_line_runner'
|
data/lib/kwala.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# * Redistributions in binary form must reproduce the above
|
13
|
+
# copyright notice, this list of conditions and the following
|
14
|
+
# disclaimer in the documentation and/or other materials provided
|
15
|
+
# with the distribution.
|
16
|
+
#
|
17
|
+
# * Neither the name of Ubit nor the names of its
|
18
|
+
# contributors may be used to endorse or promote products derived
|
19
|
+
# from this software without specific prior written permission.
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
23
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
25
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
26
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
27
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
28
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
29
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
30
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
31
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
32
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
#
|
34
|
+
# == Author
|
35
|
+
# Zev Blut (zb@ubit.com)
|
36
|
+
|
37
|
+
|
38
|
+
require "find"
|
39
|
+
require "open3"
|
40
|
+
require "fileutils"
|
41
|
+
require "stringio"
|
42
|
+
require "yaml"
|
43
|
+
require "timeout"
|
44
|
+
require "amrita/template"
|
45
|
+
include Amrita
|
46
|
+
require 'erb'
|
47
|
+
|
48
|
+
BASE_DIR = File.expand_path(File.dirname(__FILE__)) + "/kwala"
|
49
|
+
TEMPLATE_DIR = "#{BASE_DIR}/templates/"
|
50
|
+
ACTION_DIR = "#{BASE_DIR}/actions"
|
51
|
+
EXTERNAL_DIR = "#{BASE_DIR}/ext/"
|
52
|
+
LIB_DIR = "#{BASE_DIR}/lib"
|
53
|
+
NOTIFICATION_DIR = "#{BASE_DIR}/notifications"
|
54
|
+
|
55
|
+
require 'kwala/extensions'
|
56
|
+
require 'kwala/build_action'
|
57
|
+
require 'kwala/build_context'
|
58
|
+
require 'kwala/project_builder_utils'
|
59
|
+
require 'kwala/notification'
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# $Id: code_change.rb 34 2008-04-03 06:01:19Z zev $
|
2
|
+
#
|
3
|
+
# This file implements the logic to check for additions and removals
|
4
|
+
# of methods and classes in a source base.
|
5
|
+
|
6
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
10
|
+
# modification, are permitted provided that the following conditions are
|
11
|
+
# met:
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# * Redistributions of source code must retain the above copyright
|
15
|
+
# notice, this list of conditions and the following disclaimer.
|
16
|
+
#
|
17
|
+
# * Redistributions in binary form must reproduce the above
|
18
|
+
# copyright notice, this list of conditions and the following
|
19
|
+
# disclaimer in the documentation and/or other materials provided
|
20
|
+
# with the distribution.
|
21
|
+
#
|
22
|
+
# * Neither the name of Ubit nor the names of its
|
23
|
+
# contributors may be used to endorse or promote products derived
|
24
|
+
# from this software without specific prior written permission.
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
28
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
29
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
30
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
31
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
32
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
33
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
34
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
35
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
36
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
37
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
38
|
+
#
|
39
|
+
# == Author
|
40
|
+
# Zev Blut (zb@ubit.com)
|
41
|
+
|
42
|
+
class CodeChangeAction < BuildAction
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@code_changes = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_action(context)
|
49
|
+
@code_changes = find_code_changes(context.ri_dir_1, context.ri_dir_2)
|
50
|
+
end
|
51
|
+
|
52
|
+
def summary_display(context)
|
53
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
54
|
+
context.amrita_data[:code_change_results] = @code_changes
|
55
|
+
summary_expand(template, context)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.detailed_template_file
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def filter_method_from_classes?(method, classes)
|
65
|
+
m = /(.*)\/.+\.yaml$/.match(method)
|
66
|
+
base = m[1]
|
67
|
+
classes.include?(base)
|
68
|
+
end
|
69
|
+
|
70
|
+
def strip_ri_method_name(meth)
|
71
|
+
m = /(.*)-(i|c)\.yaml/.match(meth)
|
72
|
+
m[1]
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_code_changes(ri_dir="/tmp/ri", new_ri_dir="/tmp/new_ri")
|
76
|
+
results = Hash.new
|
77
|
+
|
78
|
+
new_ri_files = find_ri_files(new_ri_dir)
|
79
|
+
old_ri_files = find_ri_files(ri_dir)
|
80
|
+
|
81
|
+
new_classes = new_ri_files.reject {|f| f =~ /\.(yaml|rid)$/ || f == new_ri_dir }
|
82
|
+
old_classes = old_ri_files.reject {|f| f =~ /\.(yaml|rid)$/ || f == ri_dir }
|
83
|
+
|
84
|
+
new_methods = new_ri_files.find_all {|f| f =~/\.yaml$/}
|
85
|
+
old_methods = old_ri_files.find_all {|f| f =~/\.yaml$/}
|
86
|
+
|
87
|
+
rep_new_classes = new_classes - old_classes
|
88
|
+
rep_remove_classes = old_classes - new_classes
|
89
|
+
|
90
|
+
rep_new_methods = new_methods - old_methods
|
91
|
+
rep_remove_methods = old_methods - new_methods
|
92
|
+
|
93
|
+
rep_new_methods = rep_new_methods.find_all {|f| !filter_method_from_classes?(f, rep_new_classes)}
|
94
|
+
rep_remove_methods = rep_remove_methods.find_all {|f| !filter_method_from_classes?(f, rep_remove_classes)}
|
95
|
+
|
96
|
+
rep_new_class_methods, rep_new_instance_methods = rep_new_methods.partition {|m| m =~ /-c\.yaml$/}
|
97
|
+
rep_remove_class_methods, rep_remove_instance_methods = rep_remove_methods.partition {|m| m =~ /-c\.yaml$/}
|
98
|
+
|
99
|
+
rep_new_instance_methods = rep_new_instance_methods.map {|m| strip_ri_method_name(m)}
|
100
|
+
rep_remove_instance_methods = rep_remove_instance_methods.map {|m| strip_ri_method_name(m)}
|
101
|
+
|
102
|
+
rep_new_class_methods = rep_new_class_methods.map {|m| strip_ri_method_name(m)}
|
103
|
+
rep_remove_class_methods = rep_remove_class_methods.map {|m| strip_ri_method_name(m)}
|
104
|
+
|
105
|
+
results[:new_classes] = make_table_iteration( rep_new_classes )
|
106
|
+
results[:removed_classes] = make_table_iteration( rep_remove_classes )
|
107
|
+
results[:new_instance_methods] = make_table_iteration( rep_new_instance_methods )
|
108
|
+
results[:removed_instance_methods] = make_table_iteration( rep_remove_instance_methods )
|
109
|
+
results[:new_class_methods] = make_table_iteration( rep_new_class_methods )
|
110
|
+
results[:removed_class_methods] = make_table_iteration( rep_remove_class_methods )
|
111
|
+
|
112
|
+
results
|
113
|
+
end
|
114
|
+
|
115
|
+
def make_table_iteration(data)
|
116
|
+
{:item => data.map { |c| { :name => c } } }
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_ri_files(ri_dir)
|
120
|
+
files = Array.new
|
121
|
+
Find.find(ri_dir) do |file|
|
122
|
+
files << ProjectBuilderUtils.strip_base_dir(ri_dir, file)
|
123
|
+
end
|
124
|
+
files
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,303 @@
|
|
1
|
+
# Code coverage analysis on Unit Tests.
|
2
|
+
#
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
5
|
+
# All rights reserved.
|
6
|
+
#
|
7
|
+
# Redistribution and use in source and binary forms, with or without
|
8
|
+
# modification, are permitted provided that the following conditions are
|
9
|
+
# met:
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# * Redistributions of source code must retain the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer.
|
14
|
+
#
|
15
|
+
# * Redistributions in binary form must reproduce the above
|
16
|
+
# copyright notice, this list of conditions and the following
|
17
|
+
# disclaimer in the documentation and/or other materials provided
|
18
|
+
# with the distribution.
|
19
|
+
#
|
20
|
+
# * Neither the name of Ubit nor the names of its
|
21
|
+
# contributors may be used to endorse or promote products derived
|
22
|
+
# from this software without specific prior written permission.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
26
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
27
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
28
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
29
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
30
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
31
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
32
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
33
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
34
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
35
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
36
|
+
#
|
37
|
+
# == Author
|
38
|
+
# Zev Blut, Pierre Baumard
|
39
|
+
|
40
|
+
require "kwala"
|
41
|
+
require "#{ACTION_DIR}/loc_count"
|
42
|
+
require 'tempfile'
|
43
|
+
|
44
|
+
# run with
|
45
|
+
# ./kwala -p TestProj -d ~/projects/TestProj
|
46
|
+
# -a code_coverage -o ~/tmp/builder
|
47
|
+
|
48
|
+
class CodeCoverageAction < BuildAction
|
49
|
+
include LOCCountModule
|
50
|
+
|
51
|
+
CodeCoverageFileData = Struct.new(:ruby_file, :percent, :rcov_index,
|
52
|
+
:file_cov_path, :loc)
|
53
|
+
|
54
|
+
RCOV = '/usr/bin/env rcov'
|
55
|
+
|
56
|
+
SUMMARY_COUNT = 30
|
57
|
+
|
58
|
+
PERCENT_ERROR = 70
|
59
|
+
|
60
|
+
PERCENT_WARNING = 90
|
61
|
+
|
62
|
+
DEBUG = false
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@output_data = Array.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_action(context)
|
69
|
+
@ouput_dir = File.expand_path(context.output_directory)
|
70
|
+
if !File.exists?("#{@ouput_dir}/coverage")
|
71
|
+
FileUtils.mkdir("#{@ouput_dir}/coverage")
|
72
|
+
end
|
73
|
+
|
74
|
+
@test_cases_count = context.test_files.size
|
75
|
+
|
76
|
+
run_test_cases(context)
|
77
|
+
|
78
|
+
context.ruby_files.each do |ruby_file|
|
79
|
+
test_path = test_index_path(context, ruby_file)
|
80
|
+
rcov_index = test_path + "index.html"
|
81
|
+
file_cov_path = test_path + file_coverage_filename(context, ruby_file)
|
82
|
+
loc = count_loc(ruby_file).first
|
83
|
+
if File.exist?(test_path)
|
84
|
+
percent = parse_coverage_file(file_cov_path)
|
85
|
+
@output_data << CodeCoverageFileData.new(ruby_file, percent,
|
86
|
+
rcov_index, file_cov_path, loc)
|
87
|
+
else
|
88
|
+
@output_data << CodeCoverageFileData.new(ruby_file, nil, rcov_index,
|
89
|
+
file_cov_path, loc)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def summary_display(context)
|
97
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
98
|
+
unit_base = "#{context.project_name}_code_coverage.html"
|
99
|
+
|
100
|
+
cov_loc_weighted_sum = @output_data.inject(0) do |sum, file_data|
|
101
|
+
sum + (file_data.percent.to_i * file_data.loc)
|
102
|
+
end
|
103
|
+
loc_sum = @output_data.inject(0){|sum, file_data| sum + file_data.loc}
|
104
|
+
|
105
|
+
global_cov = cov_loc_weighted_sum / loc_sum
|
106
|
+
|
107
|
+
context.amrita_data[:code_coverage_results] = {
|
108
|
+
:c_num => @test_cases_count,
|
109
|
+
:global_cov => global_cov
|
110
|
+
}
|
111
|
+
|
112
|
+
output_summary = @output_data.sort do |x, y|
|
113
|
+
non_covered_loc(y) <=> non_covered_loc(x)
|
114
|
+
end
|
115
|
+
context.amrita_data[:code_coverage_summary] = {
|
116
|
+
:entry => entries(output_summary[0..SUMMARY_COUNT])
|
117
|
+
}
|
118
|
+
|
119
|
+
context.amrita_data[:code_coverage_details] =
|
120
|
+
(Amrita::e(:a, :href => unit_base) { "Code Coverage Details" })
|
121
|
+
summary_expand(template, context)
|
122
|
+
end
|
123
|
+
|
124
|
+
def detailed_display(context)
|
125
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
126
|
+
|
127
|
+
context.amrita_data[:code_coverage_details] = {
|
128
|
+
:entry => entries(@output_data)
|
129
|
+
}
|
130
|
+
|
131
|
+
det_res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
|
132
|
+
det_file = "#{context.output_directory}/#{context.project_name}_code_coverage.html"
|
133
|
+
[det_file, det_res]
|
134
|
+
end
|
135
|
+
|
136
|
+
###########################
|
137
|
+
# Protected Methods
|
138
|
+
###########################
|
139
|
+
protected
|
140
|
+
|
141
|
+
def non_covered_loc(file_data)
|
142
|
+
(((100.0 - file_data.percent.to_f) / 100.0) * file_data.loc).to_i
|
143
|
+
end
|
144
|
+
|
145
|
+
def url_alias(url)
|
146
|
+
url.sub(@ouput_dir, '').sub(/^(\/)*/, '')
|
147
|
+
end
|
148
|
+
|
149
|
+
def entries(output_data)
|
150
|
+
output_data.map do |file|
|
151
|
+
{
|
152
|
+
:file => amrita_file(file.percent, file.ruby_file, file.rcov_index),
|
153
|
+
:loc => file.loc.to_s,
|
154
|
+
:nloc => non_covered_loc(file).to_s,
|
155
|
+
:result => put_class(file.percent, amrita_result(file.percent, file.file_cov_path))
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
def put_class(percent, amrita_e)
|
162
|
+
percent = percent.to_i
|
163
|
+
if percent < PERCENT_ERROR
|
164
|
+
Amrita.a(:class => "error") {amrita_e}
|
165
|
+
elsif percent < PERCENT_WARNING
|
166
|
+
Amrita.a(:class => "warning") {amrita_e}
|
167
|
+
else
|
168
|
+
Amrita.a(:class => "ok") {amrita_e}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def amrita_file(percent, ruby_file, rcov_index)
|
173
|
+
if percent
|
174
|
+
Amrita.e(:a, :href => url_alias(rcov_index)) {ruby_file}
|
175
|
+
else
|
176
|
+
Amrita.e(:span, :title => "no file #{rcov_index}") {ruby_file}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def amrita_result(percent, file_cov_path)
|
181
|
+
if percent.to_i > 0
|
182
|
+
Amrita.e(:a, :href => url_alias(file_cov_path)) {"#{percent} %"}
|
183
|
+
else
|
184
|
+
Amrita.e(:span, :title => "bad file #{file_cov_path}") {"0 %"}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_index_path(context, file)
|
189
|
+
proj_dir = context.project_directory.sub(/\/$/, '')
|
190
|
+
test_file = File.expand_path(proj_dir) + '_tests' +
|
191
|
+
File.dirname(file).sub(proj_dir, '') +
|
192
|
+
'_tc_' + File.basename(file)
|
193
|
+
out_file_dir(test_file, context.output_directory)[1]
|
194
|
+
end
|
195
|
+
|
196
|
+
def file_coverage_filename(context, file)
|
197
|
+
# from /usr/local/bin/rcov:74:in `normalize_filename' (rcov 0.5)
|
198
|
+
file = File.expand_path(file).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
|
199
|
+
|
200
|
+
file.split("/").join("-").gsub('.', '_') + ".html"
|
201
|
+
end
|
202
|
+
|
203
|
+
COV_PATTERN = "<td class='covered' width='(\\d*)'"
|
204
|
+
REGEXP = /#{COV_PATTERN}.*#{COV_PATTERN}/m
|
205
|
+
|
206
|
+
def parse_coverage_file(file)
|
207
|
+
if File.exists?(file) and (match = REGEXP.match(File.read(file)))
|
208
|
+
match[2].to_i
|
209
|
+
else
|
210
|
+
0
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def out_file_dir(test_file, out_dir)
|
215
|
+
f_out = test_file.split("/").join("_") + "_coverage"
|
216
|
+
o_dir = "#{out_dir}/coverage/#{f_out}/"
|
217
|
+
[f_out, o_dir]
|
218
|
+
end
|
219
|
+
|
220
|
+
def exclude_cmd(context)
|
221
|
+
#if context.project_name =~ LIB_PROJECTS
|
222
|
+
# # discard the default regexp list, do not exclude site_ruby lib
|
223
|
+
# "--exclude-only lib/ruby/1.8"
|
224
|
+
#else
|
225
|
+
"--exclude lib/ruby"
|
226
|
+
#end
|
227
|
+
end
|
228
|
+
|
229
|
+
def run_test_cases(context)
|
230
|
+
context.test_files.each do |test_file|
|
231
|
+
run_test_case_with_rcov(test_file, context)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def run_test_case_with_rcov(test_file, context)
|
236
|
+
test_file = File.expand_path(test_file)
|
237
|
+
f_out, o_dir = out_file_dir(test_file, @ouput_dir)
|
238
|
+
o_dir = File.expand_path(o_dir)
|
239
|
+
|
240
|
+
if File.exists?(o_dir)
|
241
|
+
FileUtils.rm_rf(o_dir)
|
242
|
+
end
|
243
|
+
FileUtils.mkdir(o_dir)
|
244
|
+
|
245
|
+
cmd = "#{RCOV} #{exclude_cmd(context)} -o #{o_dir} #{test_file}"
|
246
|
+
puts cmd if DEBUG
|
247
|
+
system(cmd)
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# use --aggregate option of rcov >= 0.7
|
254
|
+
class AggregateCodeCoverageAction < CodeCoverageAction
|
255
|
+
|
256
|
+
def self.summary_template_file
|
257
|
+
superclass.summary_template_file
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.detailed_template_file
|
261
|
+
superclass.detailed_template_file
|
262
|
+
end
|
263
|
+
|
264
|
+
##################
|
265
|
+
# protected
|
266
|
+
##################
|
267
|
+
protected
|
268
|
+
|
269
|
+
def out_file_dir(test_file, out_dir)
|
270
|
+
f_out = test_file.split("/").join("_") + "_coverage"
|
271
|
+
[f_out, "#{out_dir}/coverage/aggregate/"]
|
272
|
+
end
|
273
|
+
|
274
|
+
def run_test_cases(context)
|
275
|
+
test_files = context.test_files.dup.map {|file| File.expand_path(file)}
|
276
|
+
|
277
|
+
o_dir = File.expand_path("#{File.expand_path(context.output_directory)}/coverage/aggregate")
|
278
|
+
puts o_dir
|
279
|
+
if File.exists?(o_dir)
|
280
|
+
FileUtils.rm_rf(o_dir)
|
281
|
+
end
|
282
|
+
FileUtils.mkdir(o_dir)
|
283
|
+
|
284
|
+
rcov_data = Tempfile.new('rcov_aggregate').path
|
285
|
+
|
286
|
+
one_file = test_files.shift
|
287
|
+
|
288
|
+
# first do not generate html, just aggregate to rcov file data
|
289
|
+
test_files.each do |file|
|
290
|
+
# "No file to analyze was found" with just --no-html
|
291
|
+
cmd = "#{RCOV} #{exclude_cmd(context)} --no-html -t --aggregate #{rcov_data} #{file}"
|
292
|
+
puts cmd if DEBUG
|
293
|
+
system(cmd)
|
294
|
+
end
|
295
|
+
|
296
|
+
# generate HTML for last file to be run
|
297
|
+
cmd = "#{RCOV} #{exclude_cmd(context)} --aggregate #{rcov_data} -o #{o_dir} #{one_file}"
|
298
|
+
system(cmd)
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|