stopdropandrew_ci_reporter 1.7.0.1

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