detest 3.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.
- data/CREDITS +21 -0
- data/LICENSE +15 -0
- data/bin/detest +25 -0
- data/lib/detest.rb +1211 -0
- data/lib/detest/auto.rb +17 -0
- data/lib/detest/inochi.rb +93 -0
- data/lib/detest/long.rb +25 -0
- data/lib/detest/mini.rb +92 -0
- data/lib/detest/spec.rb +31 -0
- data/lib/detest/unit.rb +101 -0
- data/man.html +1298 -0
- data/man/man1/detest.1.gz +0 -0
- metadata +94 -0
data/CREDITS
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
%#----------------------------------------------------------------------------
|
2
|
+
## AUTHORS
|
3
|
+
%#----------------------------------------------------------------------------
|
4
|
+
|
5
|
+
Suraj N. Kurapati
|
6
|
+
|
7
|
+
%#----------------------------------------------------------------------------
|
8
|
+
## CREDITS
|
9
|
+
%#----------------------------------------------------------------------------
|
10
|
+
|
11
|
+
François Beausoleil,
|
12
|
+
Gavin Sinclair,
|
13
|
+
Iñaki Baz Castillo,
|
14
|
+
Sean O'Halpin
|
15
|
+
|
16
|
+
%#----------------------------------------------------------------------------
|
17
|
+
## LICENSE
|
18
|
+
%#----------------------------------------------------------------------------
|
19
|
+
|
20
|
+
%# See the file named "LICENSE".
|
21
|
+
%< "LICENSE"
|
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
(the ISC license)
|
2
|
+
|
3
|
+
Copyright 2009 Suraj N. Kurapati <sunaku@gmail.com>
|
4
|
+
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/bin/detest
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'detest'
|
4
|
+
|
5
|
+
if ARGV.delete('-h') or ARGV.delete('--help')
|
6
|
+
# try to display UNIX version of help manual
|
7
|
+
man_path = File.join(Detest::INSTDIR, 'man')
|
8
|
+
unless system 'man', '-M', man_path, '-a', 'detest'
|
9
|
+
# try to display HTML version of help manual
|
10
|
+
man_html = man_path + '.html'
|
11
|
+
unless %w[$BROWSER open start].any? {|b| system "#{b} #{man_html}" }
|
12
|
+
# no luck; direct user to project website
|
13
|
+
puts "See #{Detest::WEBSITE}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
exit
|
17
|
+
elsif ARGV.delete('-v') or ARGV.delete('--version')
|
18
|
+
puts Detest::VERSION
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
Detest.debug = ARGV.delete('-d') || ARGV.delete('--debug')
|
23
|
+
|
24
|
+
require 'detest/auto'
|
25
|
+
ARGV.each {|glob| Dir[glob].each {|test| load test } }
|
data/lib/detest.rb
ADDED
@@ -0,0 +1,1211 @@
|
|
1
|
+
require 'detest/inochi'
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
#
|
5
|
+
# YAML raises this error when we try to serialize a class:
|
6
|
+
#
|
7
|
+
# TypeError: can't dump anonymous class Class
|
8
|
+
#
|
9
|
+
# Work around this by representing a class by its name.
|
10
|
+
#
|
11
|
+
class Class # @private
|
12
|
+
alias __to_yaml__ to_yaml
|
13
|
+
def to_yaml opts = {}
|
14
|
+
begin
|
15
|
+
__to_yaml__ opts
|
16
|
+
rescue TypeError
|
17
|
+
inspect.to_yaml opts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Detest
|
23
|
+
class << self
|
24
|
+
##
|
25
|
+
# Launch an interactive debugger
|
26
|
+
# during assertion failures so
|
27
|
+
# the user can investigate them?
|
28
|
+
#
|
29
|
+
# The default value is $DEBUG.
|
30
|
+
#
|
31
|
+
attr_accessor :debug
|
32
|
+
|
33
|
+
##
|
34
|
+
# Hash of counts of major events in test execution:
|
35
|
+
#
|
36
|
+
# [:time]
|
37
|
+
# Number of seconds elapsed for test execution.
|
38
|
+
#
|
39
|
+
# [:pass]
|
40
|
+
# Number of assertions that held true.
|
41
|
+
#
|
42
|
+
# [:fail]
|
43
|
+
# Number of assertions that did not hold true.
|
44
|
+
#
|
45
|
+
# [:error]
|
46
|
+
# Number of exceptions that were not rescued.
|
47
|
+
#
|
48
|
+
attr_reader :stats
|
49
|
+
|
50
|
+
##
|
51
|
+
# Hierarchical trace of all tests executed, where each test is
|
52
|
+
# represented by its description, is mapped to an Array of
|
53
|
+
# nested tests, and may contain zero or more assertion failures.
|
54
|
+
#
|
55
|
+
# Assertion failures are represented as a Hash:
|
56
|
+
#
|
57
|
+
# [:fail]
|
58
|
+
# Description of the assertion failure.
|
59
|
+
#
|
60
|
+
# [:call]
|
61
|
+
# Stack trace leading to the point of failure.
|
62
|
+
#
|
63
|
+
# [:code]
|
64
|
+
# Source code surrounding the point of failure.
|
65
|
+
#
|
66
|
+
# [:bind]
|
67
|
+
# Location where local variables in `:vars` were extracted.
|
68
|
+
#
|
69
|
+
# [:vars]
|
70
|
+
# Local variables visible at the point of failure.
|
71
|
+
#
|
72
|
+
attr_reader :trace
|
73
|
+
|
74
|
+
##
|
75
|
+
# Defines a new test composed of the given
|
76
|
+
# description and the given block to execute.
|
77
|
+
#
|
78
|
+
# This test may contain nested tests.
|
79
|
+
#
|
80
|
+
# Tests at the outer-most level are automatically
|
81
|
+
# insulated from the top-level Ruby environment.
|
82
|
+
#
|
83
|
+
# @param [Object, Array<Object>] description
|
84
|
+
#
|
85
|
+
# A brief title or a series of objects
|
86
|
+
# that describe the test being defined.
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
#
|
90
|
+
# D "a new array" do
|
91
|
+
# D .< { @array = [] }
|
92
|
+
#
|
93
|
+
# D "must be empty" do
|
94
|
+
# T { @array.empty? }
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# D "when populated" do
|
98
|
+
# D .< { @array.push 55 }
|
99
|
+
#
|
100
|
+
# D "must not be empty" do
|
101
|
+
# F { @array.empty? }
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
def D *description, &block
|
107
|
+
create_test @tests.empty?, *description, &block
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Defines a new test that is explicitly insulated from the tests
|
112
|
+
# that contain it and also from the top-level Ruby environment.
|
113
|
+
#
|
114
|
+
# This test may contain nested tests.
|
115
|
+
#
|
116
|
+
# @param description (see Detest.D)
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
#
|
120
|
+
# D "a root-level test" do
|
121
|
+
# @outside = 1
|
122
|
+
# T { defined? @outside }
|
123
|
+
# T { @outside == 1 }
|
124
|
+
#
|
125
|
+
# D "an inner, non-insulated test" do
|
126
|
+
# T { defined? @outside }
|
127
|
+
# T { @outside == 1 }
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# D! "an inner, insulated test" do
|
131
|
+
# F { defined? @outside }
|
132
|
+
# F { @outside == 1 }
|
133
|
+
#
|
134
|
+
# @inside = 2
|
135
|
+
# T { defined? @inside }
|
136
|
+
# T { @inside == 2 }
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# F { defined? @inside }
|
140
|
+
# F { @inside == 2 }
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
def D! *description, &block
|
144
|
+
create_test true, *description, &block
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# @overload def <(&block)
|
149
|
+
#
|
150
|
+
# Registers the given block to be executed
|
151
|
+
# before each nested test inside this test.
|
152
|
+
#
|
153
|
+
# @example
|
154
|
+
#
|
155
|
+
# D .< { puts "before each nested test" }
|
156
|
+
#
|
157
|
+
# D .< do
|
158
|
+
# puts "before each nested test"
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
def <(klass = nil, &block)
|
162
|
+
if klass
|
163
|
+
# this method is being used as a check for inheritance
|
164
|
+
#
|
165
|
+
# NOTE: we cannot call super() here because this module
|
166
|
+
# extends itself, thereby causing an infinite loop!
|
167
|
+
#
|
168
|
+
ancestors.include? klass
|
169
|
+
else
|
170
|
+
raise ArgumentError, 'block must be given' unless block
|
171
|
+
@suite.before_each << block
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# Registers the given block to be executed
|
177
|
+
# after each nested test inside this test.
|
178
|
+
#
|
179
|
+
# @example
|
180
|
+
#
|
181
|
+
# D .> { puts "after each nested test" }
|
182
|
+
#
|
183
|
+
# D .> do
|
184
|
+
# puts "after each nested test"
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
def > &block
|
188
|
+
raise ArgumentError, 'block must be given' unless block
|
189
|
+
@suite.after_each << block
|
190
|
+
end
|
191
|
+
|
192
|
+
##
|
193
|
+
# Registers the given block to be executed
|
194
|
+
# before all nested tests inside this test.
|
195
|
+
#
|
196
|
+
# @example
|
197
|
+
#
|
198
|
+
# D .<< { puts "before all nested tests" }
|
199
|
+
#
|
200
|
+
# D .<< do
|
201
|
+
# puts "before all nested tests"
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
def << &block
|
205
|
+
raise ArgumentError, 'block must be given' unless block
|
206
|
+
@suite.before_all << block
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Registers the given block to be executed
|
211
|
+
# after all nested tests inside this test.
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
#
|
215
|
+
# D .>> { puts "after all nested tests" }
|
216
|
+
#
|
217
|
+
# D .>> do
|
218
|
+
# puts "after all nested tests"
|
219
|
+
# end
|
220
|
+
#
|
221
|
+
def >> &block
|
222
|
+
raise ArgumentError, 'block must be given' unless block
|
223
|
+
@suite.after_all << block
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Asserts that the given condition or the
|
228
|
+
# result of the given block is neither
|
229
|
+
# nil nor false and returns that result.
|
230
|
+
#
|
231
|
+
# @param condition
|
232
|
+
#
|
233
|
+
# The condition to be asserted. A block
|
234
|
+
# may be given in place of this parameter.
|
235
|
+
#
|
236
|
+
# @param message
|
237
|
+
#
|
238
|
+
# Optional message to show in the test
|
239
|
+
# execution report if this assertion fails.
|
240
|
+
#
|
241
|
+
# @example no message given
|
242
|
+
#
|
243
|
+
# T { true } # passes
|
244
|
+
# T { false } # fails
|
245
|
+
# T { nil } # fails
|
246
|
+
#
|
247
|
+
# @example message is given
|
248
|
+
#
|
249
|
+
# T("computers do not doublethink") { 2 + 2 != 5 } # passes
|
250
|
+
#
|
251
|
+
def T condition = nil, message = nil, &block
|
252
|
+
assert_yield :assert, true, condition, message, &block
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Asserts that the given condition or the
|
257
|
+
# result of the given block is either nil
|
258
|
+
# or false and returns that result.
|
259
|
+
#
|
260
|
+
# @param condition (see Detest.T)
|
261
|
+
#
|
262
|
+
# @param message (see Detest.T)
|
263
|
+
#
|
264
|
+
# @example no message given
|
265
|
+
#
|
266
|
+
# T! { true } # fails
|
267
|
+
# T! { false } # passes
|
268
|
+
# T! { nil } # passes
|
269
|
+
#
|
270
|
+
# @example message is given
|
271
|
+
#
|
272
|
+
# T!("computers do not doublethink") { 2 + 2 == 5 } # passes
|
273
|
+
#
|
274
|
+
def T! condition = nil, message = nil, &block
|
275
|
+
assert_yield :negate, true, condition, message, &block
|
276
|
+
end
|
277
|
+
|
278
|
+
##
|
279
|
+
# Returns true if the given condition or
|
280
|
+
# the result of the given block is neither
|
281
|
+
# nil nor false. Otherwise, returns false.
|
282
|
+
#
|
283
|
+
# @param condition (see Detest.T)
|
284
|
+
#
|
285
|
+
# @param message
|
286
|
+
#
|
287
|
+
# This parameter is optional and completely ignored.
|
288
|
+
#
|
289
|
+
# @example no message given
|
290
|
+
#
|
291
|
+
# T? { true } # => true
|
292
|
+
# T? { false } # => false
|
293
|
+
# T? { nil } # => false
|
294
|
+
#
|
295
|
+
# @example message is given
|
296
|
+
#
|
297
|
+
# T?("computers do not doublethink") { 2 + 2 != 5 } # => true
|
298
|
+
#
|
299
|
+
def T? condition = nil, message = nil, &block
|
300
|
+
assert_yield :sample, true, condition, message, &block
|
301
|
+
end
|
302
|
+
|
303
|
+
alias F T!
|
304
|
+
|
305
|
+
alias F! T
|
306
|
+
|
307
|
+
##
|
308
|
+
# Returns true if the result of the given block is
|
309
|
+
# either nil or false. Otherwise, returns false.
|
310
|
+
#
|
311
|
+
# @param message (see Detest.T?)
|
312
|
+
#
|
313
|
+
# @example no message given
|
314
|
+
#
|
315
|
+
# F? { true } # => false
|
316
|
+
# F? { false } # => true
|
317
|
+
# F? { nil } # => true
|
318
|
+
#
|
319
|
+
# @example message is given
|
320
|
+
#
|
321
|
+
# F?( "computers do not doublethink" ) { 2 + 2 == 5 } # => true
|
322
|
+
#
|
323
|
+
def F? condition = nil, message = nil, &block
|
324
|
+
assert_yield :sample, false, condition, message, &block
|
325
|
+
end
|
326
|
+
|
327
|
+
##
|
328
|
+
# Asserts that the given condition or the
|
329
|
+
# result of the given block is nil.
|
330
|
+
#
|
331
|
+
# @param condition
|
332
|
+
#
|
333
|
+
# The condition to be asserted. A block
|
334
|
+
# may be given in place of this parameter.
|
335
|
+
#
|
336
|
+
# @param message
|
337
|
+
#
|
338
|
+
# Optional message to show in the test
|
339
|
+
# execution report if this assertion fails.
|
340
|
+
#
|
341
|
+
# @example no message given
|
342
|
+
#
|
343
|
+
# N { nil } # passes
|
344
|
+
# N { false } # fails
|
345
|
+
# N { true } # fails
|
346
|
+
#
|
347
|
+
# @example message is given
|
348
|
+
#
|
349
|
+
# T("computers do not doublethink") { 2 + 2 != 5 } # passes
|
350
|
+
#
|
351
|
+
def N condition = nil, message = nil, &block
|
352
|
+
assert_yield :assert, nil, condition, message, &block
|
353
|
+
end
|
354
|
+
|
355
|
+
def N! condition = nil, message = nil, &block
|
356
|
+
assert_yield :negate, nil, condition, message, &block
|
357
|
+
end
|
358
|
+
|
359
|
+
def N? condition = nil, message = nil, &block
|
360
|
+
assert_yield :sample, nil, condition, message, &block
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# Asserts that one of the given
|
365
|
+
# kinds of exceptions is raised
|
366
|
+
# when the given block is executed.
|
367
|
+
#
|
368
|
+
# @return
|
369
|
+
#
|
370
|
+
# If the block raises an exception,
|
371
|
+
# then that exception is returned.
|
372
|
+
#
|
373
|
+
# Otherwise, nil is returned.
|
374
|
+
#
|
375
|
+
# @param [...] kinds_then_message
|
376
|
+
#
|
377
|
+
# Exception classes that must be raised by the given
|
378
|
+
# block, optionally followed by a message to show in
|
379
|
+
# the test execution report if this assertion fails.
|
380
|
+
#
|
381
|
+
# If no exception classes are given, then
|
382
|
+
# StandardError is assumed (similar to
|
383
|
+
# how a plain 'rescue' statement without
|
384
|
+
# any arguments catches StandardError).
|
385
|
+
#
|
386
|
+
# @example no exceptions given
|
387
|
+
#
|
388
|
+
# E { } # fails
|
389
|
+
# E { raise } # passes
|
390
|
+
#
|
391
|
+
# @example single exception given
|
392
|
+
#
|
393
|
+
# E(ArgumentError) { raise ArgumentError }
|
394
|
+
# E(ArgumentError, "argument must be invalid") { raise ArgumentError }
|
395
|
+
#
|
396
|
+
# @example multiple exceptions given
|
397
|
+
#
|
398
|
+
# E(SyntaxError, NameError) { eval "..." }
|
399
|
+
# E(SyntaxError, NameError, "string must compile") { eval "..." }
|
400
|
+
#
|
401
|
+
def E *kinds_then_message, &block
|
402
|
+
assert_raise :assert, *kinds_then_message, &block
|
403
|
+
end
|
404
|
+
|
405
|
+
##
|
406
|
+
# Asserts that one of the given kinds of exceptions
|
407
|
+
# is not raised when the given block is executed.
|
408
|
+
#
|
409
|
+
# @return (see Detest.E)
|
410
|
+
#
|
411
|
+
# @param kinds_then_message (see Detest.E)
|
412
|
+
#
|
413
|
+
# @example no exceptions given
|
414
|
+
#
|
415
|
+
# E! { } # passes
|
416
|
+
# E! { raise } # fails
|
417
|
+
#
|
418
|
+
# @example single exception given
|
419
|
+
#
|
420
|
+
# E!(ArgumentError) { raise ArgumentError } # fails
|
421
|
+
# E!(ArgumentError, "argument must be invalid") { raise ArgumentError }
|
422
|
+
#
|
423
|
+
# @example multiple exceptions given
|
424
|
+
#
|
425
|
+
# E!(SyntaxError, NameError) { eval "..." }
|
426
|
+
# E!(SyntaxError, NameError, "string must compile") { eval "..." }
|
427
|
+
#
|
428
|
+
def E! *kinds_then_message, &block
|
429
|
+
assert_raise :negate, *kinds_then_message, &block
|
430
|
+
end
|
431
|
+
|
432
|
+
##
|
433
|
+
# Returns true if one of the given kinds of
|
434
|
+
# exceptions is raised when the given block
|
435
|
+
# is executed. Otherwise, returns false.
|
436
|
+
#
|
437
|
+
# @param [...] kinds_then_message
|
438
|
+
#
|
439
|
+
# Exception classes that must be raised by
|
440
|
+
# the given block, optionally followed by
|
441
|
+
# a message that is completely ignored.
|
442
|
+
#
|
443
|
+
# If no exception classes are given, then
|
444
|
+
# StandardError is assumed (similar to
|
445
|
+
# how a plain 'rescue' statement without
|
446
|
+
# any arguments catches StandardError).
|
447
|
+
#
|
448
|
+
# @example no exceptions given
|
449
|
+
#
|
450
|
+
# E? { } # => false
|
451
|
+
# E? { raise } # => true
|
452
|
+
#
|
453
|
+
# @example single exception given
|
454
|
+
#
|
455
|
+
# E?(ArgumentError) { raise ArgumentError } # => true
|
456
|
+
#
|
457
|
+
# @example multiple exceptions given
|
458
|
+
#
|
459
|
+
# E?(SyntaxError, NameError) { eval "..." } # => true
|
460
|
+
# E!(SyntaxError, NameError, "string must compile") { eval "..." }
|
461
|
+
#
|
462
|
+
def E? *kinds_then_message, &block
|
463
|
+
assert_raise :sample, *kinds_then_message, &block
|
464
|
+
end
|
465
|
+
|
466
|
+
##
|
467
|
+
# Asserts that the given symbol is thrown
|
468
|
+
# when the given block is executed.
|
469
|
+
#
|
470
|
+
# @return
|
471
|
+
#
|
472
|
+
# If a value is thrown along
|
473
|
+
# with the expected symbol,
|
474
|
+
# then that value is returned.
|
475
|
+
#
|
476
|
+
# Otherwise, nil is returned.
|
477
|
+
#
|
478
|
+
# @param [Symbol] symbol
|
479
|
+
#
|
480
|
+
# Symbol that must be thrown by the given block.
|
481
|
+
#
|
482
|
+
# @param message (see Detest.T)
|
483
|
+
#
|
484
|
+
# @example no message given
|
485
|
+
#
|
486
|
+
# C(:foo) { throw :foo, 123 } # passes, => 123
|
487
|
+
# C(:foo) { throw :bar, 456 } # fails, => 456
|
488
|
+
# C(:foo) { } # fails, => nil
|
489
|
+
#
|
490
|
+
# @example message is given
|
491
|
+
#
|
492
|
+
# C(:foo, ":foo must be thrown") { throw :bar, 789 } # fails, => nil
|
493
|
+
#
|
494
|
+
def C symbol, message = nil, &block
|
495
|
+
assert_catch :assert, symbol, message, &block
|
496
|
+
end
|
497
|
+
|
498
|
+
##
|
499
|
+
# Asserts that the given symbol is not
|
500
|
+
# thrown when the given block is executed.
|
501
|
+
#
|
502
|
+
# @return nil, always.
|
503
|
+
#
|
504
|
+
# @param [Symbol] symbol
|
505
|
+
#
|
506
|
+
# Symbol that must not be thrown by the given block.
|
507
|
+
#
|
508
|
+
# @param message (see Detest.T)
|
509
|
+
#
|
510
|
+
# @example no message given
|
511
|
+
#
|
512
|
+
# C!(:foo) { throw :foo, 123 } # fails, => nil
|
513
|
+
# C!(:foo) { throw :bar, 456 } # passes, => nil
|
514
|
+
# C!(:foo) { } # passes, => nil
|
515
|
+
#
|
516
|
+
# @example message is given
|
517
|
+
#
|
518
|
+
# C!(:foo, ":foo must be thrown") { throw :bar, 789 } # passes, => nil
|
519
|
+
#
|
520
|
+
def C! symbol, message = nil, &block
|
521
|
+
assert_catch :negate, symbol, message, &block
|
522
|
+
end
|
523
|
+
|
524
|
+
##
|
525
|
+
# Returns true if the given symbol is thrown when the
|
526
|
+
# given block is executed. Otherwise, returns false.
|
527
|
+
#
|
528
|
+
# @param symbol (see Detest.C)
|
529
|
+
#
|
530
|
+
# @param message (see Detest.T?)
|
531
|
+
#
|
532
|
+
# @example no message given
|
533
|
+
#
|
534
|
+
# C?(:foo) { throw :foo, 123 } # => true
|
535
|
+
# C?(:foo) { throw :bar, 456 } # => false
|
536
|
+
# C?(:foo) { } # => false
|
537
|
+
#
|
538
|
+
# @example message is given
|
539
|
+
#
|
540
|
+
# C?(:foo, ":foo must be thrown") { throw :bar, 789 } # => false
|
541
|
+
#
|
542
|
+
def C? symbol, message = nil, &block
|
543
|
+
assert_catch :sample, symbol, message, &block
|
544
|
+
end
|
545
|
+
|
546
|
+
##
|
547
|
+
# Adds the given messages to the test execution
|
548
|
+
# report beneath the currently running test.
|
549
|
+
#
|
550
|
+
# You can think of "I" as to "inform" the user.
|
551
|
+
#
|
552
|
+
# @param messages
|
553
|
+
#
|
554
|
+
# Objects to be added to the test execution report.
|
555
|
+
#
|
556
|
+
# @example single message given
|
557
|
+
#
|
558
|
+
# I "establishing connection..."
|
559
|
+
#
|
560
|
+
# @example multiple messages given
|
561
|
+
#
|
562
|
+
# I "beginning calculation...", Math::PI, [1, 2, 3, ['a', 'b', 'c']]
|
563
|
+
#
|
564
|
+
def I *messages
|
565
|
+
@trace.concat messages
|
566
|
+
end
|
567
|
+
|
568
|
+
##
|
569
|
+
# Starts an interactive debugging session at
|
570
|
+
# the location where this method was called.
|
571
|
+
#
|
572
|
+
# You can think of "I!" as to "investigate" the program.
|
573
|
+
#
|
574
|
+
def I!
|
575
|
+
debug
|
576
|
+
end
|
577
|
+
|
578
|
+
##
|
579
|
+
# Mechanism for sharing code between tests.
|
580
|
+
#
|
581
|
+
# If a block is given, it is shared under
|
582
|
+
# the given identifier. Otherwise, the
|
583
|
+
# code block that was previously shared
|
584
|
+
# under the given identifier is injected
|
585
|
+
# into the closest insulated Detest test
|
586
|
+
# that contains the call to this method.
|
587
|
+
#
|
588
|
+
# @param [Symbol, Object] identifier
|
589
|
+
#
|
590
|
+
# An object that identifies shared code. This must be common
|
591
|
+
# knowledge to all parties that want to partake in the sharing.
|
592
|
+
#
|
593
|
+
# @example
|
594
|
+
#
|
595
|
+
# S :knowledge do
|
596
|
+
# #...
|
597
|
+
# end
|
598
|
+
#
|
599
|
+
# D "some test" do
|
600
|
+
# S :knowledge
|
601
|
+
# end
|
602
|
+
#
|
603
|
+
# D "another test" do
|
604
|
+
# S :knowledge
|
605
|
+
# end
|
606
|
+
#
|
607
|
+
def S identifier, &block
|
608
|
+
if block_given?
|
609
|
+
if already_shared = @share[identifier]
|
610
|
+
raise ArgumentError, "A code block #{already_shared.inspect} has "\
|
611
|
+
"already been shared under the identifier #{identifier.inspect}."
|
612
|
+
end
|
613
|
+
|
614
|
+
@share[identifier] = block
|
615
|
+
|
616
|
+
elsif block = @share[identifier]
|
617
|
+
if @tests.empty?
|
618
|
+
raise "Cannot inject code block #{block.inspect} shared under "\
|
619
|
+
"identifier #{identifier.inspect} outside of a Detest test."
|
620
|
+
else
|
621
|
+
# find the closest insulated parent test; this should always
|
622
|
+
# succeed because root-level tests are insulated by default
|
623
|
+
test = @tests.reverse.find {|t| t.sandbox } or raise IndexError
|
624
|
+
test.sandbox.instance_eval(&block)
|
625
|
+
end
|
626
|
+
|
627
|
+
else
|
628
|
+
raise ArgumentError, "No code block is shared under identifier "\
|
629
|
+
"#{identifier.inspect}."
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
##
|
634
|
+
# Shares the given code block under the given
|
635
|
+
# identifier and then immediately injects that
|
636
|
+
# code block into the closest insulated Detest
|
637
|
+
# test that contains the call to this method.
|
638
|
+
#
|
639
|
+
# @param identifier (see Detest.S)
|
640
|
+
#
|
641
|
+
# @example
|
642
|
+
#
|
643
|
+
# D "some test" do
|
644
|
+
# S! :knowledge do
|
645
|
+
# #...
|
646
|
+
# end
|
647
|
+
# end
|
648
|
+
#
|
649
|
+
# D "another test" do
|
650
|
+
# S :knowledge
|
651
|
+
# end
|
652
|
+
#
|
653
|
+
def S! identifier, &block
|
654
|
+
raise 'block must be given' unless block_given?
|
655
|
+
S identifier, &block
|
656
|
+
S identifier
|
657
|
+
end
|
658
|
+
|
659
|
+
##
|
660
|
+
# Checks whether any code has been shared under the given identifier.
|
661
|
+
#
|
662
|
+
def S? identifier
|
663
|
+
@share.key? identifier
|
664
|
+
end
|
665
|
+
|
666
|
+
##
|
667
|
+
# Executes all tests defined thus far and stores
|
668
|
+
# the results in {Detest.trace} and {Detest.stats}.
|
669
|
+
#
|
670
|
+
def start
|
671
|
+
# execute the tests
|
672
|
+
start_time = Time.now
|
673
|
+
catch :DIFECTS_STOP do
|
674
|
+
BINDINGS.track do
|
675
|
+
execute
|
676
|
+
end
|
677
|
+
end
|
678
|
+
@stats[:time] = Time.now - start_time
|
679
|
+
|
680
|
+
# print test results
|
681
|
+
unless @stats.key? :fail or @stats.key? :error
|
682
|
+
#
|
683
|
+
# show execution trace only if all tests passed.
|
684
|
+
# otherwise, we will be repeating already printed
|
685
|
+
# failure details and obstructing the developer!
|
686
|
+
#
|
687
|
+
display @trace
|
688
|
+
end
|
689
|
+
|
690
|
+
display @stats
|
691
|
+
|
692
|
+
ensure
|
693
|
+
@tests.clear
|
694
|
+
@share.clear
|
695
|
+
@files.clear
|
696
|
+
end
|
697
|
+
|
698
|
+
##
|
699
|
+
# Stops the execution of the {Detest.start} method or raises
|
700
|
+
# an exception if that method is not currently executing.
|
701
|
+
#
|
702
|
+
def stop
|
703
|
+
throw :DIFECTS_STOP
|
704
|
+
end
|
705
|
+
|
706
|
+
##
|
707
|
+
# Clear all test results that were recorded thus far.
|
708
|
+
#
|
709
|
+
def reset
|
710
|
+
@stats.clear
|
711
|
+
@trace.clear
|
712
|
+
end
|
713
|
+
|
714
|
+
##
|
715
|
+
# Returns the details of the failure that
|
716
|
+
# is currently being debugged by the user.
|
717
|
+
#
|
718
|
+
def info
|
719
|
+
@trace.last
|
720
|
+
end
|
721
|
+
|
722
|
+
private
|
723
|
+
|
724
|
+
def create_test insulate, *description, &block
|
725
|
+
raise ArgumentError, 'block must be given' unless block
|
726
|
+
|
727
|
+
description = description.join(' ')
|
728
|
+
sandbox = Object.new if insulate
|
729
|
+
|
730
|
+
@suite.tests << Suite::Test.new(description, block, sandbox)
|
731
|
+
end
|
732
|
+
|
733
|
+
def assert_yield mode, expect, condition = nil, message = nil, &block
|
734
|
+
# first parameter is actually the message when block is given
|
735
|
+
message = condition if block
|
736
|
+
|
737
|
+
message ||= [
|
738
|
+
(block ? 'block must yield' : 'condition must be'),
|
739
|
+
case expect
|
740
|
+
when nil then 'nil'
|
741
|
+
when true then 'true (!nil && !false)'
|
742
|
+
when false then 'false (nil || false)'
|
743
|
+
end
|
744
|
+
].join(' ')
|
745
|
+
|
746
|
+
passed = lambda do
|
747
|
+
@stats[:pass] += 1
|
748
|
+
end
|
749
|
+
|
750
|
+
failed = lambda do
|
751
|
+
@stats[:fail] += 1
|
752
|
+
debug message
|
753
|
+
end
|
754
|
+
|
755
|
+
result = block ? call(block) : condition
|
756
|
+
|
757
|
+
verdict =
|
758
|
+
case expect
|
759
|
+
when nil then result == nil
|
760
|
+
when true then result != nil && result != false
|
761
|
+
when false then result == nil || result == false
|
762
|
+
end
|
763
|
+
|
764
|
+
case mode
|
765
|
+
when :sample then return verdict
|
766
|
+
when :assert then verdict ? passed.call : failed.call
|
767
|
+
when :negate then verdict ? failed.call : passed.call
|
768
|
+
end
|
769
|
+
|
770
|
+
result
|
771
|
+
end
|
772
|
+
|
773
|
+
def assert_raise mode, *kinds_then_message, &block
|
774
|
+
raise ArgumentError, 'block must be given' unless block
|
775
|
+
|
776
|
+
message = kinds_then_message.pop
|
777
|
+
kinds = kinds_then_message
|
778
|
+
|
779
|
+
if message.kind_of? Class
|
780
|
+
kinds << message
|
781
|
+
message = nil
|
782
|
+
end
|
783
|
+
|
784
|
+
kinds << StandardError if kinds.empty?
|
785
|
+
|
786
|
+
message ||=
|
787
|
+
case mode
|
788
|
+
when :assert then "block must raise #{kinds.join ' or '}"
|
789
|
+
when :negate then "block must not raise #{kinds.join ' or '}"
|
790
|
+
end
|
791
|
+
|
792
|
+
passed = lambda do
|
793
|
+
@stats[:pass] += 1
|
794
|
+
end
|
795
|
+
|
796
|
+
failed = lambda do |exception|
|
797
|
+
@stats[:fail] += 1
|
798
|
+
|
799
|
+
if exception
|
800
|
+
# debug the uncaught exception...
|
801
|
+
debug_uncaught_exception exception
|
802
|
+
|
803
|
+
# ...in addition to debugging this assertion
|
804
|
+
debug [message, {'block raised' => exception}]
|
805
|
+
|
806
|
+
else
|
807
|
+
debug message
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
begin
|
812
|
+
block.call
|
813
|
+
|
814
|
+
rescue Exception => exception
|
815
|
+
expected = kinds.any? {|k| exception.kind_of? k }
|
816
|
+
|
817
|
+
case mode
|
818
|
+
when :sample then return expected
|
819
|
+
when :assert then expected ? passed.call : failed.call(exception)
|
820
|
+
when :negate then expected ? failed.call(exception) : passed.call
|
821
|
+
end
|
822
|
+
|
823
|
+
else # nothing was raised
|
824
|
+
case mode
|
825
|
+
when :sample then return false
|
826
|
+
when :assert then failed.call nil
|
827
|
+
when :negate then passed.call
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
exception
|
832
|
+
end
|
833
|
+
|
834
|
+
def assert_catch mode, symbol, message = nil, &block
|
835
|
+
raise ArgumentError, 'block must be given' unless block
|
836
|
+
|
837
|
+
symbol = symbol.to_sym
|
838
|
+
message ||= "block must throw #{symbol.inspect}"
|
839
|
+
|
840
|
+
passed = lambda do
|
841
|
+
@stats[:pass] += 1
|
842
|
+
end
|
843
|
+
|
844
|
+
failed = lambda do
|
845
|
+
@stats[:fail] += 1
|
846
|
+
debug message
|
847
|
+
end
|
848
|
+
|
849
|
+
# if nothing was thrown, the result of catch()
|
850
|
+
# is simply the result of executing the block
|
851
|
+
result = catch(symbol) do
|
852
|
+
begin
|
853
|
+
block.call
|
854
|
+
rescue Exception => e
|
855
|
+
#
|
856
|
+
# ignore error about the wrong symbol being thrown
|
857
|
+
#
|
858
|
+
# NOTE: Ruby 1.8 formats the thrown value in `quotes'
|
859
|
+
# whereas Ruby 1.9 formats it like a :symbol
|
860
|
+
#
|
861
|
+
unless e.message =~ /\Auncaught throw (`.*?'|:.*)\z/
|
862
|
+
debug_uncaught_exception e
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
self # unlikely that block will throw *this* object
|
867
|
+
end
|
868
|
+
|
869
|
+
caught = result != self
|
870
|
+
result = nil unless caught
|
871
|
+
|
872
|
+
case mode
|
873
|
+
when :sample then return caught
|
874
|
+
when :assert then caught ? passed.call : failed.call
|
875
|
+
when :negate then caught ? failed.call : passed.call
|
876
|
+
end
|
877
|
+
|
878
|
+
result
|
879
|
+
end
|
880
|
+
|
881
|
+
##
|
882
|
+
# Prints the given object in YAML
|
883
|
+
# format if possible, or falls back
|
884
|
+
# to Ruby's pretty print library.
|
885
|
+
#
|
886
|
+
def display object
|
887
|
+
# stringify symbols in YAML output for better readability
|
888
|
+
puts object.to_yaml.gsub(/^([[:blank:]]*(- )?):(?=@?\w+: )/, '\1')
|
889
|
+
rescue
|
890
|
+
require 'pp'
|
891
|
+
pp object
|
892
|
+
end
|
893
|
+
|
894
|
+
##
|
895
|
+
# Executes the current test suite recursively.
|
896
|
+
#
|
897
|
+
def execute
|
898
|
+
suite = @suite
|
899
|
+
trace = @trace
|
900
|
+
|
901
|
+
suite.before_all.each {|b| call b }
|
902
|
+
|
903
|
+
suite.tests.each do |test|
|
904
|
+
suite.before_each.each {|b| call b }
|
905
|
+
|
906
|
+
@tests.push test
|
907
|
+
|
908
|
+
begin
|
909
|
+
# create nested suite
|
910
|
+
@suite = Suite.new
|
911
|
+
@trace = []
|
912
|
+
|
913
|
+
# populate nested suite
|
914
|
+
call test.block, test.sandbox
|
915
|
+
|
916
|
+
# execute nested suite
|
917
|
+
execute
|
918
|
+
|
919
|
+
ensure
|
920
|
+
# restore outer values
|
921
|
+
@suite = suite
|
922
|
+
|
923
|
+
trace << build_exec_trace(@trace)
|
924
|
+
@trace = trace
|
925
|
+
end
|
926
|
+
|
927
|
+
@tests.pop
|
928
|
+
|
929
|
+
suite.after_each.each {|b| call b }
|
930
|
+
end
|
931
|
+
|
932
|
+
suite.after_all.each {|b| call b }
|
933
|
+
end
|
934
|
+
|
935
|
+
##
|
936
|
+
# Invokes the given block and debugs any
|
937
|
+
# exceptions that may arise as a result.
|
938
|
+
#
|
939
|
+
def call block, sandbox = nil
|
940
|
+
if sandbox
|
941
|
+
sandbox.instance_eval(&block)
|
942
|
+
else
|
943
|
+
block.call
|
944
|
+
end
|
945
|
+
|
946
|
+
rescue Exception => e
|
947
|
+
debug_uncaught_exception e
|
948
|
+
end
|
949
|
+
|
950
|
+
INTERNALS = /^#{Regexp.escape(File.dirname(__FILE__))}/ # @private
|
951
|
+
|
952
|
+
class << BINDINGS = Hash.new {|h,k| h[k] = {} } # @private
|
953
|
+
##
|
954
|
+
# Keeps track of bindings for all
|
955
|
+
# lines of code processed by Ruby
|
956
|
+
# for use later in {Detest.debug}.
|
957
|
+
#
|
958
|
+
def track
|
959
|
+
raise ArgumentError unless block_given?
|
960
|
+
|
961
|
+
set_trace_func lambda {|event, file, line, id, binding, klass|
|
962
|
+
unless file =~ INTERNALS
|
963
|
+
self[file][line] = binding
|
964
|
+
end
|
965
|
+
}
|
966
|
+
|
967
|
+
yield
|
968
|
+
|
969
|
+
ensure
|
970
|
+
set_trace_func nil
|
971
|
+
clear
|
972
|
+
end
|
973
|
+
end
|
974
|
+
|
975
|
+
##
|
976
|
+
# Adds debugging information to the test execution report and
|
977
|
+
# invokes the debugger if the {Detest.debug} option is enabled.
|
978
|
+
#
|
979
|
+
# @param message
|
980
|
+
#
|
981
|
+
# Message describing the failure
|
982
|
+
# in the code being debugged.
|
983
|
+
#
|
984
|
+
# @param [Array<String>] backtrace
|
985
|
+
#
|
986
|
+
# Stack trace corresponding to point of
|
987
|
+
# failure in the code being debugged.
|
988
|
+
#
|
989
|
+
def debug message = nil, backtrace = caller
|
990
|
+
# omit internals from failure details
|
991
|
+
backtrace = backtrace.reject {|s| s =~ INTERNALS }
|
992
|
+
|
993
|
+
backtrace.first =~ /(.+?):(\d+(?=:|\z))/ or raise SyntaxError
|
994
|
+
source_file, source_line = $1, $2.to_i
|
995
|
+
|
996
|
+
binding_by_line = BINDINGS[source_file]
|
997
|
+
binding_line =
|
998
|
+
if binding_by_line.key? source_line
|
999
|
+
source_line
|
1000
|
+
else
|
1001
|
+
#
|
1002
|
+
# There is no binding for the line number given in the backtrace, so
|
1003
|
+
# try to adjust it to the nearest line that actually has a binding.
|
1004
|
+
#
|
1005
|
+
# This problem occurs because line numbers reported in backtraces
|
1006
|
+
# sometimes do not agree with those observed by set_trace_func(),
|
1007
|
+
# particularly in method calls that span multiple lines:
|
1008
|
+
# set_trace_func() will consistently observe the ending parenthesis
|
1009
|
+
# (last line of the method call) whereas the backtrace will oddly
|
1010
|
+
# report a line somewhere in the middle of the method call.
|
1011
|
+
#
|
1012
|
+
# NOTE: I chose to adjust the imprecise line to the nearest one
|
1013
|
+
# BELOW it. This might not always be correct because the nearest
|
1014
|
+
# line below could belong to a different scope, like a new class.
|
1015
|
+
#
|
1016
|
+
binding_by_line.keys.sort.find {|n| n > source_line }
|
1017
|
+
end
|
1018
|
+
binding = binding_by_line[binding_line]
|
1019
|
+
|
1020
|
+
# record failure details in the test execution report
|
1021
|
+
details = Hash[
|
1022
|
+
# user message
|
1023
|
+
:fail, message,
|
1024
|
+
|
1025
|
+
# stack trace
|
1026
|
+
:call, backtrace,
|
1027
|
+
|
1028
|
+
# code snippet
|
1029
|
+
:code, (
|
1030
|
+
if source = @files[source_file]
|
1031
|
+
radius = 5 # number of surrounding lines to show
|
1032
|
+
region = [source_line - radius, 1].max ..
|
1033
|
+
[source_line + radius, source.length].min
|
1034
|
+
|
1035
|
+
# ensure proper alignment by zero-padding line numbers
|
1036
|
+
format = "%2s %0#{region.last.to_s.length}d %s"
|
1037
|
+
|
1038
|
+
pretty = region.map do |n|
|
1039
|
+
format % [('=>' if n == source_line), n, source[n-1].chomp]
|
1040
|
+
end.unshift "[#{region.inspect}] in #{source_file}"
|
1041
|
+
|
1042
|
+
pretty.extend FailureDetails::CodeListing
|
1043
|
+
end
|
1044
|
+
)
|
1045
|
+
]
|
1046
|
+
|
1047
|
+
if binding
|
1048
|
+
# binding location
|
1049
|
+
details[:bind] = [source_file, binding_line].join(':')
|
1050
|
+
|
1051
|
+
# variable values
|
1052
|
+
variables = eval('::Kernel.local_variables', binding, __FILE__, __LINE__)
|
1053
|
+
|
1054
|
+
details[:vars] = variables.inject(Hash.new) do |hash, variable|
|
1055
|
+
hash[variable.to_sym] = eval(variable.to_s, binding, __FILE__, __LINE__)
|
1056
|
+
hash
|
1057
|
+
end.extend(FailureDetails::VariablesListing)
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
details.reject! {|key, value| (value || details[key]).nil? }
|
1061
|
+
@trace << details
|
1062
|
+
|
1063
|
+
# show all failure details to the user
|
1064
|
+
display build_fail_trace(details)
|
1065
|
+
|
1066
|
+
# allow user to investigate the failure
|
1067
|
+
if @debug and binding
|
1068
|
+
unless defined? IRB
|
1069
|
+
require 'irb'
|
1070
|
+
IRB.setup nil
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
irb = IRB::Irb.new(IRB::WorkSpace.new(binding))
|
1074
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
1075
|
+
catch(:IRB_EXIT) { irb.eval_input }
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
nil
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
##
|
1082
|
+
# Debugs the given uncaught exception inside the given context.
|
1083
|
+
#
|
1084
|
+
def debug_uncaught_exception exception
|
1085
|
+
@stats[:error] += 1
|
1086
|
+
debug exception, exception.backtrace
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
##
|
1090
|
+
# Returns a report that associates the given
|
1091
|
+
# failure details with the currently running test.
|
1092
|
+
#
|
1093
|
+
def build_exec_trace details
|
1094
|
+
if @tests.empty?
|
1095
|
+
details
|
1096
|
+
else
|
1097
|
+
{ @tests.last.desc => (details unless details.empty?) }
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
##
|
1102
|
+
# Returns a report that qualifies the given
|
1103
|
+
# failure details with the current test stack.
|
1104
|
+
#
|
1105
|
+
def build_fail_trace details
|
1106
|
+
@tests.reverse.inject(details) do |inner, outer|
|
1107
|
+
{ outer.desc => inner }
|
1108
|
+
end
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
##
|
1112
|
+
# Logic to pretty print the details of an assertion failure.
|
1113
|
+
#
|
1114
|
+
module FailureDetails # @private
|
1115
|
+
module CodeListing
|
1116
|
+
def to_yaml options = {}
|
1117
|
+
#
|
1118
|
+
# strip because to_yaml() will render the paragraph without escaping
|
1119
|
+
# newlines ONLY IF the first and last character are non-whitespace!
|
1120
|
+
#
|
1121
|
+
join("\n").strip.to_yaml(options)
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
def pretty_print printer
|
1125
|
+
margin = ' ' * printer.indent
|
1126
|
+
printer.text [
|
1127
|
+
first, self[1..-1].map {|line| margin + line }, margin
|
1128
|
+
].join(printer.newline)
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
module VariablesListing
|
1133
|
+
def to_yaml options = {}
|
1134
|
+
require 'pp'
|
1135
|
+
require 'stringio'
|
1136
|
+
|
1137
|
+
inject(Hash.new) do |hash, (variable, value)|
|
1138
|
+
pretty = PP.pp(value, StringIO.new).string.chomp
|
1139
|
+
hash[variable] = "(#{value.class}) #{pretty}"
|
1140
|
+
hash
|
1141
|
+
end.to_yaml(options)
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
class Suite # @private
|
1147
|
+
attr_reader :tests, :before_each, :after_each, :before_all, :after_all
|
1148
|
+
|
1149
|
+
def initialize
|
1150
|
+
@tests = []
|
1151
|
+
@before_each = []
|
1152
|
+
@after_each = []
|
1153
|
+
@before_all = []
|
1154
|
+
@after_all = []
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
Test = Struct.new(:desc, :block, :sandbox) # @private
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
# provide mixin-able versions of Detest's core vocabulary
|
1162
|
+
singleton_methods(false).grep(/^[[:upper:]]?[[:punct:]]*$/).each do |meth|
|
1163
|
+
#
|
1164
|
+
# XXX: using eval() on a string because Ruby 1.8's
|
1165
|
+
# define_method() cannot take a block parameter
|
1166
|
+
#
|
1167
|
+
file, line = __FILE__, __LINE__ ; module_eval %{
|
1168
|
+
def #{meth}(*args, &block)
|
1169
|
+
::#{name}.#{meth}(*args, &block)
|
1170
|
+
end
|
1171
|
+
}, file, line
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
# allow mixin-able methods to be accessed as class methods
|
1175
|
+
extend self
|
1176
|
+
|
1177
|
+
# allow before and after hooks to be specified via the
|
1178
|
+
# following method syntax when this module is mixed-in:
|
1179
|
+
#
|
1180
|
+
# D .<< { puts "before all nested tests" }
|
1181
|
+
# D .< { puts "before each nested test" }
|
1182
|
+
# D .> { puts "after each nested test" }
|
1183
|
+
# D .>> { puts "after all nested tests" }
|
1184
|
+
#
|
1185
|
+
D = self
|
1186
|
+
|
1187
|
+
# set Detest::Hash from an ordered hash library in lesser Ruby versions
|
1188
|
+
if RUBY_VERSION < '1.9'
|
1189
|
+
begin
|
1190
|
+
#
|
1191
|
+
# NOTE: I realize that there are other libraries, such as facets and
|
1192
|
+
# activesupport, that provide an ordered hash implementation, but this
|
1193
|
+
# particular library does not interfere with pretty printing routines.
|
1194
|
+
#
|
1195
|
+
require 'orderedhash'
|
1196
|
+
Hash = OrderedHash
|
1197
|
+
rescue LoadError
|
1198
|
+
warn "#{inspect}: Install 'orderedhash' gem for better failure reports."
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
@debug = $DEBUG
|
1203
|
+
|
1204
|
+
@stats = Hash.new {|h,k| h[k] = 0 }
|
1205
|
+
@trace = []
|
1206
|
+
|
1207
|
+
@suite = class << self; Suite.new; end
|
1208
|
+
@share = {}
|
1209
|
+
@tests = []
|
1210
|
+
@files = Hash.new {|h,k| h[k] = File.readlines(k) rescue nil }
|
1211
|
+
end
|