timfel-ci_reporter 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,21 @@
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
+ module Test #:nodoc:all
9
+ module Unit
10
+ module UI
11
+ module Console
12
+ class TestRunner
13
+ def create_mediator(suite)
14
+ # swap in our custom mediator
15
+ return CI::Reporter::TestUnit.new(suite)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
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
+ filename = "#{@basename}-#{suite.name.gsub(/[^a-zA-Z0-9]+/, '-')}.xml"
18
+ filename = "#{filename}_#{Time.now}" if File.exist? filename
19
+ File.open(filename, "w") do |f|
20
+ f << suite.to_xml
21
+ end
22
+ end
23
+ end
24
+ end
25
+ 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,132 @@
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
+ TestUnitError.new(fault)
18
+ end
19
+ end
20
+
21
+ # Wrapper around a <code>Test::Unit</code> error to be used by the test suite to interpret results.
22
+ class TestUnitError
23
+ def initialize(fault) @fault = fault end
24
+ def failure?() false end
25
+ def error?() true end
26
+ def name() @fault.exception.class.name end
27
+ def message() @fault.exception.message end
28
+ def location() @fault.exception.backtrace.join("\n") end
29
+ end
30
+
31
+ # Wrapper around a <code>Test::Unit</code> failure to be used by the test suite to interpret results.
32
+ class TestUnitFailure
33
+ def initialize(fault) @fault = fault end
34
+ def failure?() true end
35
+ def error?() false end
36
+ def name() Test::Unit::AssertionFailedError.name end
37
+ def message() @fault.message end
38
+ def location() @fault.location.join("\n") end
39
+ end
40
+
41
+ # Wrapper around a <code>Test::Unit</code> 2.0 omission.
42
+ class TestUnitSkipped
43
+ def initialize(fault) @fault = fault end
44
+ def failure?() false end
45
+ def error?() false end
46
+ def name() Test::Unit::Omission.name end
47
+ def message() @fault.message end
48
+ def location() @fault.location.join("\n") end
49
+ end
50
+
51
+ # Replacement Mediator that adds listeners to capture the results of the <code>Test::Unit</code> runs.
52
+ class TestUnit < Test::Unit::UI::TestRunnerMediator
53
+ def initialize(suite, report_mgr = nil)
54
+ super(suite)
55
+ @report_manager = report_mgr || ReportManager.new("test")
56
+ add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:started))
57
+ add_listener(Test::Unit::TestCase::STARTED, &method(:test_started))
58
+ add_listener(Test::Unit::TestCase::FINISHED, &method(:test_finished))
59
+ add_listener(Test::Unit::TestResult::FAULT, &method(:fault))
60
+ add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:finished))
61
+ end
62
+
63
+ def started(result)
64
+ @suite_result = result
65
+ @last_assertion_count = 0
66
+ @current_suite = nil
67
+ @unknown_count = 0
68
+ @result_assertion_count = 0
69
+ end
70
+
71
+ def test_started(name)
72
+ test_name, suite_name = extract_names(name)
73
+ unless @current_suite && @current_suite.name == suite_name
74
+ finish_suite
75
+ start_suite(suite_name)
76
+ end
77
+ start_test(test_name)
78
+ end
79
+
80
+ def test_finished(name)
81
+ finish_test
82
+ end
83
+
84
+ def fault(fault)
85
+ tc = @current_suite.testcases.last
86
+ tc.failures << Failure.new(fault)
87
+ end
88
+
89
+ def finished(elapsed_time)
90
+ finish_suite
91
+ end
92
+
93
+ private
94
+ def extract_names(name)
95
+ match = name.match(/(.*)\(([^)]*)\)/)
96
+ if match
97
+ [match[1], match[2]]
98
+ else
99
+ @unknown_count += 1
100
+ [name, "unknown-#{@unknown_count}"]
101
+ end
102
+ end
103
+
104
+ def start_suite(suite_name)
105
+ @current_suite = TestSuite.new(suite_name)
106
+ @current_suite.start
107
+ end
108
+
109
+ def finish_suite
110
+ if @current_suite
111
+ @current_suite.finish
112
+ @current_suite.assertions = @suite_result.assertion_count - @last_assertion_count
113
+ @last_assertion_count = @suite_result.assertion_count
114
+ @report_manager.write_report(@current_suite)
115
+ end
116
+ end
117
+
118
+ def start_test(test_name)
119
+ tc = TestCase.new(test_name)
120
+ tc.start
121
+ @current_suite.testcases << tc
122
+ end
123
+
124
+ def finish_test
125
+ tc = @current_suite.testcases.last
126
+ tc.finish
127
+ tc.assertions = @suite_result.assertion_count - @result_assertion_count
128
+ @result_assertion_count = @suite_result.assertion_count
129
+ end
130
+ end
131
+ end
132
+ end