This repository has been archived by the owner on Oct 17, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
/
builder.rb
231 lines (210 loc) · 8.09 KB
/
builder.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
require 'digest'
require 'logger'
require 'mimemagic'
require 'nokogiri'
require 'uuid'
require 'socket'
module AllureRubyAdaptorApi
class Builder
class << self
attr_accessor :suites
MUTEX = Mutex.new
HOSTNAME = Socket.gethostname
LOGGER = Logger.new(STDOUT)
def start_suite(suite, labels = {:severity => :normal})
init_suites
MUTEX.synchronize do
LOGGER.debug "Starting case_or_suite #{suite} with labels #{labels}"
self.suites[suite] = {
:title => suite,
:start => timestamp,
:tests => {},
:labels => add_default_labels(labels)
}
end
end
def start_test(suite, test, labels = {:severity => :normal})
MUTEX.synchronize do
LOGGER.debug "Starting test #{suite}.#{test} with labels #{labels}"
self.suites[suite][:tests][test] = {
:title => test,
:start => timestamp,
:failure => nil,
:steps => {},
:attachments => [],
:labels => add_default_labels(labels),
}
end
end
def stop_test(suite, test, result = {})
self.suites[suite][:tests][test][:steps].each do |step_title, step|
if step[:stop].nil? || step[:stop] == 0
stop_step(suite, test, step_title, result[:status])
end
end
MUTEX.synchronize do
LOGGER.debug "Stopping test #{suite}.#{test}"
self.suites[suite][:tests][test][:stop] = timestamp(result[:finished_at])
self.suites[suite][:tests][test][:start] = timestamp(result[:started_at]) if result[:started_at]
self.suites[suite][:tests][test][:status] = result[:status]
if (result[:status].to_sym != :passed)
self.suites[suite][:tests][test][:failure] = {
:stacktrace => ((result[:exception] && result[:exception].backtrace) || []).map { |s| s.to_s }.join("\r\n"),
:message => result[:exception].to_s,
}
end
end
end
def start_step(suite, test, step, step_id = step)
MUTEX.synchronize do
LOGGER.debug "Starting step #{suite}.#{test}.#{step}.#{step_id}"
self.suites[suite][:tests][test][:steps][step_id] = {
:title => step,
:start => timestamp,
:attachments => []
}
end
end
def add_attachment(suite, test, opts = {:step_id => nil, :file => nil, :mime_type => nil})
raise "File cannot be nil!" if opts[:file].nil?
step_id = opts[:step_id]
file = opts[:file]
title = opts[:title] || File.basename(file)
LOGGER.debug "Adding attachment #{opts[:title]} to #{suite}.#{test}#{step_id.nil? ? "" : ".#{step_id}"}"
dir = Pathname.new(Dir.pwd).join(config.output_dir)
FileUtils.mkdir_p(dir)
file_extname = File.extname(file.path.downcase)
mime_type = opts[:mime_type] || MimeMagic.by_path(file.path) || "text/plain"
attachment = dir.join("#{Digest::SHA256.file(file.path).hexdigest}-attachment#{(file_extname.empty?) ? '' : file_extname}")
LOGGER.debug "Copying attachment to '#{attachment}'..."
FileUtils.cp(file.path, attachment)
attach = {
:type => mime_type,
:title => title,
:source => attachment.basename,
:file => attachment.basename,
:target => attachment.basename,
:size => File.stat(attachment).size
}
if step_id.nil?
self.suites[suite][:tests][test][:attachments] << attach
else
self.suites[suite][:tests][test][:steps][step_id][:attachments] << attach
end
end
def stop_step(suite, test, step, step_id = '', status = :passed)
step_id = step if step_id == ''
MUTEX.synchronize do
LOGGER.debug "Stopping step #{suite}.#{test}.#{step}.#{step_id}"
self.suites[suite][:tests][test][:steps][step_id][:stop] = timestamp
self.suites[suite][:tests][test][:steps][step_id][:status] = status
self.suites[suite][:tests][test][:steps][step_id][:name] = name
end
end
def stop_suite(title)
init_suites
MUTEX.synchronize do
LOGGER.debug "Stopping case_or_suite #{title}"
self.suites[title][:stop] = timestamp
end
end
def build!(opts = {}, &block)
suites_xml = []
(self.suites || []).each do |suite_title, suite|
builder = Nokogiri::XML::Builder.new do |xml|
xml.send "ns2:test-suite", :start => suite[:start] || 0, :stop => suite[:stop] || 0, 'xmlns' => '', "xmlns:ns2" => "urn:model.allure.qatools.yandex.ru" do
xml.send :name, suite_title
xml.send :title, suite_title
xml.send "test-cases" do
suite[:tests].each do |test_title, test|
xml.send "test-case", :start => test[:start] || 0, :stop => test[:stop] || 0, :status => test[:status] do
xml.send :name, test_title
xml.send :title, test_title
unless test[:failure].nil?
xml.failure do
xml.message test[:failure][:message]
xml.send "stack-trace", test[:failure][:stacktrace]
end
end
xml.steps do
test[:steps].each do |step_id, step_obj|
xml.step(:start => step_obj[:start] || 0, :stop => step_obj[:stop] || 0, :status => step_obj[:status]) do
xml.send :name, test[:steps][step_id][:title]
xml.send :title, test[:steps][step_id][:title]
xml_attachments(xml, step_obj[:attachments])
end
end
end
xml_attachments(xml, test[:attachments])
xml_labels(xml, suite[:labels].merge(test[:labels]))
xml.parameters
end
end
end
xml_labels(xml, suite[:labels])
end
end
unless suite[:tests].empty?
xml = builder.to_xml
xml = yield suite, xml if block_given?
dir = Pathname.new(config.output_dir)
FileUtils.mkdir_p(dir)
out_file = dir.join("#{UUID.new.generate}-testsuite.xml")
LOGGER.debug "Writing file '#{out_file}'..."
File.open(out_file, 'w+') do |file|
file.write(validate_xml(xml))
end
suites_xml << xml
end
end
suites_xml
end
private
def add_default_labels(labels = {})
labels[:thread] ||= Thread.current.object_id
labels[:host] ||= HOSTNAME
labels
end
def config
AllureRubyAdaptorApi::Config
end
def init_suites
MUTEX.synchronize {
self.suites ||= {}
}
LOGGER.level = config.logging_level
end
def timestamp(time = nil)
((time || Time.now).to_f * 1000).to_i
end
def validate_xml(xml)
xsd = Nokogiri::XML::Schema(File.read(Pathname.new(File.dirname(__FILE__)).join("../../allure-model-#{AllureRubyAdaptorApi::Version::ALLURE}.xsd")))
doc = Nokogiri::XML(xml)
xsd.validate(doc).each do |error|
$stderr.puts error.message
end
xml
end
def xml_attachments(xml, attachments)
xml.attachments do
attachments.each do |attach|
xml.attachment :source => attach[:source], :title => attach[:title], :size => attach[:size], :type => attach[:type]
end
end
end
def xml_labels(xml, labels)
xml.labels do
labels.each do |name, value|
if value.is_a?(Array)
value.each do |v|
xml.label :name => name, :value => v
end
else
xml.label :name => name, :value => value
end
end
end
end
end
end
end