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.
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