minitest_log 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +2579 -0
- data/Rakefile +10 -0
- data/lib/helpers/array_helper.rb +31 -0
- data/lib/helpers/hash_helper.rb +32 -0
- data/lib/helpers/set_helper.rb +14 -0
- data/lib/minitest_log.rb +498 -0
- data/lib/minitest_log/version.rb +3 -0
- data/lib/verdict_assertion.rb +356 -0
- data/minitest_log.gemspec +38 -0
- metadata +129 -0
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
|
3
|
+
class ArrayHelper
|
4
|
+
|
5
|
+
# Compare two arrays.
|
6
|
+
def self.compare(expected, actual)
|
7
|
+
sdiff = Diff::LCS.sdiff(expected, actual)
|
8
|
+
changes = {}
|
9
|
+
action_words = {
|
10
|
+
'!' => 'changed',
|
11
|
+
'+' => 'unexpected',
|
12
|
+
'-' => 'missing',
|
13
|
+
'=' => 'unchanged'
|
14
|
+
}
|
15
|
+
sdiff.each_with_index do |change, i|
|
16
|
+
action_word = action_words.fetch(change.action)
|
17
|
+
key = "change_#{i}"
|
18
|
+
attrs = %W/
|
19
|
+
action=#{action_word}
|
20
|
+
old_pos=#{change.old_position}
|
21
|
+
old_ele=#{change.old_element}
|
22
|
+
new_pos=#{change.old_position}
|
23
|
+
new_ele=#{change.old_element}
|
24
|
+
/
|
25
|
+
value = attrs.join(' ')
|
26
|
+
changes.store(key, value)
|
27
|
+
end
|
28
|
+
{:sdiff => changes}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class HashHelper
|
2
|
+
|
3
|
+
# Compare two hashes.
|
4
|
+
# Returns a hash with keys +:ok+, +:missing+, +:unexpected+, +:changed+.
|
5
|
+
def self.compare(expected, actual)
|
6
|
+
result = {
|
7
|
+
:missing => {},
|
8
|
+
:unexpected => {},
|
9
|
+
:changed => {},
|
10
|
+
:ok => {},
|
11
|
+
}
|
12
|
+
expected.each_pair do |key_expected, value_expected|
|
13
|
+
if actual.include?(key_expected)
|
14
|
+
value_actual = actual[key_expected]
|
15
|
+
if value_actual == value_expected
|
16
|
+
result[:ok][key_expected] = value_expected
|
17
|
+
else
|
18
|
+
result[:changed][key_expected] = {:expected => value_expected, :actual => value_actual}
|
19
|
+
end
|
20
|
+
else
|
21
|
+
result[:missing][key_expected] = value_expected
|
22
|
+
end
|
23
|
+
end
|
24
|
+
actual.each_pair do |key_actual, value_actual|
|
25
|
+
next if expected.include?(key_actual)
|
26
|
+
result[:unexpected][key_actual] = value_actual
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class SetHelper
|
2
|
+
|
3
|
+
# Compare two sets.
|
4
|
+
# Returns a hash with keys +:ok+, +:missing+, +:unexpected+.
|
5
|
+
def self.compare(expected, actual)
|
6
|
+
{
|
7
|
+
:missing => expected - actual,
|
8
|
+
:unexpected => actual - expected,
|
9
|
+
:ok => expected & actual,
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
data/lib/minitest_log.rb
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/assertions'
|
4
|
+
require 'diff/lcs'
|
5
|
+
|
6
|
+
require_relative 'verdict_assertion'
|
7
|
+
|
8
|
+
class MinitestLog
|
9
|
+
|
10
|
+
include VerdictAssertion
|
11
|
+
|
12
|
+
attr_accessor \
|
13
|
+
:assertions,
|
14
|
+
:counts,
|
15
|
+
:file,
|
16
|
+
:file_path,
|
17
|
+
:backtrace_filter,
|
18
|
+
:root_name,
|
19
|
+
:verdict_ids,
|
20
|
+
:xml_indentation,
|
21
|
+
:error_verdict,
|
22
|
+
:summary
|
23
|
+
|
24
|
+
include REXML
|
25
|
+
include Minitest::Assertions
|
26
|
+
|
27
|
+
class MinitestLogError < Exception; end
|
28
|
+
class NoBlockError < MinitestLogError; end
|
29
|
+
class DuplicateVerdictIdError < MinitestLogError; end
|
30
|
+
class IllegalElementNameError < MinitestLogError; end
|
31
|
+
class IllegalNewError < MinitestLogError; end
|
32
|
+
|
33
|
+
def initialize(file_path, options=Hash.new)
|
34
|
+
raise NoBlockError.new('No block given for MinitestLog#new.') unless (block_given?)
|
35
|
+
default_options = Hash[
|
36
|
+
:root_name => 'log',
|
37
|
+
:xml_indentation => 2,
|
38
|
+
:error_verdict => false,
|
39
|
+
:summary => false
|
40
|
+
]
|
41
|
+
options = default_options.merge(options)
|
42
|
+
self.assertions = 0
|
43
|
+
self.file_path = file_path
|
44
|
+
self.root_name = options[:root_name]
|
45
|
+
self.xml_indentation = options[:xml_indentation]
|
46
|
+
self.summary = options[:summary]
|
47
|
+
self.error_verdict = options[:error_verdict] || false
|
48
|
+
self.backtrace_filter = options[:backtrace_filter] || /minitest/
|
49
|
+
self.file = File.open(self.file_path, 'w')
|
50
|
+
log_puts("REMARK\tThis text log is the precursor for an XML log.")
|
51
|
+
log_puts("REMARK\tIf the logged process completes, this text will be converted to XML.")
|
52
|
+
log_puts("BEGIN\t#{self.root_name}")
|
53
|
+
self.counts = Hash[
|
54
|
+
:verdict => 0,
|
55
|
+
:failure => 0,
|
56
|
+
:error => 0,
|
57
|
+
]
|
58
|
+
begin
|
59
|
+
yield self
|
60
|
+
rescue => x
|
61
|
+
put_element('uncaught_exception', :timestamp, :class => x.class) do
|
62
|
+
put_element('message', x.message)
|
63
|
+
put_element('backtrace') do
|
64
|
+
backtrace = filter_backtrace(x.backtrace)
|
65
|
+
put_pre(backtrace.join("\n"))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
dispose
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def section(name, *args)
|
74
|
+
put_element('section', {:name => name}, *args) do
|
75
|
+
yield if block_given?
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def comment(text)
|
81
|
+
if text.match("\n")
|
82
|
+
# Separate text from containing punctuation.
|
83
|
+
put_element('comment') do
|
84
|
+
cdata("\n#{text}\n")
|
85
|
+
end
|
86
|
+
else
|
87
|
+
put_element('comment', text)
|
88
|
+
end
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def put_element(element_name = 'element', *args)
|
93
|
+
if false ||
|
94
|
+
caller[0].match(/minitest_log.rb/) ||
|
95
|
+
caller[0].match(/verdict_assertion.rb/)
|
96
|
+
# Make the element name special.
|
97
|
+
element_name += '_'
|
98
|
+
elsif element_name.end_with?('_')
|
99
|
+
# Don't accept user's special.
|
100
|
+
message = "Element name should not end with underscore: #{element_name}"
|
101
|
+
raise IllegalElementNameError.new(message)
|
102
|
+
else
|
103
|
+
# Ok.
|
104
|
+
end
|
105
|
+
attributes = {}
|
106
|
+
pcdata = ''
|
107
|
+
start_time = nil
|
108
|
+
duration_to_be_included = false
|
109
|
+
block_to_be_rescued = false
|
110
|
+
args.each do |arg|
|
111
|
+
case
|
112
|
+
when arg.kind_of?(Hash)
|
113
|
+
attributes.merge!(arg)
|
114
|
+
when arg.kind_of?(String)
|
115
|
+
pcdata += arg
|
116
|
+
when arg == :timestamp
|
117
|
+
attributes[:timestamp] = MinitestLog.timestamp
|
118
|
+
when arg == :duration
|
119
|
+
duration_to_be_included = true
|
120
|
+
when arg == :rescue
|
121
|
+
block_to_be_rescued = true
|
122
|
+
else
|
123
|
+
pcdata = pcdata + arg.inspect
|
124
|
+
end
|
125
|
+
end
|
126
|
+
log_puts("BEGIN\t#{element_name}")
|
127
|
+
put_attributes(attributes)
|
128
|
+
unless pcdata.empty?
|
129
|
+
# Guard against using a terminator that's a substring of pcdata.
|
130
|
+
s = 'EOT'
|
131
|
+
terminator = s
|
132
|
+
while pcdata.match(terminator) do
|
133
|
+
terminator += s
|
134
|
+
end
|
135
|
+
log_puts("PCDATA\t<<#{terminator}")
|
136
|
+
log_puts(pcdata)
|
137
|
+
log_puts(terminator)
|
138
|
+
end
|
139
|
+
start_time = Time.new if duration_to_be_included
|
140
|
+
if block_given?
|
141
|
+
if block_to_be_rescued
|
142
|
+
begin
|
143
|
+
yield
|
144
|
+
rescue Exception => x
|
145
|
+
put_element('rescued_exception', {:class => x.class, :message => x.message}) do
|
146
|
+
put_element('backtrace') do
|
147
|
+
backtrace = filter_backtrace(x.backtrace)
|
148
|
+
put_pre(backtrace.join("\n"))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
self.counts[:error] += 1
|
152
|
+
end
|
153
|
+
else
|
154
|
+
yield
|
155
|
+
end
|
156
|
+
end
|
157
|
+
if start_time
|
158
|
+
end_time = Time.now
|
159
|
+
duration_f = end_time.to_f - start_time.to_f
|
160
|
+
duration_s = format('%.3f', duration_f)
|
161
|
+
put_attributes({:duration_seconds => duration_s})
|
162
|
+
end
|
163
|
+
log_puts("END\t#{element_name}")
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def put_each_with_index(name, obj)
|
168
|
+
lines = ['']
|
169
|
+
obj.each_with_index do |item, i|
|
170
|
+
lines.push(format('%d: %s', i, item.to_s))
|
171
|
+
end
|
172
|
+
attrs = {
|
173
|
+
:name => name,
|
174
|
+
:class => obj.class,
|
175
|
+
:method => ':each_with_index',
|
176
|
+
}
|
177
|
+
add_attr_if(attrs, obj, :size)
|
178
|
+
put_element('data', attrs) do
|
179
|
+
put_pre(lines.join("\n"))
|
180
|
+
end
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
alias put_array put_each_with_index
|
184
|
+
alias put_set put_each_with_index
|
185
|
+
|
186
|
+
def put_each(name, obj)
|
187
|
+
lines = ['']
|
188
|
+
obj.each do |item|
|
189
|
+
lines.push(item)
|
190
|
+
end
|
191
|
+
attrs = {
|
192
|
+
:name => name,
|
193
|
+
:class => obj.class,
|
194
|
+
:method => ':each',
|
195
|
+
}
|
196
|
+
add_attr_if(attrs, obj, :size)
|
197
|
+
put_element('data', attrs) do
|
198
|
+
put_pre(lines.join("\n"))
|
199
|
+
end
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def put_each_pair(name, obj)
|
204
|
+
lines = ['']
|
205
|
+
obj.each_pair do |key, value|
|
206
|
+
lines.push(format('%s => %s', key, value))
|
207
|
+
end
|
208
|
+
attrs = {
|
209
|
+
:name => name,
|
210
|
+
:class => obj.class,
|
211
|
+
:method => ':each_pair',
|
212
|
+
}
|
213
|
+
add_attr_if(attrs, obj, :size)
|
214
|
+
put_element('data', attrs) do
|
215
|
+
put_pre(lines.join("\n"))
|
216
|
+
end
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
alias put_hash put_each_pair
|
220
|
+
|
221
|
+
def put_to_s(name, obj)
|
222
|
+
put_element('data', obj.to_s, :name => name, :class => obj.class, :method => ':to_s')
|
223
|
+
end
|
224
|
+
|
225
|
+
def put_string(name, obj)
|
226
|
+
put_element('data', obj.to_s, :name => name, :class => obj.class, :size => obj.size)
|
227
|
+
end
|
228
|
+
|
229
|
+
def put_inspect(name, obj)
|
230
|
+
put_element('data', obj.inspect, :name => name, :class => obj.class, :method => ':inspect')
|
231
|
+
end
|
232
|
+
|
233
|
+
def put_id(name, obj)
|
234
|
+
put_element('data', :name => name, :class => obj.class, :id => obj.__id__)
|
235
|
+
end
|
236
|
+
|
237
|
+
def put_data(name, obj)
|
238
|
+
case
|
239
|
+
when obj.kind_of?(String)
|
240
|
+
put_string(name, obj)
|
241
|
+
when obj.respond_to?(:each_pair)
|
242
|
+
put_each_pair(name, obj)
|
243
|
+
when obj.respond_to?(:each_with_index)
|
244
|
+
put_each_with_index(name, obj)
|
245
|
+
when obj.respond_to?(:each)
|
246
|
+
put_each(name, obj)
|
247
|
+
when obj.respond_to?(:to_s)
|
248
|
+
put_to_s(name, obj)
|
249
|
+
when obj.respond_to?(:inspect)
|
250
|
+
put_inspect(name, obj)
|
251
|
+
when obj.respond_to?(:__id__)
|
252
|
+
put_id(name, obj)
|
253
|
+
else
|
254
|
+
message = "Object does not respond to method :__id__: name=#{name}, obj=#{obj}"
|
255
|
+
raise ArgumentError.new(message)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def put_pre(text, verbatim = false)
|
260
|
+
if verbatim
|
261
|
+
put_cdata(text)
|
262
|
+
else
|
263
|
+
t = text.clone
|
264
|
+
until t.start_with?("\n")
|
265
|
+
t = "\n" + t
|
266
|
+
end
|
267
|
+
until t.end_with?("\n\n")
|
268
|
+
t = t + "\n"
|
269
|
+
end
|
270
|
+
put_cdata(t)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def dispose
|
277
|
+
|
278
|
+
# Add a verdict for the error count, if needed.
|
279
|
+
if self.error_verdict
|
280
|
+
verdict_assert_equal?('error_count', 0, self.counts[:error])
|
281
|
+
end
|
282
|
+
|
283
|
+
# Close the text log.
|
284
|
+
log_puts("END\t#{self.root_name}")
|
285
|
+
self.file.close
|
286
|
+
|
287
|
+
# Create the xml log.
|
288
|
+
document = REXML::Document.new
|
289
|
+
File.open(self.file_path, 'r') do |file|
|
290
|
+
element = document
|
291
|
+
stack = Array.new
|
292
|
+
data_a = Array.new
|
293
|
+
terminator = nil
|
294
|
+
file.each_line do |line|
|
295
|
+
line.chomp!
|
296
|
+
line_type, text = line.split("\t", 2)
|
297
|
+
case line_type
|
298
|
+
when 'REMARK'
|
299
|
+
next
|
300
|
+
when 'BEGIN'
|
301
|
+
element_name = text
|
302
|
+
element = element.add_element(element_name)
|
303
|
+
stack.push(element)
|
304
|
+
if stack.length == 1 && self.summary
|
305
|
+
summary_element = element.add_element('summary_')
|
306
|
+
summary_element.add_attribute('verdicts', self.counts[:verdict].to_s)
|
307
|
+
summary_element.add_attribute('failures', self.counts[:failure].to_s)
|
308
|
+
summary_element.add_attribute('errors', self.counts[:error].to_s)
|
309
|
+
end
|
310
|
+
when 'END'
|
311
|
+
stack.pop
|
312
|
+
element = stack.last
|
313
|
+
when 'ATTRIBUTE'
|
314
|
+
attr_name, attr_value = text.split("\t", 2)
|
315
|
+
element.add_attribute(attr_name, attr_value)
|
316
|
+
when 'CDATA'
|
317
|
+
stack.push(:cdata)
|
318
|
+
data_a = Array.new
|
319
|
+
terminator = text.split('<<', 2).last
|
320
|
+
when 'PCDATA'
|
321
|
+
stack.push(:pcdata)
|
322
|
+
data_a = Array.new
|
323
|
+
terminator = text.split('<<', 2).last
|
324
|
+
when terminator
|
325
|
+
data_s = data_a.join("\n")
|
326
|
+
data_a = Array.new
|
327
|
+
terminator = nil
|
328
|
+
data_type = stack.last
|
329
|
+
case data_type
|
330
|
+
when :cdata
|
331
|
+
cdata = CData.new(data_s)
|
332
|
+
element.add(cdata)
|
333
|
+
when :pcdata
|
334
|
+
element.add_text(data_s)
|
335
|
+
else
|
336
|
+
# Don't want to raise an exception and spoil the run
|
337
|
+
end
|
338
|
+
stack.pop
|
339
|
+
else
|
340
|
+
data_a.push(line) if (terminator)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
document << XMLDecl.default
|
344
|
+
end
|
345
|
+
|
346
|
+
File.open(self.file_path, 'w') do |file|
|
347
|
+
document.write(file, self.xml_indentation)
|
348
|
+
end
|
349
|
+
# Trailing newline.
|
350
|
+
File.open(self.file_path, 'a') do |file|
|
351
|
+
file.write("\n")
|
352
|
+
end
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
|
356
|
+
def _get_verdict?(verdict_method, verdict_id, message, args_hash)
|
357
|
+
assertion_method = assertion_method_for(verdict_method)
|
358
|
+
if block_given?
|
359
|
+
outcome, exception = get_assertion_outcome(verdict_id, assertion_method, *args_hash.values) do
|
360
|
+
yield
|
361
|
+
end
|
362
|
+
else
|
363
|
+
outcome, exception = get_assertion_outcome(verdict_id, assertion_method, *args_hash.values)
|
364
|
+
end
|
365
|
+
element_attributes = {
|
366
|
+
:method => verdict_method,
|
367
|
+
:outcome => outcome,
|
368
|
+
:id => verdict_id,
|
369
|
+
}
|
370
|
+
element_attributes.store(:message, message) unless message.nil?
|
371
|
+
put_element('verdict', element_attributes) do
|
372
|
+
args_hash.each_pair do |k, v|
|
373
|
+
put_element(k.to_s, {:class => v.class, :value => v.inspect})
|
374
|
+
end
|
375
|
+
if exception
|
376
|
+
self.counts[:failure] += 1
|
377
|
+
# If the encoding is not UTF-8, a string will have been added.
|
378
|
+
# Remove it, so that the message is the same on all platforms.
|
379
|
+
conditioned_message = exception.message.gsub("# encoding: UTF-8\n", '')
|
380
|
+
put_element('exception', {:class => exception.class, :message => conditioned_message}) do
|
381
|
+
put_element('backtrace') do
|
382
|
+
backtrace = filter_backtrace(exception.backtrace)
|
383
|
+
put_pre(backtrace.join("\n"))
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
outcome == :passed
|
389
|
+
end
|
390
|
+
|
391
|
+
def put_attributes(attributes)
|
392
|
+
attributes.each_pair do |name, value|
|
393
|
+
value = case
|
394
|
+
when value.is_a?(String)
|
395
|
+
value
|
396
|
+
when value.is_a?(Symbol)
|
397
|
+
value.to_s
|
398
|
+
else
|
399
|
+
value.inspect
|
400
|
+
end
|
401
|
+
log_puts("ATTRIBUTE\t#{name}\t#{value}")
|
402
|
+
end
|
403
|
+
nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def log_puts(text)
|
407
|
+
self.file.puts(text)
|
408
|
+
self.file.flush
|
409
|
+
nil
|
410
|
+
end
|
411
|
+
|
412
|
+
def validate_verdict_id(verdict_id)
|
413
|
+
self.verdict_ids ||= Set.new
|
414
|
+
if self.verdict_ids.include?(verdict_id)
|
415
|
+
message = format('Duplicate verdict id %s; must be unique within its test method', verdict_id.inspect)
|
416
|
+
raise DuplicateVerdictIdError.new(message)
|
417
|
+
end
|
418
|
+
self.verdict_ids.add(verdict_id)
|
419
|
+
nil
|
420
|
+
end
|
421
|
+
|
422
|
+
def put_cdata(text)
|
423
|
+
# Guard against using a terminator that's a substring of the cdata.
|
424
|
+
s = 'EOT'
|
425
|
+
terminator = s
|
426
|
+
while text.match(terminator) do
|
427
|
+
terminator += s
|
428
|
+
end
|
429
|
+
log_puts("CDATA\t<<#{terminator}")
|
430
|
+
log_puts(text)
|
431
|
+
log_puts(terminator)
|
432
|
+
nil
|
433
|
+
end
|
434
|
+
|
435
|
+
def get_assertion_outcome(verdict_id, assertion_method, *assertion_args)
|
436
|
+
validate_verdict_id(verdict_id)
|
437
|
+
self.counts[:verdict] += 1
|
438
|
+
begin
|
439
|
+
if block_given?
|
440
|
+
send(assertion_method, *assertion_args) do
|
441
|
+
yield
|
442
|
+
end
|
443
|
+
else
|
444
|
+
send(assertion_method, *assertion_args)
|
445
|
+
end
|
446
|
+
return :passed, nil
|
447
|
+
rescue Minitest::Assertion => x
|
448
|
+
return :failed, x
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Filters lines that are from ruby or log, to make the backtrace more readable.
|
453
|
+
def filter_backtrace(lines)
|
454
|
+
filtered = []
|
455
|
+
lines.each do |line|
|
456
|
+
unless line.match(self.backtrace_filter)
|
457
|
+
filtered.push(line)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
filtered
|
461
|
+
end
|
462
|
+
|
463
|
+
# Return a timestamp string.
|
464
|
+
# The important property of this string
|
465
|
+
# is that it can be incorporated into a legal directory path
|
466
|
+
# (i.e., has no colons, etc.).
|
467
|
+
def self.timestamp
|
468
|
+
now = Time.now
|
469
|
+
ts = now.strftime('%Y-%m-%d-%a-%H.%M.%S')
|
470
|
+
usec_s = (now.usec / 1000).to_s
|
471
|
+
while usec_s.length < 3 do
|
472
|
+
usec_s = '0' + usec_s
|
473
|
+
end
|
474
|
+
# noinspection RubyUnusedLocalVariable
|
475
|
+
ts += ".#{usec_s}"
|
476
|
+
end
|
477
|
+
|
478
|
+
def assertion_method_for(verdict_method)
|
479
|
+
# Our verdict method name is just an assertion method name
|
480
|
+
# with prefixed 'verdict_' and suffixed '?'.
|
481
|
+
# Just remove them to form the assertion method name.
|
482
|
+
verdict_method.to_s.sub('verdict_', '').sub('?', '').to_sym
|
483
|
+
end
|
484
|
+
|
485
|
+
def add_attr_if(attrs, obj, method)
|
486
|
+
return unless obj.respond_to?(method)
|
487
|
+
attrs[method] = obj.send(method)
|
488
|
+
end
|
489
|
+
|
490
|
+
def self.parse(file_path)
|
491
|
+
document = nil
|
492
|
+
File.open(file_path) do |file|
|
493
|
+
document = REXML::Document.new(file)
|
494
|
+
end
|
495
|
+
document
|
496
|
+
end
|
497
|
+
|
498
|
+
end
|