kwala 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README +62 -0
  2. data/bin/kwala +39 -0
  3. data/lib/kwala.rb +59 -0
  4. data/lib/kwala/actions/code_change.rb +128 -0
  5. data/lib/kwala/actions/code_coverage.rb +303 -0
  6. data/lib/kwala/actions/code_duplication.rb +222 -0
  7. data/lib/kwala/actions/code_formatting.rb +358 -0
  8. data/lib/kwala/actions/cycle_detection.rb +118 -0
  9. data/lib/kwala/actions/cyclomatic_complexity.rb +159 -0
  10. data/lib/kwala/actions/dead_code.rb +68 -0
  11. data/lib/kwala/actions/executable_files_check.rb +111 -0
  12. data/lib/kwala/actions/flagged_comments.rb +128 -0
  13. data/lib/kwala/actions/gem_plugin.rb +31 -0
  14. data/lib/kwala/actions/loc_count.rb +153 -0
  15. data/lib/kwala/actions/multi_ruby_unit_test.rb +62 -0
  16. data/lib/kwala/actions/outside_links.rb +85 -0
  17. data/lib/kwala/actions/rails_migrate.rb +62 -0
  18. data/lib/kwala/actions/strange_requires.rb +106 -0
  19. data/lib/kwala/actions/syntax_check.rb +130 -0
  20. data/lib/kwala/actions/unit_test.rb +368 -0
  21. data/lib/kwala/build_action.rb +88 -0
  22. data/lib/kwala/build_context.rb +49 -0
  23. data/lib/kwala/command_line_runner.rb +184 -0
  24. data/lib/kwala/ext/demos.jar +0 -0
  25. data/lib/kwala/ext/pmd.jar +0 -0
  26. data/lib/kwala/ext/prefuse.jar +0 -0
  27. data/lib/kwala/extensions.rb +36 -0
  28. data/lib/kwala/lib/code_analyzer.rb +678 -0
  29. data/lib/kwala/lib/cycle_detector.rb +793 -0
  30. data/lib/kwala/lib/strange_requires_detector.rb +145 -0
  31. data/lib/kwala/notification.rb +45 -0
  32. data/lib/kwala/notifications/email.rb +54 -0
  33. data/lib/kwala/notifications/rss.rb +151 -0
  34. data/lib/kwala/project_builder_utils.rb +178 -0
  35. data/lib/kwala/templates/build_template.html +33 -0
  36. data/lib/kwala/templates/code_change_summary.html +27 -0
  37. data/lib/kwala/templates/code_coverage_detailed.html +25 -0
  38. data/lib/kwala/templates/code_coverage_summary.html +30 -0
  39. data/lib/kwala/templates/code_duplication_detailed.html +45 -0
  40. data/lib/kwala/templates/code_duplication_summary.html +9 -0
  41. data/lib/kwala/templates/code_formatting_detailed.html +27 -0
  42. data/lib/kwala/templates/code_formatting_summary.html +11 -0
  43. data/lib/kwala/templates/cycle_detection_detailed.html +20 -0
  44. data/lib/kwala/templates/cycle_detection_summary.html +7 -0
  45. data/lib/kwala/templates/cyclomatic_complexity_summary.html +27 -0
  46. data/lib/kwala/templates/executable_files_check_detailed.html +31 -0
  47. data/lib/kwala/templates/executable_files_check_summary.html +20 -0
  48. data/lib/kwala/templates/flagged_comments_detailed.html +22 -0
  49. data/lib/kwala/templates/flagged_comments_summary.html +10 -0
  50. data/lib/kwala/templates/loc_count_detailed.html +24 -0
  51. data/lib/kwala/templates/loc_count_summary.html +14 -0
  52. data/lib/kwala/templates/mdd/1.png +0 -0
  53. data/lib/kwala/templates/mdd/2.png +0 -0
  54. data/lib/kwala/templates/mdd/3.png +0 -0
  55. data/lib/kwala/templates/mdd/4.png +0 -0
  56. data/lib/kwala/templates/mdd/5.png +0 -0
  57. data/lib/kwala/templates/outside_links_summary.html +4 -0
  58. data/lib/kwala/templates/strange_requires_detailed.html +23 -0
  59. data/lib/kwala/templates/strange_requires_summary.html +13 -0
  60. data/lib/kwala/templates/style.css +95 -0
  61. data/lib/kwala/templates/syntax_check_detailed.html +21 -0
  62. data/lib/kwala/templates/syntax_check_summary.html +7 -0
  63. data/lib/kwala/templates/unit_test_detailed.html +66 -0
  64. data/lib/kwala/templates/unit_test_summary.html +70 -0
  65. data/test/tc_build_action.rb +32 -0
  66. data/test/tc_templates.rb +24 -0
  67. 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