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
@@ -0,0 +1,62 @@
|
|
1
|
+
class RailsMigrateAction < BuildAction
|
2
|
+
|
3
|
+
def build_action(context)
|
4
|
+
@res = `RAILS_ENV=test rake cerego:db:remigrate 2>&1`
|
5
|
+
@success = $?.exitstatus == 0
|
6
|
+
nres = ''
|
7
|
+
if !@success
|
8
|
+
# QUICK HACK as remigrate blows up on a timeout lock..
|
9
|
+
nres = `RAILS_ENV=test rake db:migrate 2>&1`
|
10
|
+
@success = $?.exitstatus == 0
|
11
|
+
@res = @res + "\n ------ MIGRATE RERUN -----\n\n\n" + nres
|
12
|
+
end
|
13
|
+
|
14
|
+
if !@success
|
15
|
+
raise KwalaAbortBuildException.new
|
16
|
+
end
|
17
|
+
|
18
|
+
return @success
|
19
|
+
# return unless @success
|
20
|
+
# @res = @res + "\n ----- MIGRATE REVERSE ----\n\n\n" + nres
|
21
|
+
# reverse = `RAILS_ENV=test rake db:migrate VERSION=0 2>&1; echo $?`
|
22
|
+
# unless $?.exitstatus == 0
|
23
|
+
# # Reverse migration failed
|
24
|
+
# @success = (reverse.index('ActiveRecord::IrreversibleMigration') != nil)
|
25
|
+
# # Success here if and only if Migration is irreversible
|
26
|
+
# end
|
27
|
+
# @res = @res + reverse
|
28
|
+
# return unless @success
|
29
|
+
# @res = @res + "\n ----- MIGRATE FORWARD ----\n\n\n" + nres
|
30
|
+
# @res = `RAILS_ENV=test rake db:migrate 2>&1; echo $?`
|
31
|
+
# @success = $?.exitstatus == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def score
|
35
|
+
# success is great else fail!
|
36
|
+
@success ? 10 : -1000
|
37
|
+
end
|
38
|
+
|
39
|
+
def summary_display(context)
|
40
|
+
details_link = "<a href=\"#{context.project_name}_rails_migrate.html\">Details</a>"
|
41
|
+
if @success
|
42
|
+
Amrita::SanitizedString.new("<h2>MIGRATION Succeeded.</h2>#{details_link}")
|
43
|
+
else
|
44
|
+
Amrita::SanitizedString.new("<h2>MIGRATION FAILED !:</h2>#{details_link}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def detailed_display(context)
|
49
|
+
det_file = "#{context.output_directory}/#{context.project_name}_rails_migrate.html"
|
50
|
+
res = "<html><body><h1>Migration Details</h1><pre><code>#{@res}</code></pre></body></html>"
|
51
|
+
[det_file, res]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.summary_template_file
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.detailed_template_file
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# $Id: strange_requires.rb 32 2008-04-02 07:50:18Z 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
|
+
# Michael Reinsch
|
41
|
+
|
42
|
+
require "#{LIB_DIR}/strange_requires_detector"
|
43
|
+
|
44
|
+
class StrangeRequires < BuildAction
|
45
|
+
|
46
|
+
class AmritaPrintStrategy
|
47
|
+
def initialize
|
48
|
+
@subdir_reqs = []
|
49
|
+
@parentdir_reqs = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def subdir_requirement(from, to)
|
53
|
+
@subdir_reqs << { :from => from, :to => to }
|
54
|
+
end
|
55
|
+
def parent_dir_requirement(from, to)
|
56
|
+
if not /\/lib\// =~ to
|
57
|
+
@parentdir_reqs << { :from => from, :to => to }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def require_data_summary
|
62
|
+
{ :sub_dir_requires => @subdir_reqs.size,
|
63
|
+
:parent_dir_requires => @parentdir_reqs.size }
|
64
|
+
end
|
65
|
+
|
66
|
+
def require_data_detail
|
67
|
+
[ { :require_type => "Subdirectory Requirements",
|
68
|
+
:entry => @subdir_reqs },
|
69
|
+
{ :require_type => "Parent Directory Requirements",
|
70
|
+
:entry => @parentdir_reqs} ]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@amrita_ps = AmritaPrintStrategy.new
|
76
|
+
@strange_requires_detector = StrangeRequiresDetector.new(@amrita_ps)
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_action(context)
|
80
|
+
@strange_requires_detector.check(context.project_directory, context.project_directory)
|
81
|
+
end
|
82
|
+
|
83
|
+
def details_file(context)
|
84
|
+
"#{context.output_directory}/#{details_link(context)}"
|
85
|
+
end
|
86
|
+
def details_link(context)
|
87
|
+
"#{context.project_name}_requires.html"
|
88
|
+
end
|
89
|
+
|
90
|
+
def summary_display(context)
|
91
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
92
|
+
context.amrita_data[:require_results] = @amrita_ps.require_data_summary
|
93
|
+
context.amrita_data[:require_details] =
|
94
|
+
Amrita::e(:a, :href => details_link(context)) { "Requires Details" }
|
95
|
+
summary_expand(template, context)
|
96
|
+
end
|
97
|
+
|
98
|
+
def detailed_display(context)
|
99
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
100
|
+
context.amrita_data[:require_details] = @amrita_ps.require_data_detail
|
101
|
+
det_res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
|
102
|
+
det_file = details_file(context)
|
103
|
+
[det_file, det_res]
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# $Id: syntax_check.rb 39 2008-04-30 10:55:14Z zev $
|
2
|
+
#
|
3
|
+
# This file implements the logic to perform a syntax check.
|
4
|
+
|
5
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are
|
10
|
+
# met:
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright
|
14
|
+
# notice, this list of conditions and the following disclaimer.
|
15
|
+
#
|
16
|
+
# * Redistributions in binary form must reproduce the above
|
17
|
+
# copyright notice, this list of conditions and the following
|
18
|
+
# disclaimer in the documentation and/or other materials provided
|
19
|
+
# with the distribution.
|
20
|
+
#
|
21
|
+
# * Neither the name of Ubit nor the names of its
|
22
|
+
# contributors may be used to endorse or promote products derived
|
23
|
+
# from this software without specific prior written permission.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
27
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
28
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
29
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
30
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
31
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
32
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
33
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
34
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
35
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
36
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
37
|
+
#
|
38
|
+
# == Author
|
39
|
+
# Zev Blut (zb@ubit.com)
|
40
|
+
|
41
|
+
class SyntaxCheckAction < BuildAction
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@syntax_results = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_action(context)
|
48
|
+
@syntax_results = check_ruby_syntax(context.ruby_files)
|
49
|
+
end
|
50
|
+
|
51
|
+
def summary_display(context)
|
52
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
53
|
+
|
54
|
+
if @syntax_results.empty?
|
55
|
+
context.amrita_data[:syntax_details] = "Syntax Check"
|
56
|
+
context.amrita_data[:syntax_results] = "There are no syntax errors."
|
57
|
+
else
|
58
|
+
context.amrita_data[:syntax_results] = {
|
59
|
+
:entry => @syntax_results
|
60
|
+
}
|
61
|
+
|
62
|
+
syntax_base = "#{context.project_name}_syntax.html"
|
63
|
+
if context.amrita_data[:syntax_results][:entry]
|
64
|
+
context.amrita_data[:syntax_results][:entry].each do |entry|
|
65
|
+
entry[:file_name_link] = Amrita::a(:href => "#{syntax_base}\##{entry[:file_name]}") { entry[:file_name] }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context.amrita_data[:syntax_details] =
|
70
|
+
(Amrita::e(:a, :href=>syntax_base) { "Syntax Check Details" })
|
71
|
+
end
|
72
|
+
summary_expand(template, context)
|
73
|
+
end
|
74
|
+
|
75
|
+
def detailed_display(context)
|
76
|
+
context.amrita_data[:syntax_results] = {
|
77
|
+
:entry => @syntax_results
|
78
|
+
}
|
79
|
+
det_res = expand_syntax_check_template(context.amrita_data[:syntax_results])
|
80
|
+
det_file = "#{context.output_directory}/#{context.project_name}_syntax.html"
|
81
|
+
[det_file, det_res]
|
82
|
+
end
|
83
|
+
|
84
|
+
def score
|
85
|
+
return 10 if @syntax_results.empty?
|
86
|
+
errors = @syntax_results.map { |e| e[:error_count] }.inject(0) { |s,v| s + v}
|
87
|
+
if errors > 0
|
88
|
+
#errors > 10 ? -10 : 10 - errors
|
89
|
+
# For now any sytnax error is bad.
|
90
|
+
-100
|
91
|
+
else
|
92
|
+
# must have warnings 3 is an arbitrary choice at the moment.
|
93
|
+
3
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_ruby_syntax(ruby_files)
|
98
|
+
# need kcode addition
|
99
|
+
results = Array.new
|
100
|
+
ruby_files.each do |f|
|
101
|
+
entry = Hash.new
|
102
|
+
#STDERR.puts "Checking with command:\nruby -KS -c #{f}"
|
103
|
+
Open3.popen3("/usr/bin/env ruby -c #{f}") do |stdin, stdout, stderr|
|
104
|
+
stdout_data = time_out_readlines(stdout)
|
105
|
+
stderr_data = time_out_readlines(stderr)
|
106
|
+
if stdout_data.first !~ /Syntax OK/ || stderr_data.size > 0
|
107
|
+
warnings, errors = stderr_data.partition {|r| r =~ /: warning:/}
|
108
|
+
entry[:file_name] = f
|
109
|
+
entry[:warnings] = warnings
|
110
|
+
entry[:warning_count] = warnings.size
|
111
|
+
entry[:errors] = errors
|
112
|
+
entry[:error_count] = errors.size
|
113
|
+
results << entry
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
results
|
118
|
+
end
|
119
|
+
|
120
|
+
def expand_syntax_check_template(data)
|
121
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
122
|
+
data[:entry].each do |entry|
|
123
|
+
entry[:file_name_ref] = Amrita::a(:name => entry[:file_name]) { entry[:file_name] }
|
124
|
+
entry[:errors] = entry[:errors].to_a.map {|d| {:error => d} }
|
125
|
+
entry[:warnings] = entry[:warnings].to_a.map {|d| {:warning => d} }
|
126
|
+
end
|
127
|
+
ProjectBuilderUtils.expand_template(template, data)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
# $Id: unit_test.rb 33 2008-04-02 08:14:27Z zev $
|
2
|
+
#
|
3
|
+
# This file implements the logic to perform Unit Tests
|
4
|
+
|
5
|
+
# Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are
|
10
|
+
# met:
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright
|
14
|
+
# notice, this list of conditions and the following disclaimer.
|
15
|
+
#
|
16
|
+
# * Redistributions in binary form must reproduce the above
|
17
|
+
# copyright notice, this list of conditions and the following
|
18
|
+
# disclaimer in the documentation and/or other materials provided
|
19
|
+
# with the distribution.
|
20
|
+
#
|
21
|
+
# * Neither the name of Ubit nor the names of its
|
22
|
+
# contributors may be used to endorse or promote products derived
|
23
|
+
# from this software without specific prior written permission.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
27
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
28
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
29
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
30
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
31
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
32
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
33
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
34
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
35
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
36
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
37
|
+
#
|
38
|
+
# == Author
|
39
|
+
# Zev Blut, Pierre Baumard, Michael Reinsch, Paul McMahon
|
40
|
+
|
41
|
+
require 'kwala'
|
42
|
+
|
43
|
+
class UnitTestBuildAction < BuildAction
|
44
|
+
|
45
|
+
RES_REGEXP = '(.*)Finished in (\d+\.\d+) seconds.(.*)\s*(\d+) tests?, ' +
|
46
|
+
'(\d+) assertions?, (\d+) failures?, (\d+) errors?'
|
47
|
+
|
48
|
+
RUBY = '/usr/bin/env ruby'
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@test_results = nil
|
52
|
+
@results_by_version = nil
|
53
|
+
@time_start = Time.now
|
54
|
+
|
55
|
+
# The maximum number of violations to show in the summary
|
56
|
+
@time_violations_limit = 5
|
57
|
+
|
58
|
+
# time in seconds
|
59
|
+
@time_error_threshold = 5.0
|
60
|
+
@time_warning_threshold = 1.0
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_action(context)
|
64
|
+
time_start = Time.now
|
65
|
+
@test_results = run_test_cases(context.test_files,
|
66
|
+
context.pre_requires,
|
67
|
+
context.output_directory)
|
68
|
+
|
69
|
+
@filtered_entries = filter_test_results_for_summary( @test_results )
|
70
|
+
|
71
|
+
notify_test_results
|
72
|
+
@task_time = (Time.now - time_start).to_i.to_s + "s"
|
73
|
+
end
|
74
|
+
|
75
|
+
def summary_display(context)
|
76
|
+
template = TemplateFile.new(self.class.summary_template_file)
|
77
|
+
unit_base = "#{context.project_name}_unit_test.html"
|
78
|
+
|
79
|
+
context.amrita_data[:unit_test_results] = {
|
80
|
+
:unit_tests_link => Amrita::e(:a, :href => unit_base) { "Unit Test Details" },
|
81
|
+
:ruby_version => ruby_version,
|
82
|
+
:entry => @filtered_entries,
|
83
|
+
}
|
84
|
+
|
85
|
+
context.amrita_data[:process_run_time] = {
|
86
|
+
:task_time => @task_time
|
87
|
+
}
|
88
|
+
|
89
|
+
timing_base = "#{context.project_name}_timing.html"
|
90
|
+
context.amrita_data[:unit_test_timing] = {
|
91
|
+
:timing_link => Amrita::e(:a, :href => timing_base) { "Timing Details" },
|
92
|
+
:time_violations_limit => @time_violations_limit,
|
93
|
+
:error_threshold => @time_error_threshold,
|
94
|
+
:warning_threshold => @time_warning_threshold,
|
95
|
+
:timing_stats => timing_stats( @test_results ),
|
96
|
+
:time_violations => time_violations( @test_results )
|
97
|
+
}
|
98
|
+
|
99
|
+
if context.amrita_data[:unit_test_results][:entry]
|
100
|
+
context.amrita_data[:unit_test_results][:entry].each do |entry|
|
101
|
+
entry[:file_name_link] = Amrita::a(:href =>
|
102
|
+
"#{unit_base}\##{entry[:file_name]}") { entry[:file_name] }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if context.amrita_data[:unit_test_timing][:time_violations]
|
107
|
+
context.amrita_data[:unit_test_timing][:time_violations].each do |entry|
|
108
|
+
entry[:file_name_link] = Amrita::a(:href =>
|
109
|
+
"#{unit_base}\##{entry[:file_name]}") { entry[:file_name] }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
summary_expand(template, context)
|
114
|
+
end
|
115
|
+
|
116
|
+
def detailed_display(context)
|
117
|
+
context.amrita_data[:unit_test_results] = {
|
118
|
+
:entry => @test_results
|
119
|
+
}
|
120
|
+
det_res = expand_unit_test_template(context.amrita_data[:unit_test_results])
|
121
|
+
det_file = "#{context.output_directory}/#{context.project_name}_unit_test.html"
|
122
|
+
[det_file, det_res]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Should be a bit more complicated, but for now we punish any test failures.
|
126
|
+
def score
|
127
|
+
if @test_results.empty?
|
128
|
+
10
|
129
|
+
else
|
130
|
+
1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
####################
|
135
|
+
# Protected Methods
|
136
|
+
####################
|
137
|
+
protected
|
138
|
+
|
139
|
+
def notify_test_results
|
140
|
+
# only mail errors and failures
|
141
|
+
mail_entries = @filtered_entries.find_all do |e|
|
142
|
+
[:errors, :failures].find do |type|
|
143
|
+
e.key?(type) && !ignorable?(e[type])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if mail_entries.size > 0
|
148
|
+
data = { :filtered_results => mail_entries, :test_results => @test_results }
|
149
|
+
notify(:failure, data)
|
150
|
+
else
|
151
|
+
notify(:success)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def ruby_version
|
156
|
+
return @version if defined? @version
|
157
|
+
@version = `#{RUBY} -v`
|
158
|
+
end
|
159
|
+
|
160
|
+
def expand_unit_test_template(data)
|
161
|
+
template = TemplateFile.new(self.class.detailed_template_file)
|
162
|
+
data[:entry].each do |entry|
|
163
|
+
entry[:file_name_ref] = Amrita::a(:name => entry[:file_name]) { entry[:file_name] }
|
164
|
+
entry[:sys_errors] = entry[:sys_errors].empty? ? nil : entry[:sys_errors].to_a.map {|d| {:error => d} }
|
165
|
+
entry[:warnings] = entry[:warnings].empty? ? nil : entry[:warnings].to_a.map {|d| {:warning => d} }
|
166
|
+
end
|
167
|
+
ProjectBuilderUtils.expand_template(template, data)
|
168
|
+
end
|
169
|
+
|
170
|
+
def timing_stats(results)
|
171
|
+
results_by_version(results).collect do |version_name,results|
|
172
|
+
{:version_name => version_name,
|
173
|
+
:total_time => results.inject(0) { |sum,r| sum + r[:time].to_f},
|
174
|
+
:errors => results.find_all {|x| x[:time].to_f >= @time_error_threshold}.size,
|
175
|
+
:warnings => results.find_all {|x| x[:time].to_f >= @time_warning_threshold}.size,
|
176
|
+
}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def results_by_version(results)
|
181
|
+
return @results_by_version unless @results_by_version.nil?
|
182
|
+
|
183
|
+
@results_by_version = {}
|
184
|
+
results.each do |result|
|
185
|
+
result[:version].each do |version|
|
186
|
+
name = version[:version_name]
|
187
|
+
@results_by_version[name] = [] if @results_by_version[name].nil?
|
188
|
+
@results_by_version[name] << version
|
189
|
+
end
|
190
|
+
end
|
191
|
+
@results_by_version
|
192
|
+
end
|
193
|
+
|
194
|
+
def time_violations(results)
|
195
|
+
violations = []
|
196
|
+
results_by_version(results).each do |version_name,results|
|
197
|
+
version_violations = results.sort {|x,y| -(x[:time].to_f <=> y[:time].to_f)}
|
198
|
+
violations << version_violations[0, @time_violations_limit]
|
199
|
+
end
|
200
|
+
|
201
|
+
violations.flatten.collect do |x|
|
202
|
+
{:time => template_timing_error_num(x[:time]),
|
203
|
+
:file_name => x[:file_name],
|
204
|
+
:version_name => x[:version_name]}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def run_test_cases(test_files, pre_requires=nil, out_dir="./")
|
209
|
+
# Run test cases collect warnings merged for all versions
|
210
|
+
results = Array.new
|
211
|
+
test_files.each do |f|
|
212
|
+
entry = Hash.new
|
213
|
+
stdout_data, stderr_data = execute_test_file(pre_requires, f)
|
214
|
+
entry[:file_name] = f
|
215
|
+
entry[:version] = parse_test_results(stdout_data.join("\n"), f)
|
216
|
+
warnings, errors = stderr_data.partition {|r| r =~ /: warning:/}
|
217
|
+
entry[:warnings] = warnings
|
218
|
+
entry[:sys_errors] = errors
|
219
|
+
results << entry
|
220
|
+
end
|
221
|
+
results
|
222
|
+
end
|
223
|
+
|
224
|
+
def execute_test_file(pre_requires, f)
|
225
|
+
# XXX UGLY WEDGE FOR RAILS
|
226
|
+
rails_mig = `RAILS_ENV=test rake cerego:db:empty 2>&1`
|
227
|
+
rails_exitstatus = $?.exitstatus
|
228
|
+
|
229
|
+
stdout_data = stderr_data = nil
|
230
|
+
Open3.popen3(ruby_command_line(pre_requires, f)) do |stdin, stdout, stderr|
|
231
|
+
stdout_data = time_out_readlines(stdout)
|
232
|
+
stderr_data = time_out_readlines(stderr)
|
233
|
+
end
|
234
|
+
if rails_exitstatus != 0
|
235
|
+
stderr_data = stderr_data.to_s + "\n RAILS MIG FAILURE \n" + rails_mig
|
236
|
+
end
|
237
|
+
|
238
|
+
[stdout_data, stderr_data]
|
239
|
+
end
|
240
|
+
|
241
|
+
def ruby_command_line(pre_requires, file)
|
242
|
+
req_cmd = ""
|
243
|
+
if pre_requires
|
244
|
+
req_cmd = "-r #{pre_requires}"
|
245
|
+
end
|
246
|
+
"#{RUBY} #{req_cmd} #{file}"
|
247
|
+
end
|
248
|
+
|
249
|
+
# returns array of results for each version
|
250
|
+
def parse_test_results(data, file)
|
251
|
+
reg = /#{RES_REGEXP}/im
|
252
|
+
if m = reg.match(data)
|
253
|
+
results = Hash.new
|
254
|
+
results[:file_name] = file
|
255
|
+
results[:version_name] = ruby_version()
|
256
|
+
results[:time] = m[2]
|
257
|
+
results[:tests] = m[4]
|
258
|
+
results[:assertions] = m[5]
|
259
|
+
results[:failures] = template_error_num(m[6])
|
260
|
+
results[:errors] = template_error_num(m[7])
|
261
|
+
results[:test_results] = parse_out_test_info(m[3]).map {|i| {:test_info => i}}
|
262
|
+
else
|
263
|
+
results = no_match_result(data, file)
|
264
|
+
end
|
265
|
+
[results]
|
266
|
+
end
|
267
|
+
|
268
|
+
# e.g. time-out
|
269
|
+
def no_match_result(data, file)
|
270
|
+
{
|
271
|
+
:file_name => file,
|
272
|
+
:version_name => ruby_version,
|
273
|
+
:time => '',
|
274
|
+
:tests => '',
|
275
|
+
:assertions => '',
|
276
|
+
:failures => '',
|
277
|
+
:errors => a(:bgcolor, 'red') {"unexpected"},
|
278
|
+
:test_results => [{:test_info => data}]
|
279
|
+
}
|
280
|
+
end
|
281
|
+
|
282
|
+
def template_error_num(num_str)
|
283
|
+
num_str.to_i > 0 ? a(:bgcolor => "red") { num_str } : num_str
|
284
|
+
end
|
285
|
+
|
286
|
+
def template_timing_error_num(num_str)
|
287
|
+
if num_str.to_f >= @time_error_threshold
|
288
|
+
a(:bgcolor => "red") { num_str }
|
289
|
+
elsif num_str.to_f >= @time_warning_threshold
|
290
|
+
a(:bgcolor => "yellow") { num_str }
|
291
|
+
else
|
292
|
+
num_str
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def filter_test_results_for_summary(orig_results)
|
297
|
+
filtered = Array.new
|
298
|
+
orig_results.each do |orig_result|
|
299
|
+
file_errors = nil
|
300
|
+
# Let's show test cases that have warnings or errors
|
301
|
+
# in the Ruby program.
|
302
|
+
if (!orig_result[:warnings].to_s.empty?) ||
|
303
|
+
(!orig_result[:sys_errors].to_s.empty?)
|
304
|
+
filtered.concat(orig_result[:version])
|
305
|
+
next
|
306
|
+
end
|
307
|
+
orig_result[:version].each do |version|
|
308
|
+
if version.key?(:errors) and !ignorable?(version[:errors])
|
309
|
+
filtered << version
|
310
|
+
next
|
311
|
+
end
|
312
|
+
if version.key?(:failures) and !ignorable?(version[:failures])
|
313
|
+
filtered << version
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
filtered
|
318
|
+
end
|
319
|
+
|
320
|
+
def ignorable?(data)
|
321
|
+
(data.is_a?(String) && data.to_i == 0)
|
322
|
+
end
|
323
|
+
|
324
|
+
def parse_out_test_info(test_info)
|
325
|
+
groups = group_test_info(test_info)
|
326
|
+
|
327
|
+
results = Array.new
|
328
|
+
groups.each do |line|
|
329
|
+
line.scan(/(\d+)\) (Failure|Error):\s*(.*)/im) do |num, type, msg|
|
330
|
+
results << "#{num}) #{type}\n#{msg}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
results
|
334
|
+
end
|
335
|
+
|
336
|
+
def group_test_info(test_info)
|
337
|
+
in_group = false
|
338
|
+
groups = Array.new
|
339
|
+
cur_group = Array.new
|
340
|
+
|
341
|
+
test_info.split(/\n{2}/).each do |line|
|
342
|
+
if !line.empty? && !in_group
|
343
|
+
in_group = true
|
344
|
+
if !cur_group.empty?
|
345
|
+
groups << cur_group
|
346
|
+
end
|
347
|
+
cur_group = Array.new
|
348
|
+
cur_group << line
|
349
|
+
elsif line.empty?
|
350
|
+
in_group = false
|
351
|
+
if !cur_group.empty?
|
352
|
+
groups << cur_group.join("\n")
|
353
|
+
cur_group = Array.new
|
354
|
+
end
|
355
|
+
else
|
356
|
+
cur_group << line
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
if !cur_group.empty?
|
362
|
+
groups << cur_group.join("\n")
|
363
|
+
end
|
364
|
+
|
365
|
+
groups
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|