amfranz-ci_reporter 1.6.2

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,6 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ $: << File.dirname(__FILE__) + "/../../.."
6
+ require 'ci/reporter/cucumber'
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ namespace :ci do
6
+ namespace :setup do
7
+ task :spec_report_cleanup do
8
+ rm_rf ENV["CI_REPORTS"] || "spec/reports"
9
+ end
10
+
11
+ task :rspec => :spec_report_cleanup do
12
+ spec_opts = ["--require", "#{File.dirname(__FILE__)}/rspec_loader.rb",
13
+ "--format", "CI::Reporter::RSpec"].join(" ")
14
+ ENV["SPEC_OPTS"] = "#{ENV['SPEC_OPTS']} #{spec_opts}"
15
+ end
16
+
17
+ task :rspecdoc => :spec_report_cleanup do
18
+ spec_opts = ["--require", "#{File.dirname(__FILE__)}/rspec_loader.rb",
19
+ "--format", "CI::Reporter::RSpecDoc"].join(" ")
20
+ ENV["SPEC_OPTS"] = "#{ENV['SPEC_OPTS']} #{spec_opts}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ $: << File.dirname(__FILE__) + "/../../.."
6
+ require 'ci/reporter/rspec'
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ namespace :ci do
6
+ namespace :setup do
7
+ task :testunit do
8
+ rm_rf ENV["CI_REPORTS"] || "test/reports"
9
+ ENV["TESTOPTS"] = "#{ENV["TESTOPTS"]} #{File.dirname(__FILE__)}/test_unit_loader.rb"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ $: << File.dirname(__FILE__) + "/../../.."
6
+ require 'ci/reporter/test_unit'
7
+
8
+ # Intercepts mediator creation in ruby-test < 2.1
9
+ module Test #:nodoc:all
10
+ module Unit
11
+ module UI
12
+ module Console
13
+ class TestRunner
14
+ def create_mediator(suite)
15
+ # swap in our custom mediator
16
+ return CI::Reporter::TestUnit.new(suite)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Intercepts mediator creation in ruby-test >= 2.1
25
+ module Test #:nodoc:all
26
+ module Unit
27
+ module UI
28
+ class TestRunner
29
+ def setup_mediator
30
+ # swap in our custom mediator
31
+ @mediator = CI::Reporter::TestUnit.new(@suite)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2006-2010 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'fileutils'
6
+
7
+ module CI #:nodoc:
8
+ module Reporter #:nodoc:
9
+ class ReportManager
10
+ def initialize(prefix)
11
+ @basedir = ENV['CI_REPORTS'] || File.expand_path("#{Dir.getwd}/#{prefix.downcase}/reports")
12
+ @basename = "#{@basedir}/#{prefix.upcase}"
13
+ FileUtils.mkdir_p(@basedir)
14
+ end
15
+
16
+ def write_report(suite)
17
+ File.open("#{@basename}-#{suite.name.gsub(/[^a-zA-Z0-9]+/, '-')}.xml", "w") do |f|
18
+ f << suite.to_xml
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,148 @@
1
+ # Copyright (c) 2006-2010 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
+ tried_gem = false
7
+ begin
8
+ require 'spec'
9
+ require 'spec/runner/formatter/progress_bar_formatter'
10
+ require 'spec/runner/formatter/specdoc_formatter'
11
+ rescue LoadError
12
+ unless tried_gem
13
+ tried_gem = true
14
+ require 'rubygems'
15
+ gem 'rspec'
16
+ retry
17
+ end
18
+ end
19
+
20
+ module CI
21
+ module Reporter
22
+ # Wrapper around a <code>RSpec</code> error or failure to be used by the test suite to interpret results.
23
+ class RSpecFailure
24
+ def initialize(failure)
25
+ @failure = failure
26
+ end
27
+
28
+ def failure?
29
+ @failure.expectation_not_met?
30
+ end
31
+
32
+ def error?
33
+ !@failure.expectation_not_met?
34
+ end
35
+
36
+ def name() @failure.exception.class.name end
37
+ def message() @failure.exception.message end
38
+ def location() @failure.exception.backtrace.join("\n") end
39
+ end
40
+
41
+ # Custom +RSpec+ formatter used to hook into the spec runs and capture results.
42
+ class RSpec < Spec::Runner::Formatter::BaseFormatter
43
+ attr_accessor :report_manager
44
+ attr_accessor :formatter
45
+ def initialize(*args)
46
+ super
47
+ @formatter ||= Spec::Runner::Formatter::ProgressBarFormatter.new(*args)
48
+ @report_manager = ReportManager.new("spec")
49
+ @suite = nil
50
+ end
51
+
52
+ def start(spec_count)
53
+ @formatter.start(spec_count)
54
+ end
55
+
56
+ # rspec 0.9
57
+ def add_behaviour(name)
58
+ @formatter.add_behaviour(name)
59
+ new_suite(name)
60
+ end
61
+
62
+ # Compatibility with rspec < 1.2.4
63
+ def add_example_group(example_group)
64
+ @formatter.add_example_group(example_group)
65
+ new_suite(example_group.description)
66
+ end
67
+
68
+ # rspec >= 1.2.4
69
+ def example_group_started(example_group)
70
+ @formatter.example_group_started(example_group)
71
+ new_suite(example_group.description)
72
+ end
73
+
74
+ def example_started(name)
75
+ @formatter.example_started(name)
76
+ name = name.description if name.respond_to?(:description)
77
+ spec = TestCase.new name
78
+ @suite.testcases << spec
79
+ spec.start
80
+ end
81
+
82
+ def example_failed(name, counter, failure)
83
+ @formatter.example_failed(name, counter, failure)
84
+ # In case we fail in before(:all)
85
+ if @suite.testcases.empty?
86
+ example_started(name)
87
+ end
88
+ spec = @suite.testcases.last
89
+ spec.finish
90
+ spec.failures << RSpecFailure.new(failure)
91
+ end
92
+
93
+ def example_passed(name)
94
+ @formatter.example_passed(name)
95
+ spec = @suite.testcases.last
96
+ spec.finish
97
+ end
98
+
99
+ def example_pending(*args)
100
+ @formatter.example_pending(*args)
101
+ spec = @suite.testcases.last
102
+ spec.finish
103
+ spec.name = "#{spec.name} (PENDING)"
104
+ spec.skipped = true
105
+ end
106
+
107
+ def start_dump
108
+ @formatter.start_dump
109
+ end
110
+
111
+ def dump_failure(*args)
112
+ @formatter.dump_failure(*args)
113
+ end
114
+
115
+ def dump_summary(*args)
116
+ @formatter.dump_summary(*args)
117
+ write_report
118
+ end
119
+
120
+ def dump_pending
121
+ @formatter.dump_pending
122
+ end
123
+
124
+ def close
125
+ @formatter.close
126
+ end
127
+
128
+ private
129
+ def write_report
130
+ @suite.finish
131
+ @report_manager.write_report(@suite)
132
+ end
133
+
134
+ def new_suite(name)
135
+ write_report if @suite
136
+ @suite = TestSuite.new name
137
+ @suite.start
138
+ end
139
+ end
140
+
141
+ class RSpecDoc < RSpec
142
+ def initialize(*args)
143
+ @formatter = Spec::Runner::Formatter::SpecdocFormatter.new(*args)
144
+ super
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,157 @@
1
+ # Copyright (c) 2006-2010 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
+ begin
70
+ require 'rubygems'
71
+ gem 'builder'
72
+ rescue LoadError
73
+ end
74
+
75
+ require 'builder'
76
+ # :escape_attrs is obsolete in a newer version, but should do no harm
77
+ Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
78
+ end
79
+
80
+ # Creates an xml string containing the test suite results.
81
+ def to_xml
82
+ builder = create_builder
83
+ # more recent version of Builder doesn't need the escaping
84
+ def builder.trunc!(txt)
85
+ txt.sub(/\n.*/m, '...')
86
+ end
87
+ builder.instruct!
88
+ attrs = {}
89
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty? }
90
+ builder.testsuite(attrs) do
91
+ @testcases.each do |tc|
92
+ tc.to_xml(builder)
93
+ end
94
+ builder.tag! "system-out" do
95
+ builder.text! self.stdout
96
+ end
97
+ builder.tag! "system-err" do
98
+ builder.text! self.stderr
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ # Structure used to represent an individual test case. Used to time the test and store the result.
105
+ class TestCase < Struct.new(:name, :time, :assertions)
106
+ attr_accessor :failures
107
+ attr_accessor :skipped
108
+
109
+ def initialize(*args)
110
+ super
111
+ @failures = []
112
+ end
113
+
114
+ # Starts timing the test.
115
+ def start
116
+ @start = Time.now
117
+ end
118
+
119
+ # Finishes timing the test.
120
+ def finish
121
+ self.time = Time.now - @start
122
+ end
123
+
124
+ # Returns non-nil if the test failed.
125
+ def failure?
126
+ !failures.empty? && failures.detect {|f| f.failure? }
127
+ end
128
+
129
+ # Returns non-nil if the test had an error.
130
+ def error?
131
+ !failures.empty? && failures.detect {|f| f.error? }
132
+ end
133
+
134
+ def skipped?
135
+ return skipped
136
+ end
137
+
138
+ # Writes xml representing the test result to the provided builder.
139
+ def to_xml(builder)
140
+ attrs = {}
141
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty?}
142
+ builder.testcase(attrs) do
143
+ if skipped
144
+ builder.skipped
145
+ else
146
+ failures.each do |failure|
147
+ builder.failure(:type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
148
+ builder.text!(failure.message + " (#{failure.name})\n")
149
+ builder.text!(failure.location)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,143 @@
1
+ # Copyright (c) 2006-2010 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() Test::Unit::Omission.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() Test::Unit::Notification.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