minitest_log 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|