brasten-ci_reporter 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
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 File.expand_path('../utils', __FILE__)
6
+
7
+ namespace :ci do
8
+ namespace :setup do
9
+ task :cucumber_report_cleanup do
10
+ rm_rf ENV["CI_REPORTS"] || "features/reports"
11
+ end
12
+
13
+ task :cucumber => :cucumber_report_cleanup do
14
+ cuke_opts = ["--require", CI::Reporter.maybe_quote_filename("#{File.dirname(__FILE__)}/cucumber_loader.rb"),
15
+ "--format", "CI::Reporter::Cucumber"].join(" ")
16
+ ENV["CUCUMBER_OPTS"] = "#{ENV['CUCUMBER_OPTS']} #{cuke_opts}"
17
+ end
18
+ end
19
+ 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/cucumber'
@@ -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 File.expand_path('../utils', __FILE__)
6
+
7
+ namespace :ci do
8
+ namespace :setup do
9
+ task :spec_report_cleanup do
10
+ rm_rf ENV["CI_REPORTS"] || "spec/reports"
11
+ end
12
+
13
+ task :rspec => :spec_report_cleanup do
14
+ spec_opts = ["--require", CI::Reporter.maybe_quote_filename("#{File.dirname(__FILE__)}/rspec_loader.rb"),
15
+ "--format", "CI::Reporter::RSpec"].join(" ")
16
+ ENV["SPEC_OPTS"] = "#{ENV['SPEC_OPTS']} #{spec_opts}"
17
+ end
18
+
19
+ task :rspecdoc => :spec_report_cleanup do
20
+ spec_opts = ["--require", CI::Reporter.maybe_quote_filename("#{File.dirname(__FILE__)}/rspec_loader.rb"),
21
+ "--format", "CI::Reporter::RSpecDoc"].join(" ")
22
+ ENV["SPEC_OPTS"] = "#{ENV['SPEC_OPTS']} #{spec_opts}"
23
+ end
24
+ end
25
+ 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,15 @@
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 File.expand_path('../utils', __FILE__)
6
+
7
+ namespace :ci do
8
+ namespace :setup do
9
+ task :testunit do
10
+ rm_rf ENV["CI_REPORTS"] || "test/reports"
11
+ test_loader = CI::Reporter.maybe_quote_filename "#{File.dirname(__FILE__)}/test_unit_loader.rb"
12
+ ENV["TESTOPTS"] = "#{ENV["TESTOPTS"]} #{test_loader}"
13
+ end
14
+ end
15
+ 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,14 @@
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
+ module CI
6
+ module Reporter
7
+ def self.maybe_quote_filename(fn)
8
+ if fn =~ /\s/
9
+ fn = %{"#{fn}"}
10
+ end
11
+ fn
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,61 @@
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 = "#{prefix.upcase}"
13
+ FileUtils.mkdir_p(@basedir)
14
+ end
15
+
16
+ def write_report(suite)
17
+ File.open(filename_for(suite), "w") do |f|
18
+ f << suite.to_xml
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+
25
+ # creates a uniqe filename per suite
26
+ # to prevent results from being overwritten
27
+ # if a result file is already written, it appends an index
28
+ # e.g.
29
+ # SPEC-MailsController.xml
30
+ # SPEC-MailsController.0.xml
31
+ # SPEC-MailsController.1.xml
32
+ # SPEC-MailsController...xml
33
+ # SPEC-MailsController.N.xml
34
+ #
35
+ # with N < 100000, to prevent endless sidestep loops
36
+ MAX_SIDESTEPS = 100000
37
+ #
38
+ def filename_for(suite)
39
+ basename = "#{@basename}-#{suite.name.gsub(/[^a-zA-Z0-9]+/, '-')}"
40
+ suffix = "xml"
41
+
42
+ # the initial filename, e.g. SPEC-MailsController.xml
43
+ filename = [basename, suffix].join(".")
44
+
45
+ # if the initial filename is already in use
46
+ # do sidesteps, beginning with SPEC-MailsController.0.xml
47
+ i = 0
48
+ while File.exists?(filename) && i < MAX_SIDESTEPS
49
+ filename = [basename, i, suffix].join(".")
50
+ i += 1
51
+ end
52
+
53
+ if filename.size > 251
54
+ filename = "#{filename[0..209]}-#{Digest::SHA1.hexdigest(filename[209..-1])}.#{suffix}"
55
+ end
56
+
57
+ "#{@basedir}/#{filename}"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,189 @@
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
+
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() exception.class.name end
47
+ def message() exception.message end
48
+ def location() (exception.backtrace || ["No backtrace available"]).join("\n") end
49
+ end
50
+
51
+ class RSpec2Failure < RSpecFailure
52
+ def initialize(example)
53
+ @example = example
54
+ @exception = @example.execution_result[:exception] || @example.execution_result[:exception_encountered]
55
+ end
56
+
57
+ def failure?
58
+ exception.is_a?(::RSpec::Expectations::ExpectationNotMetError)
59
+ end
60
+ end
61
+
62
+ # Custom +RSpec+ formatter used to hook into the spec runs and capture results.
63
+ class RSpec < RSpecFormatters::BaseFormatter
64
+ attr_accessor :report_manager
65
+ attr_accessor :formatter
66
+ def initialize(*args)
67
+ super
68
+ @formatter ||= RSpecFormatters::ProgressFormatter.new(*args)
69
+ @report_manager = ReportManager.new("spec")
70
+ @suite = nil
71
+ end
72
+
73
+ def start(spec_count)
74
+ @formatter.start(spec_count)
75
+ end
76
+
77
+ # rspec 0.9
78
+ def add_behaviour(name)
79
+ @formatter.add_behaviour(name)
80
+ new_suite(name)
81
+ end
82
+
83
+ # Compatibility with rspec < 1.2.4
84
+ def add_example_group(example_group)
85
+ @formatter.add_example_group(example_group)
86
+ new_suite(description_for(example_group))
87
+ end
88
+
89
+ # rspec >= 1.2.4
90
+ def example_group_started(example_group)
91
+ @formatter.example_group_started(example_group)
92
+ new_suite(description_for(example_group))
93
+ end
94
+
95
+ def example_started(name_or_example)
96
+ @formatter.example_started(name_or_example)
97
+ spec = TestCase.new
98
+ @suite.testcases << spec
99
+ spec.start
100
+ end
101
+
102
+ def example_failed(name_or_example, *rest)
103
+ @formatter.example_failed(name_or_example, *rest)
104
+
105
+ # In case we fail in before(:all)
106
+ example_started(name_or_example) if @suite.testcases.empty?
107
+
108
+ if name_or_example.respond_to?(:execution_result) # RSpec 2
109
+ failure = RSpec2Failure.new(name_or_example)
110
+ else
111
+ failure = RSpecFailure.new(rest[1]) # example_failed(name, counter, failure) in RSpec 1
112
+ end
113
+
114
+ spec = @suite.testcases.last
115
+ spec.finish
116
+ spec.name = description_for(name_or_example)
117
+ spec.failures << failure
118
+ end
119
+
120
+ def example_passed(name_or_example)
121
+ @formatter.example_passed(name_or_example)
122
+ spec = @suite.testcases.last
123
+ spec.finish
124
+ spec.name = description_for(name_or_example)
125
+ end
126
+
127
+ def example_pending(*args)
128
+ @formatter.example_pending(*args)
129
+ name = description_for(args[0])
130
+ spec = @suite.testcases.last
131
+ spec.finish
132
+ spec.name = "#{name} (PENDING)"
133
+ spec.skipped = true
134
+ end
135
+
136
+ def start_dump
137
+ @formatter.start_dump
138
+ end
139
+
140
+ def dump_failure(*args)
141
+ @formatter.dump_failure(*args)
142
+ end
143
+
144
+ def dump_summary(*args)
145
+ @formatter.dump_summary(*args)
146
+ write_report
147
+ end
148
+
149
+ def dump_pending
150
+ @formatter.dump_pending
151
+ end
152
+
153
+ def close
154
+ @formatter.close
155
+ end
156
+
157
+ private
158
+ def description_for(name_or_example)
159
+ if name_or_example.respond_to?(:full_description)
160
+ name_or_example.full_description
161
+ elsif name_or_example.respond_to?(:metadata)
162
+ name_or_example.metadata[:example_group][:full_description]
163
+ elsif name_or_example.respond_to?(:description)
164
+ name_or_example.description
165
+ else
166
+ "UNKNOWN"
167
+ end
168
+ end
169
+
170
+ def write_report
171
+ @suite.finish
172
+ @report_manager.write_report(@suite)
173
+ end
174
+
175
+ def new_suite(name)
176
+ write_report if @suite
177
+ @suite = TestSuite.new name
178
+ @suite.start
179
+ end
180
+ end
181
+
182
+ class RSpecDoc < RSpec
183
+ def initialize(*args)
184
+ @formatter = RSpecFormatters::DocFormatter.new(*args)
185
+ super
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,151 @@
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
+ 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
+ builder.failure(:type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
142
+ builder.text!(failure.message + " (#{failure.name})\n")
143
+ builder.text!(failure.location)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end