stopdropandrew_ci_reporter 1.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,227 @@
1
+ # Copyright (c) 2006-2012 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'ci/reporter/core'
6
+
7
+ module CI
8
+ module Reporter
9
+ module RSpecFormatters
10
+ begin
11
+ require 'rspec/core/formatters/base_formatter'
12
+ require 'rspec/core/formatters/progress_formatter'
13
+ require 'rspec/core/formatters/documentation_formatter'
14
+ BaseFormatter = ::RSpec::Core::Formatters::BaseFormatter
15
+ ProgressFormatter = ::RSpec::Core::Formatters::ProgressFormatter
16
+ DocFormatter = ::RSpec::Core::Formatters::DocumentationFormatter
17
+ rescue LoadError => first_error
18
+ begin
19
+ require 'spec/runner/formatter/progress_bar_formatter'
20
+ require 'spec/runner/formatter/specdoc_formatter'
21
+ BaseFormatter = ::Spec::Runner::Formatter::BaseFormatter
22
+ ProgressFormatter = ::Spec::Runner::Formatter::ProgressBarFormatter
23
+ DocFormatter = ::Spec::Runner::Formatter::SpecdocFormatter
24
+ rescue LoadError
25
+ raise first_error
26
+ end
27
+ end
28
+ end
29
+
30
+ # Wrapper around a <code>RSpec</code> error or failure to be used by the test suite to interpret results.
31
+ class RSpecFailure
32
+ attr_reader :exception
33
+ def initialize(failure)
34
+ @failure = failure
35
+ @exception = failure.exception
36
+ end
37
+
38
+ def failure?
39
+ @failure.expectation_not_met?
40
+ end
41
+
42
+ def error?
43
+ !failure?
44
+ end
45
+
46
+ def name() @example.metadata[:execution_result][:exception].class.name end
47
+ def message() @example.metadata[:execution_result][:exception].message end
48
+ def location() @example.metadata[:execution_result][:exception].backtrace.join("\n") end
49
+ end
50
+
51
+ class RSpec2Failure < RSpecFailure
52
+ def initialize(example, formatter)
53
+ @formatter = formatter
54
+ @example = example
55
+ @exception = @example.execution_result[:exception] || @example.execution_result[:exception_encountered]
56
+ end
57
+
58
+ def name
59
+ @exception.class.name
60
+ end
61
+
62
+ def message
63
+ @exception.message
64
+ end
65
+
66
+ def failure?
67
+ exception.is_a?(::RSpec::Expectations::ExpectationNotMetError)
68
+ end
69
+
70
+ def location
71
+ output = []
72
+ output.push "#{exception.class.name << ":"}" unless exception.class.name =~ /RSpec/
73
+ output.push @exception.message
74
+
75
+ [@formatter.format_backtrace(@exception.backtrace, @example)].flatten.each do |backtrace_info|
76
+ backtrace_info.lines.each do |line|
77
+ output.push " #{line}"
78
+ end
79
+ end
80
+ output.join "\n"
81
+ end
82
+ end
83
+
84
+ # Custom +RSpec+ formatter used to hook into the spec runs and capture results.
85
+ class RSpec < RSpecFormatters::BaseFormatter
86
+ attr_accessor :report_manager
87
+ attr_accessor :formatter
88
+ def initialize(*args)
89
+ super
90
+ @formatter ||= RSpecFormatters::ProgressFormatter.new(*args)
91
+ @report_manager = ReportManager.new("spec")
92
+ @suite = nil
93
+ end
94
+
95
+ def start(spec_count)
96
+ @formatter.start(spec_count)
97
+ end
98
+
99
+ # rspec 0.9
100
+ def add_behaviour(name)
101
+ @formatter.add_behaviour(name)
102
+ new_suite(name)
103
+ end
104
+
105
+ # Compatibility with rspec < 1.2.4
106
+ def add_example_group(example_group)
107
+ @formatter.add_example_group(example_group)
108
+ new_suite(description_for(example_group))
109
+ end
110
+
111
+ # rspec >= 1.2.4
112
+ def example_group_started(example_group)
113
+ @formatter.example_group_started(example_group)
114
+ new_suite(description_for(example_group))
115
+ end
116
+
117
+ def example_group_finished(example_group)
118
+ @formatter.example_group_finished(example_group)
119
+ end
120
+
121
+ def example_started(name_or_example)
122
+ @formatter.example_started(name_or_example)
123
+ spec = TestCase.new
124
+ @suite.testcases << spec
125
+ spec.start
126
+ end
127
+
128
+ def example_failed(name_or_example, *rest)
129
+ @formatter.example_failed(name_or_example, *rest)
130
+
131
+ # In case we fail in before(:all)
132
+ example_started(name_or_example) if @suite.testcases.empty?
133
+
134
+ if name_or_example.respond_to?(:execution_result) # RSpec 2
135
+ failure = RSpec2Failure.new(name_or_example, @formatter)
136
+ else
137
+ failure = RSpecFailure.new(rest[1]) # example_failed(name, counter, failure) in RSpec 1
138
+ end
139
+
140
+ spec = @suite.testcases.last
141
+ spec.finish
142
+ spec.name = description_for(name_or_example)
143
+ spec.failures << failure
144
+ end
145
+
146
+ def example_passed(name_or_example)
147
+ @formatter.example_passed(name_or_example)
148
+ spec = @suite.testcases.last
149
+ spec.finish
150
+ spec.name = description_for(name_or_example)
151
+ end
152
+
153
+ def example_pending(*args)
154
+ @formatter.example_pending(*args)
155
+ name = description_for(args[0])
156
+ spec = @suite.testcases.last
157
+ spec.finish
158
+ spec.name = "#{name} (PENDING)"
159
+ spec.skipped = true
160
+ end
161
+
162
+ def start_dump
163
+ @formatter.start_dump
164
+ end
165
+
166
+ def dump_failures(*args)
167
+ @formatter.dump_failures(*args)
168
+ end
169
+
170
+ def dump_failure(*args)
171
+ @formatter.dump_failure(*args)
172
+ end
173
+
174
+ def dump_summary(*args)
175
+ @formatter.dump_summary(*args)
176
+ write_report
177
+ @formatter.dump_failures
178
+ end
179
+
180
+ def dump_pending
181
+ @formatter.dump_pending
182
+ end
183
+
184
+ def close
185
+ @formatter.close
186
+ end
187
+
188
+ private
189
+ def description_for(name_or_example)
190
+ if name_or_example.respond_to?(:full_description)
191
+ name_or_example.full_description
192
+ elsif name_or_example.respond_to?(:metadata)
193
+ name_or_example.metadata[:example_group][:full_description]
194
+ elsif name_or_example.respond_to?(:description)
195
+ name_or_example.description
196
+ else
197
+ "UNKNOWN"
198
+ end
199
+ end
200
+
201
+ def write_report
202
+ @suite.finish
203
+ @report_manager.write_report(@suite)
204
+ end
205
+
206
+ def new_suite(name)
207
+ write_report if @suite
208
+ @suite = TestSuite.new name
209
+ @suite.start
210
+ end
211
+ end
212
+
213
+ class RSpecDoc < RSpec
214
+ def initialize(*args)
215
+ @formatter = RSpecFormatters::DocFormatter.new(*args)
216
+ super
217
+ end
218
+ end
219
+
220
+ class RSpecBase < RSpec
221
+ def initialize(*args)
222
+ @formatter = RSpecFormatters::BaseFormatter.new(*args)
223
+ super
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,156 @@
1
+ # Copyright (c) 2006-2012 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'delegate'
6
+ require 'stringio'
7
+
8
+ module CI
9
+ module Reporter
10
+ # Emulates/delegates IO to $stdout or $stderr in order to capture output to report in the XML file.
11
+ class OutputCapture < DelegateClass(IO)
12
+ # Start capturing IO, using the given block to assign self to the proper IO global.
13
+ def initialize(io, &assign)
14
+ super
15
+ @delegate_io = io
16
+ @captured_io = StringIO.new
17
+ @assign_block = assign
18
+ @assign_block.call self
19
+ end
20
+
21
+ # Finalize the capture and reset to the original IO object.
22
+ def finish
23
+ @assign_block.call @delegate_io
24
+ @captured_io.string
25
+ end
26
+
27
+ # setup tee methods
28
+ %w(<< print printf putc puts write).each do |m|
29
+ module_eval(<<-EOS, __FILE__, __LINE__)
30
+ def #{m}(*args, &block)
31
+ @delegate_io.send(:#{m}, *args, &block)
32
+ @captured_io.send(:#{m}, *args, &block)
33
+ end
34
+ EOS
35
+ end
36
+ end
37
+
38
+ # Basic structure representing the running of a test suite. Used to time tests and store results.
39
+ class TestSuite < Struct.new(:name, :tests, :time, :failures, :errors, :skipped, :assertions)
40
+ attr_accessor :testcases
41
+ attr_accessor :stdout, :stderr
42
+ def initialize(name)
43
+ super(name.to_s) # RSpec passes a "description" object instead of a string
44
+ @testcases = []
45
+ end
46
+
47
+ # Starts timing the test suite.
48
+ def start
49
+ @start = Time.now
50
+ unless ENV['CI_CAPTURE'] == "off"
51
+ @capture_out = OutputCapture.new($stdout) {|io| $stdout = io }
52
+ @capture_err = OutputCapture.new($stderr) {|io| $stderr = io }
53
+ end
54
+ end
55
+
56
+ # Finishes timing the test suite.
57
+ def finish
58
+ self.tests = testcases.size
59
+ self.time = Time.now - @start
60
+ self.failures = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.failure? }.size }
61
+ self.errors = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.error? }.size }
62
+ self.skipped = testcases.inject(0) {|sum,tc| sum += (tc.skipped? ? 1 : 0) }
63
+ self.stdout = @capture_out.finish if @capture_out
64
+ self.stderr = @capture_err.finish if @capture_err
65
+ end
66
+
67
+ # Creates the xml builder instance used to create the report xml document.
68
+ def create_builder
69
+ require 'builder'
70
+ # :escape_attrs is obsolete in a newer version, but should do no harm
71
+ Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
72
+ end
73
+
74
+ # Creates an xml string containing the test suite results.
75
+ def to_xml
76
+ builder = create_builder
77
+ # more recent version of Builder doesn't need the escaping
78
+ def builder.trunc!(txt)
79
+ txt.sub(/\n.*/m, '...')
80
+ end
81
+ builder.instruct!
82
+ attrs = {}
83
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty? }
84
+ builder.testsuite(attrs) do
85
+ @testcases.each do |tc|
86
+ tc.to_xml(builder)
87
+ end
88
+ builder.tag! "system-out" do
89
+ builder.text!(self.stdout || '' )
90
+ end
91
+ builder.tag! "system-err" do
92
+ builder.text!(self.stderr || '' )
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # Structure used to represent an individual test case. Used to time the test and store the result.
99
+ class TestCase < Struct.new(:name, :time, :assertions)
100
+ attr_accessor :failures
101
+ attr_accessor :skipped
102
+
103
+ def initialize(*args)
104
+ super
105
+ @failures = []
106
+ end
107
+
108
+ # Starts timing the test.
109
+ def start
110
+ @start = Time.now
111
+ end
112
+
113
+ # Finishes timing the test.
114
+ def finish
115
+ self.time = Time.now - @start
116
+ end
117
+
118
+ # Returns non-nil if the test failed.
119
+ def failure?
120
+ !failures.empty? && failures.detect {|f| f.failure? }
121
+ end
122
+
123
+ # Returns non-nil if the test had an error.
124
+ def error?
125
+ !failures.empty? && failures.detect {|f| f.error? }
126
+ end
127
+
128
+ def skipped?
129
+ return skipped
130
+ end
131
+
132
+ # Writes xml representing the test result to the provided builder.
133
+ def to_xml(builder)
134
+ attrs = {}
135
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty?}
136
+ builder.testcase(attrs) do
137
+ if skipped
138
+ builder.skipped
139
+ else
140
+ failures.each do |failure|
141
+ tag = case failure.class.name
142
+ when /TestUnitSkipped/ then :skipped
143
+ when /TestUnitError/, /MiniTestError/ then :error
144
+ else :failure end
145
+
146
+ builder.tag!(tag, :type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
147
+ builder.text!(failure.message + " (#{failure.name})\n")
148
+ builder.text!(failure.location)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,143 @@
1
+ # Copyright (c) 2006-2012 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'ci/reporter/core'
6
+ require 'test/unit'
7
+ require 'test/unit/ui/console/testrunner'
8
+
9
+ module CI
10
+ module Reporter
11
+ # Factory for constructing either a CI::Reporter::TestUnitFailure or CI::Reporter::TestUnitError depending on the result
12
+ # of the test.
13
+ class Failure
14
+ def self.new(fault)
15
+ return TestUnitFailure.new(fault) if fault.kind_of?(Test::Unit::Failure)
16
+ return TestUnitSkipped.new(fault) if Test::Unit.constants.include?("Omission") && (fault.kind_of?(Test::Unit::Omission) || fault.kind_of?(Test::Unit::Pending))
17
+ return TestUnitNotification.new(fault) if Test::Unit.constants.include?("Notification") && fault.kind_of?(Test::Unit::Notification)
18
+ TestUnitError.new(fault)
19
+ end
20
+ end
21
+
22
+ # Wrapper around a <code>Test::Unit</code> error to be used by the test suite to interpret results.
23
+ class TestUnitError
24
+ def initialize(fault) @fault = fault end
25
+ def failure?() false end
26
+ def error?() true end
27
+ def name() @fault.exception.class.name end
28
+ def message() @fault.exception.message end
29
+ def location() @fault.exception.backtrace.join("\n") end
30
+ end
31
+
32
+ # Wrapper around a <code>Test::Unit</code> failure to be used by the test suite to interpret results.
33
+ class TestUnitFailure
34
+ def initialize(fault) @fault = fault end
35
+ def failure?() true end
36
+ def error?() false end
37
+ def name() Test::Unit::AssertionFailedError.name end
38
+ def message() @fault.message end
39
+ def location() @fault.location.join("\n") end
40
+ end
41
+
42
+ # Wrapper around a <code>Test::Unit</code> 2.0 omission.
43
+ class TestUnitSkipped
44
+ def initialize(fault) @fault = fault end
45
+ def failure?() false end
46
+ def error?() false end
47
+ def name() @fault.class.name end
48
+ def message() @fault.message end
49
+ def location() @fault.location.join("\n") end
50
+ end
51
+
52
+ # Wrapper around a <code>Test::Unit</code> 2.0 notification.
53
+ class TestUnitNotification
54
+ def initialize(fault) @fault = fault end
55
+ def failure?() false end
56
+ def error?() false end
57
+ def name() @fault.class.name end
58
+ def message() @fault.message end
59
+ def location() @fault.location.join("\n") end
60
+ end
61
+
62
+ # Replacement Mediator that adds listeners to capture the results of the <code>Test::Unit</code> runs.
63
+ class TestUnit < Test::Unit::UI::TestRunnerMediator
64
+ def initialize(suite, report_mgr = nil)
65
+ super(suite)
66
+ @report_manager = report_mgr || ReportManager.new("test")
67
+ add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:started))
68
+ add_listener(Test::Unit::TestCase::STARTED, &method(:test_started))
69
+ add_listener(Test::Unit::TestCase::FINISHED, &method(:test_finished))
70
+ add_listener(Test::Unit::TestResult::FAULT, &method(:fault))
71
+ add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:finished))
72
+ end
73
+
74
+ def started(result)
75
+ @suite_result = result
76
+ @last_assertion_count = 0
77
+ @current_suite = nil
78
+ @unknown_count = 0
79
+ @result_assertion_count = 0
80
+ end
81
+
82
+ def test_started(name)
83
+ test_name, suite_name = extract_names(name)
84
+ unless @current_suite && @current_suite.name == suite_name
85
+ finish_suite
86
+ start_suite(suite_name)
87
+ end
88
+ start_test(test_name)
89
+ end
90
+
91
+ def test_finished(name)
92
+ finish_test
93
+ end
94
+
95
+ def fault(fault)
96
+ tc = @current_suite.testcases.last
97
+ tc.failures << Failure.new(fault)
98
+ end
99
+
100
+ def finished(elapsed_time)
101
+ finish_suite
102
+ end
103
+
104
+ private
105
+ def extract_names(name)
106
+ match = name.match(/(.*)\(([^)]*)\)/)
107
+ if match
108
+ [match[1], match[2]]
109
+ else
110
+ @unknown_count += 1
111
+ [name, "unknown-#{@unknown_count}"]
112
+ end
113
+ end
114
+
115
+ def start_suite(suite_name)
116
+ @current_suite = TestSuite.new(suite_name)
117
+ @current_suite.start
118
+ end
119
+
120
+ def finish_suite
121
+ if @current_suite
122
+ @current_suite.finish
123
+ @current_suite.assertions = @suite_result.assertion_count - @last_assertion_count
124
+ @last_assertion_count = @suite_result.assertion_count
125
+ @report_manager.write_report(@current_suite)
126
+ end
127
+ end
128
+
129
+ def start_test(test_name)
130
+ tc = TestCase.new(test_name)
131
+ tc.start
132
+ @current_suite.testcases << tc
133
+ end
134
+
135
+ def finish_test
136
+ tc = @current_suite.testcases.last
137
+ tc.finish
138
+ tc.assertions = @suite_result.assertion_count - @result_assertion_count
139
+ @result_assertion_count = @suite_result.assertion_count
140
+ end
141
+ end
142
+ end
143
+ end