/
test_suite.rb
171 lines (151 loc) · 5.41 KB
/
test_suite.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# Copyright (c) 2006-2012 Nick Sieger <nicksieger@gmail.com>
# See the file LICENSE.txt included with the distribution for
# software license details.
require 'delegate'
require 'stringio'
module CI
module Reporter
# Emulates/delegates IO to $stdout or $stderr in order to capture output to report in the XML file.
module OutputCapture
class Delegate < DelegateClass(IO)
include OutputCapture
def initialize(io, &assign)
super(io)
capture(io, &assign)
end
end
def self.wrap(io, &assign)
if defined?(RUBY_ENGINE) # JRuby, Ruby 1.9, etc.
Delegate.new(io, &assign)
else # Ruby 1.8 requires streams to be subclass of IO
IO.new(io.fileno, "w").tap {|x| x.extend self; x.capture(io, &assign) }
end
end
# Start capturing IO, using the given block to assign self to the proper IO global.
def capture(io, &assign)
@delegate_io = io
@captured_io = StringIO.new
@assign_block = assign
@assign_block.call self
end
# Finalize the capture and reset to the original IO object.
def finish
@assign_block.call @delegate_io
@captured_io.string
end
# setup tee methods
%w(<< print printf putc puts write).each do |m|
module_eval(<<-EOS, __FILE__, __LINE__)
def #{m}(*args, &block)
@delegate_io.send(:#{m}, *args, &block)
@captured_io.send(:#{m}, *args, &block)
end
EOS
end
end
# Basic structure representing the running of a test suite. Used to time tests and store results.
class TestSuite < Struct.new(:name, :tests, :time, :failures, :errors, :skipped, :assertions)
attr_accessor :testcases
attr_accessor :stdout, :stderr
def initialize(name)
super(name.to_s) # RSpec passes a "description" object instead of a string
@testcases = []
end
# Starts timing the test suite.
def start
@start = Time.now
unless ENV['CI_CAPTURE'] == "off"
@capture_out = OutputCapture.wrap($stdout) {|io| $stdout = io }
@capture_err = OutputCapture.wrap($stderr) {|io| $stderr = io }
end
end
# Finishes timing the test suite.
def finish
self.tests = testcases.size
self.time = Time.now - @start
self.failures = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.failure? }.size }
self.errors = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.error? }.size }
self.skipped = testcases.inject(0) {|sum,tc| sum += (tc.skipped? ? 1 : 0) }
self.stdout = @capture_out.finish if @capture_out
self.stderr = @capture_err.finish if @capture_err
end
# Creates the xml builder instance used to create the report xml document.
def create_builder
require 'builder'
# :escape_attrs is obsolete in a newer version, but should do no harm
Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
end
# Creates an xml string containing the test suite results.
def to_xml
builder = create_builder
# more recent version of Builder doesn't need the escaping
def builder.trunc!(txt)
txt.sub(/\n.*/m, '...')
end
builder.instruct!
attrs = {}
each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty? }
builder.testsuite(attrs) do
@testcases.each do |tc|
tc.to_xml(builder)
end
builder.tag! "system-out" do
builder.text!(self.stdout || '' )
end
builder.tag! "system-err" do
builder.text!(self.stderr || '' )
end
end
end
end
# Structure used to represent an individual test case. Used to time the test and store the result.
class TestCase < Struct.new(:name, :time, :assertions)
attr_accessor :failures
attr_accessor :skipped
def initialize(*args)
super
@failures = []
end
# Starts timing the test.
def start
@start = Time.now
end
# Finishes timing the test.
def finish
self.time = Time.now - @start
end
# Returns non-nil if the test failed.
def failure?
!failures.empty? && failures.detect {|f| f.failure? }
end
# Returns non-nil if the test had an error.
def error?
!failures.empty? && failures.detect {|f| f.error? }
end
def skipped?
return skipped
end
# Writes xml representing the test result to the provided builder.
def to_xml(builder)
attrs = {}
each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty?}
builder.testcase(attrs) do
if skipped
builder.skipped
else
failures.each do |failure|
tag = case failure.class.name
when /TestUnitSkipped/ then :skipped
when /TestUnitError/, /MiniTestError/ then :error
else :failure end
builder.tag!(tag, :type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
builder.text!(failure.message + " (#{failure.name})\n")
builder.text!(failure.location)
end
end
end
end
end
end
end
end