forked from ci-reporter/ci_reporter
/
test_suite.rb
147 lines (127 loc) · 4.13 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
require 'time'
require 'builder'
require 'ci/reporter/output_capture'
require 'ci/reporter/monotonic_time'
module CI
module Reporter
module StructureXmlHelpers
# Struct#to_h is not available in Ruby 1.9
def attr_hash
Hash[self.members.zip(self.values)]
end
# Removes empty attributes and truncates long attributes.
def cleaned_attributes
attr_array = attr_hash
.reject {|k,v| v.to_s.empty? }
.map {|k,v| [k, truncate_at_newline(v)] }
Hash[attr_array]
end
def truncate_at_newline(txt)
txt.to_s.sub(/\n.*/m, '...')
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, :timestamp)
include StructureXmlHelpers
attr_accessor :testcases
attr_accessor :stdout, :stderr
def initialize(name)
super(name.to_s) # RSpec passes a "description" object instead of a string
@testcases = []
@capture_out = nil
@capture_err = nil
end
# Starts timing the test suite.
def start
@start_time = Time.now
@start = MonotonicTime.time_in_seconds
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 = MonotonicTime.time_in_seconds - @start
self.timestamp = @start_time.iso8601
self.failures = testcases.map(&:failure_count).reduce(&:+)
self.errors = testcases.map(&:error_count).reduce(&:+)
self.skipped = testcases.count(&:skipped?)
self.stdout = @capture_out.finish if @capture_out
self.stderr = @capture_err.finish if @capture_err
end
# Creates an xml string containing the test suite results.
def to_xml
builder = Builder::XmlMarkup.new(indent: 2)
builder.instruct!
builder.testsuite(cleaned_attributes) do
@testcases.each do |tc|
tc.to_xml(builder)
end
unless self.stdout.to_s.empty?
builder.tag! "system-out" do
builder.text!(self.stdout)
end
end
unless self.stderr.to_s.empty?
builder.tag! "system-err" do
builder.text!(self.stderr)
end
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)
include StructureXmlHelpers
attr_accessor :failures
attr_accessor :skipped
def initialize(*args)
super
@failures = []
end
# Starts timing the test.
def start
@start = MonotonicTime.time_in_seconds
end
# Finishes timing the test.
def finish
self.time = MonotonicTime.time_in_seconds - @start
end
# Returns non-nil if the test failed.
def failure?
failures.any?(&:failure?)
end
# Returns non-nil if the test had an error.
def error?
failures.any?(&:error?)
end
def failure_count
failures.count(&:failure?)
end
def error_count
failures.count(&:error?)
end
def skipped?
skipped
end
# Writes xml representing the test result to the provided builder.
def to_xml(builder)
builder.testcase(cleaned_attributes) do
if skipped?
builder.skipped
else
failures.each do |failure|
tag = failure.error? ? :error : :failure
builder.tag!(tag, type: truncate_at_newline(failure.name), message: truncate_at_newline(failure.message)) do
builder.text!(failure.message + " (#{failure.name})\n")
builder.text!(failure.location)
end
end
end
end
end
end
end
end