betatest 0.0.1

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.
@@ -0,0 +1,661 @@
1
+ require "rbconfig"
2
+
3
+ module Betatest
4
+ ##
5
+ # Betatest Assertions. All assertion methods accept a +msg+ which is
6
+ # printed if the assertion fails.
7
+ #
8
+ # Protocol: Nearly everything here boils up to +assert+, which
9
+ # expects to be able to increment an instance accessor named
10
+ # +assertions+. This is not provided by Assertions and must be
11
+ # provided by the thing including Assertions. See Betatest::Runnable
12
+ # for an example.
13
+
14
+ module Assertions
15
+ UNDEFINED = Object.new # :nodoc:
16
+
17
+ def UNDEFINED.inspect # :nodoc:
18
+ "UNDEFINED" # again with the rdoc bugs... :(
19
+ end
20
+
21
+ ##
22
+ # Returns the diff command to use in #diff. Tries to intelligently
23
+ # figure out what diff to use.
24
+
25
+ def self.diff
26
+ @diff = if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ &&
27
+ system("diff.exe", __FILE__, __FILE__)) then
28
+ "diff.exe -u"
29
+ elsif Betatest::Test.maglev? then
30
+ "diff -u"
31
+ elsif system("gdiff", __FILE__, __FILE__)
32
+ "gdiff -u" # solaris and kin suck
33
+ elsif system("diff", __FILE__, __FILE__)
34
+ "diff -u"
35
+ else
36
+ nil
37
+ end unless defined? @diff
38
+
39
+ @diff
40
+ end
41
+
42
+ ##
43
+ # Set the diff command to use in #diff.
44
+
45
+ def self.diff= o
46
+ @diff = o
47
+ end
48
+
49
+ ##
50
+ # Returns a diff between +exp+ and +act+. If there is no known
51
+ # diff command or if it doesn't make sense to diff the output
52
+ # (single line, short output), then it simply returns a basic
53
+ # comparison between the two.
54
+
55
+ def diff exp, act
56
+ require "tempfile"
57
+
58
+ expect = mu_pp_for_diff exp
59
+ butwas = mu_pp_for_diff act
60
+ result = nil
61
+
62
+ need_to_diff =
63
+ Betatest::Assertions.diff &&
64
+ (expect.include?("\n") ||
65
+ butwas.include?("\n") ||
66
+ expect.size > 30 ||
67
+ butwas.size > 30 ||
68
+ expect == butwas)
69
+
70
+ return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
71
+ need_to_diff
72
+
73
+ Tempfile.open("expect") do |a|
74
+ a.puts expect
75
+ a.flush
76
+
77
+ Tempfile.open("butwas") do |b|
78
+ b.puts butwas
79
+ b.flush
80
+
81
+ result = `#{Betatest::Assertions.diff} #{a.path} #{b.path}`
82
+ result.sub!(/^\-\-\- .+/, "--- expected")
83
+ result.sub!(/^\+\+\+ .+/, "+++ actual")
84
+
85
+ if result.empty? then
86
+ klass = exp.class
87
+ result = [
88
+ "No visible difference in the #{klass}#inspect output.\n",
89
+ "You should look at the implementation of #== on ",
90
+ "#{klass} or its members.\n",
91
+ expect,
92
+ ].join
93
+ end
94
+ end
95
+ end
96
+
97
+ result
98
+ end
99
+
100
+ ##
101
+ # This returns a human-readable version of +obj+. By default
102
+ # #inspect is called. You can override this to use #pretty_print
103
+ # if you want.
104
+
105
+ def mu_pp obj
106
+ s = obj.inspect
107
+ s = s.encode Encoding.default_external if defined? Encoding
108
+ s
109
+ end
110
+
111
+ ##
112
+ # This returns a diff-able human-readable version of +obj+. This
113
+ # differs from the regular mu_pp because it expands escaped
114
+ # newlines and makes hex-values generic (like object_ids). This
115
+ # uses mu_pp to do the first pass and then cleans it up.
116
+
117
+ def mu_pp_for_diff obj
118
+ mu_pp(obj).gsub(/\\n/, "\n").gsub(/:0x[a-fA-F0-9]{4,}/m, ':0xXXXXXX')
119
+ end
120
+
121
+ ##
122
+ # Fails unless +test+ is truthy.
123
+
124
+ def assert test, msg = nil
125
+ msg ||= "Failed assertion, no message given."
126
+ self.assertions += 1
127
+ unless test then
128
+ msg = msg.call if Proc === msg
129
+ raise Betatest::Assertion, msg
130
+ end
131
+ true
132
+ end
133
+
134
+ def _synchronize # :nodoc:
135
+ yield
136
+ end
137
+
138
+ ##
139
+ # Fails unless +obj+ is empty.
140
+
141
+ def assert_empty obj, msg = nil
142
+ msg = message(msg) { "Expected #{mu_pp(obj)} to be empty" }
143
+ assert_respond_to obj, :empty?
144
+ assert obj.empty?, msg
145
+ end
146
+
147
+ ##
148
+ # Fails unless <tt>exp == act</tt> printing the difference between
149
+ # the two, if possible.
150
+ #
151
+ # If there is no visible difference but the assertion fails, you
152
+ # should suspect that your #== is buggy, or your inspect output is
153
+ # missing crucial details.
154
+ #
155
+ # For floats use assert_in_delta.
156
+ #
157
+ # See also: Betatest::Assertions.diff
158
+
159
+ def assert_equal exp, act, msg = nil
160
+ msg = message(msg, "") { diff exp, act }
161
+ assert exp == act, msg
162
+ end
163
+
164
+ ##
165
+ # For comparing Floats. Fails unless +exp+ and +act+ are within +delta+
166
+ # of each other.
167
+ #
168
+ # assert_in_delta Math::PI, (22.0 / 7.0), 0.01
169
+
170
+ def assert_in_delta exp, act, delta = 0.001, msg = nil
171
+ n = (exp - act).abs
172
+ msg = message(msg) {
173
+ "Expected |#{exp} - #{act}| (#{n}) to be <= #{delta}"
174
+ }
175
+ assert delta >= n, msg
176
+ end
177
+
178
+ ##
179
+ # For comparing Floats. Fails unless +exp+ and +act+ have a relative
180
+ # error less than +epsilon+.
181
+
182
+ def assert_in_epsilon a, b, epsilon = 0.001, msg = nil
183
+ assert_in_delta a, b, [a.abs, b.abs].min * epsilon, msg
184
+ end
185
+
186
+ ##
187
+ # Fails unless +collection+ includes +obj+.
188
+
189
+ def assert_includes collection, obj, msg = nil
190
+ msg = message(msg) {
191
+ "Expected #{mu_pp(collection)} to include #{mu_pp(obj)}"
192
+ }
193
+ assert_respond_to collection, :include?
194
+ assert collection.include?(obj), msg
195
+ end
196
+
197
+ ##
198
+ # Fails unless +obj+ is an instance of +cls+.
199
+
200
+ def assert_instance_of cls, obj, msg = nil
201
+ msg = message(msg) {
202
+ "Expected #{mu_pp(obj)} to be an instance of #{cls}, not #{obj.class}"
203
+ }
204
+
205
+ assert obj.instance_of?(cls), msg
206
+ end
207
+
208
+ ##
209
+ # Fails unless +obj+ is a kind of +cls+.
210
+
211
+ def assert_kind_of cls, obj, msg = nil
212
+ msg = message(msg) {
213
+ "Expected #{mu_pp(obj)} to be a kind of #{cls}, not #{obj.class}" }
214
+
215
+ assert obj.kind_of?(cls), msg
216
+ end
217
+
218
+ ##
219
+ # Fails unless +matcher+ <tt>=~</tt> +obj+.
220
+
221
+ def assert_match matcher, obj, msg = nil
222
+ msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" }
223
+ assert_respond_to matcher, :"=~"
224
+ matcher = Regexp.new Regexp.escape matcher if String === matcher
225
+ assert matcher =~ obj, msg
226
+ end
227
+
228
+ ##
229
+ # Fails unless +obj+ is nil
230
+
231
+ def assert_nil obj, msg = nil
232
+ msg = message(msg) { "Expected #{mu_pp(obj)} to be nil" }
233
+ assert obj.nil?, msg
234
+ end
235
+
236
+ ##
237
+ # For testing with binary operators. Eg:
238
+ #
239
+ # assert_operator 5, :<=, 4
240
+
241
+ def assert_operator o1, op, o2 = UNDEFINED, msg = nil
242
+ return assert_predicate o1, op, msg if UNDEFINED == o2
243
+ msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
244
+ assert o1.__send__(op, o2), msg
245
+ end
246
+
247
+ ##
248
+ # Fails if stdout or stderr do not output the expected results.
249
+ # Pass in nil if you don't care about that streams output. Pass in
250
+ # "" if you require it to be silent. Pass in a regexp if you want
251
+ # to pattern match.
252
+ #
253
+ # NOTE: this uses #capture_io, not #capture_subprocess_io.
254
+ #
255
+ # See also: #assert_silent
256
+
257
+ def assert_output stdout = nil, stderr = nil
258
+ out, err = capture_io do
259
+ yield
260
+ end
261
+
262
+ err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr
263
+ out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout
264
+
265
+ y = send err_msg, stderr, err, "In stderr" if err_msg
266
+ x = send out_msg, stdout, out, "In stdout" if out_msg
267
+
268
+ (!stdout || x) && (!stderr || y)
269
+ end
270
+
271
+ ##
272
+ # For testing with predicates. Eg:
273
+ #
274
+ # assert_predicate str, :empty?
275
+ #
276
+ # This is really meant for specs and is front-ended by assert_operator:
277
+ #
278
+ # str.must_be :empty?
279
+
280
+ def assert_predicate o1, op, msg = nil
281
+ msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op}" }
282
+ assert o1.__send__(op), msg
283
+ end
284
+
285
+ ##
286
+ # Fails unless the block raises one of +exp+. Returns the
287
+ # exception matched so you can check the message, attributes, etc.
288
+
289
+ def assert_raises *exp
290
+ msg = "#{exp.pop}.\n" if String === exp.last
291
+
292
+ begin
293
+ yield
294
+ rescue Betatest::Skip => e
295
+ return e if exp.include? Betatest::Skip
296
+ raise e
297
+ rescue Exception => e
298
+ expected = exp.any? { |ex|
299
+ if ex.instance_of? Module then
300
+ e.kind_of? ex
301
+ else
302
+ e.instance_of? ex
303
+ end
304
+ }
305
+
306
+ assert expected, proc {
307
+ exception_details(e, "#{msg}#{mu_pp(exp)} exception expected, not")
308
+ }
309
+
310
+ return e
311
+ end
312
+
313
+ exp = exp.first if exp.size == 1
314
+
315
+ flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised."
316
+ end
317
+
318
+ ##
319
+ # Fails unless +obj+ responds to +meth+.
320
+
321
+ def assert_respond_to obj, meth, msg = nil
322
+ msg = message(msg) {
323
+ "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
324
+ }
325
+ assert obj.respond_to?(meth), msg
326
+ end
327
+
328
+ ##
329
+ # Fails unless +exp+ and +act+ are #equal?
330
+
331
+ def assert_same exp, act, msg = nil
332
+ msg = message(msg) {
333
+ data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
334
+ "Expected %s (oid=%d) to be the same as %s (oid=%d)" % data
335
+ }
336
+ assert exp.equal?(act), msg
337
+ end
338
+
339
+ ##
340
+ # +send_ary+ is a receiver, message and arguments.
341
+ #
342
+ # Fails unless the call returns a true value
343
+
344
+ def assert_send send_ary, m = nil
345
+ recv, msg, *args = send_ary
346
+ m = message(m) {
347
+ "Expected #{mu_pp(recv)}.#{msg}(*#{mu_pp(args)}) to return true" }
348
+ assert recv.__send__(msg, *args), m
349
+ end
350
+
351
+ ##
352
+ # Fails if the block outputs anything to stderr or stdout.
353
+ #
354
+ # See also: #assert_output
355
+
356
+ def assert_silent
357
+ assert_output "", "" do
358
+ yield
359
+ end
360
+ end
361
+
362
+ ##
363
+ # Fails unless the block throws +sym+
364
+
365
+ def assert_throws sym, msg = nil
366
+ default = "Expected #{mu_pp(sym)} to have been thrown"
367
+ caught = true
368
+ catch(sym) do
369
+ begin
370
+ yield
371
+ rescue ThreadError => e # wtf?!? 1.8 + threads == suck
372
+ default += ", not \:#{e.message[/uncaught throw \`(\w+?)\'/, 1]}"
373
+ rescue ArgumentError => e # 1.9 exception
374
+ default += ", not #{e.message.split(/ /).last}"
375
+ rescue NameError => e # 1.8 exception
376
+ default += ", not #{e.name.inspect}"
377
+ end
378
+ caught = false
379
+ end
380
+
381
+ assert caught, message(msg) { default }
382
+ end
383
+
384
+ ##
385
+ # Captures $stdout and $stderr into strings:
386
+ #
387
+ # out, err = capture_io do
388
+ # puts "Some info"
389
+ # warn "You did a bad thing"
390
+ # end
391
+ #
392
+ # assert_match %r%info%, out
393
+ # assert_match %r%bad%, err
394
+ #
395
+ # NOTE: For efficiency, this method uses StringIO and does not
396
+ # capture IO for subprocesses. Use #capture_subprocess_io for
397
+ # that.
398
+
399
+ def capture_io
400
+ _synchronize do
401
+ begin
402
+ require 'stringio'
403
+
404
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
405
+
406
+ orig_stdout, orig_stderr = $stdout, $stderr
407
+ $stdout, $stderr = captured_stdout, captured_stderr
408
+
409
+ yield
410
+
411
+ return captured_stdout.string, captured_stderr.string
412
+ ensure
413
+ $stdout = orig_stdout
414
+ $stderr = orig_stderr
415
+ end
416
+ end
417
+ end
418
+
419
+ ##
420
+ # Captures $stdout and $stderr into strings, using Tempfile to
421
+ # ensure that subprocess IO is captured as well.
422
+ #
423
+ # out, err = capture_subprocess_io do
424
+ # system "echo Some info"
425
+ # system "echo You did a bad thing 1>&2"
426
+ # end
427
+ #
428
+ # assert_match %r%info%, out
429
+ # assert_match %r%bad%, err
430
+ #
431
+ # NOTE: This method is approximately 10x slower than #capture_io so
432
+ # only use it when you need to test the output of a subprocess.
433
+
434
+ def capture_subprocess_io
435
+ _synchronize do
436
+ begin
437
+ require 'tempfile'
438
+
439
+ captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
440
+
441
+ orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
442
+ $stdout.reopen captured_stdout
443
+ $stderr.reopen captured_stderr
444
+
445
+ yield
446
+
447
+ $stdout.rewind
448
+ $stderr.rewind
449
+
450
+ return captured_stdout.read, captured_stderr.read
451
+ ensure
452
+ captured_stdout.unlink
453
+ captured_stderr.unlink
454
+ $stdout.reopen orig_stdout
455
+ $stderr.reopen orig_stderr
456
+ end
457
+ end
458
+ end
459
+
460
+ ##
461
+ # Returns details for exception +e+
462
+
463
+ def exception_details e, msg
464
+ [
465
+ "#{msg}",
466
+ "Class: <#{e.class}>",
467
+ "Message: <#{e.message.inspect}>",
468
+ "---Backtrace---",
469
+ "#{Betatest::filter_backtrace(e.backtrace).join("\n")}",
470
+ "---------------",
471
+ ].join "\n"
472
+ end
473
+
474
+ ##
475
+ # Fails with +msg+
476
+
477
+ def flunk msg = nil
478
+ msg ||= "Epic Fail!"
479
+ assert false, msg
480
+ end
481
+
482
+ ##
483
+ # Returns a proc that will output +msg+ along with the default message.
484
+
485
+ def message msg = nil, ending = ".", &default
486
+ proc {
487
+ msg = msg.call.chomp(".") if Proc === msg
488
+ custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
489
+ "#{custom_message}#{default.call}#{ending}"
490
+ }
491
+ end
492
+
493
+ ##
494
+ # used for counting assertions
495
+
496
+ def pass msg = nil
497
+ assert true
498
+ end
499
+
500
+ ##
501
+ # Fails if +test+ is truthy.
502
+
503
+ def refute test, msg = nil
504
+ msg ||= "Failed refutation, no message given"
505
+ not assert(! test, msg)
506
+ end
507
+
508
+ ##
509
+ # Fails if +obj+ is empty.
510
+
511
+ def refute_empty obj, msg = nil
512
+ msg = message(msg) { "Expected #{mu_pp(obj)} to not be empty" }
513
+ assert_respond_to obj, :empty?
514
+ refute obj.empty?, msg
515
+ end
516
+
517
+ ##
518
+ # Fails if <tt>exp == act</tt>.
519
+ #
520
+ # For floats use refute_in_delta.
521
+
522
+ def refute_equal exp, act, msg = nil
523
+ msg = message(msg) {
524
+ "Expected #{mu_pp(act)} to not be equal to #{mu_pp(exp)}"
525
+ }
526
+ refute exp == act, msg
527
+ end
528
+
529
+ ##
530
+ # For comparing Floats. Fails if +exp+ is within +delta+ of +act+.
531
+ #
532
+ # refute_in_delta Math::PI, (22.0 / 7.0)
533
+
534
+ def refute_in_delta exp, act, delta = 0.001, msg = nil
535
+ n = (exp - act).abs
536
+ msg = message(msg) {
537
+ "Expected |#{exp} - #{act}| (#{n}) to not be <= #{delta}"
538
+ }
539
+ refute delta >= n, msg
540
+ end
541
+
542
+ ##
543
+ # For comparing Floats. Fails if +exp+ and +act+ have a relative error
544
+ # less than +epsilon+.
545
+
546
+ def refute_in_epsilon a, b, epsilon = 0.001, msg = nil
547
+ refute_in_delta a, b, a * epsilon, msg
548
+ end
549
+
550
+ ##
551
+ # Fails if +collection+ includes +obj+.
552
+
553
+ def refute_includes collection, obj, msg = nil
554
+ msg = message(msg) {
555
+ "Expected #{mu_pp(collection)} to not include #{mu_pp(obj)}"
556
+ }
557
+ assert_respond_to collection, :include?
558
+ refute collection.include?(obj), msg
559
+ end
560
+
561
+ ##
562
+ # Fails if +obj+ is an instance of +cls+.
563
+
564
+ def refute_instance_of cls, obj, msg = nil
565
+ msg = message(msg) {
566
+ "Expected #{mu_pp(obj)} to not be an instance of #{cls}"
567
+ }
568
+ refute obj.instance_of?(cls), msg
569
+ end
570
+
571
+ ##
572
+ # Fails if +obj+ is a kind of +cls+.
573
+
574
+ def refute_kind_of cls, obj, msg = nil
575
+ msg = message(msg) { "Expected #{mu_pp(obj)} to not be a kind of #{cls}" }
576
+ refute obj.kind_of?(cls), msg
577
+ end
578
+
579
+ ##
580
+ # Fails if +matcher+ <tt>=~</tt> +obj+.
581
+
582
+ def refute_match matcher, obj, msg = nil
583
+ msg = message(msg) {"Expected #{mu_pp matcher} to not match #{mu_pp obj}"}
584
+ assert_respond_to matcher, :"=~"
585
+ matcher = Regexp.new Regexp.escape matcher if String === matcher
586
+ refute matcher =~ obj, msg
587
+ end
588
+
589
+ ##
590
+ # Fails if +obj+ is nil.
591
+
592
+ def refute_nil obj, msg = nil
593
+ msg = message(msg) { "Expected #{mu_pp(obj)} to not be nil" }
594
+ refute obj.nil?, msg
595
+ end
596
+
597
+ ##
598
+ # Fails if +o1+ is not +op+ +o2+. Eg:
599
+ #
600
+ # refute_operator 1, :>, 2 #=> pass
601
+ # refute_operator 1, :<, 2 #=> fail
602
+
603
+ def refute_operator o1, op, o2 = UNDEFINED, msg = nil
604
+ return refute_predicate o1, op, msg if UNDEFINED == o2
605
+ msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op} #{mu_pp(o2)}"}
606
+ refute o1.__send__(op, o2), msg
607
+ end
608
+
609
+ ##
610
+ # For testing with predicates.
611
+ #
612
+ # refute_predicate str, :empty?
613
+ #
614
+ # This is really meant for specs and is front-ended by refute_operator:
615
+ #
616
+ # str.wont_be :empty?
617
+
618
+ def refute_predicate o1, op, msg = nil
619
+ msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op}" }
620
+ refute o1.__send__(op), msg
621
+ end
622
+
623
+ ##
624
+ # Fails if +obj+ responds to the message +meth+.
625
+
626
+ def refute_respond_to obj, meth, msg = nil
627
+ msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" }
628
+
629
+ refute obj.respond_to?(meth), msg
630
+ end
631
+
632
+ ##
633
+ # Fails if +exp+ is the same (by object identity) as +act+.
634
+
635
+ def refute_same exp, act, msg = nil
636
+ msg = message(msg) {
637
+ data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
638
+ "Expected %s (oid=%d) to not be the same as %s (oid=%d)" % data
639
+ }
640
+ refute exp.equal?(act), msg
641
+ end
642
+
643
+ ##
644
+ # Skips the current run. If run in verbose-mode, the skipped run
645
+ # gets listed at the end of the run but doesn't cause a failure
646
+ # exit code.
647
+
648
+ def skip msg = nil, bt = caller
649
+ msg ||= "Skipped, no message given"
650
+ @skip = true
651
+ raise Betatest::Skip, msg, bt
652
+ end
653
+
654
+ ##
655
+ # Was this testcase skipped? Meant for #teardown.
656
+
657
+ def skipped?
658
+ defined?(@skip) and @skip
659
+ end
660
+ end
661
+ end