detest 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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 } }
@@ -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