1# -*- coding: utf-8 -*- 2 3## 4# Minimal Test framework for mruby 5# 6module MTest 7 8 9 ## 10 # Assertion base class 11 12 class Assertion < Exception; end 13 14 ## 15 # Assertion raised when skipping a test 16 17 class Skip < Assertion; end 18 19 module Assertions 20 def mu_pp obj 21 obj.inspect 22 end 23 24 def diff exp, act 25 return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" 26 end 27 28 def _assertions= n 29 @_assertions = n 30 end 31 32 def _assertions 33 @_assertions = 0 unless @_assertions 34 @_assertions 35 end 36 37 ## 38 # Fails unless +test+ is a true value. 39 40 def assert test, msg = nil 41 msg ||= "Failed assertion, no message given." 42 self._assertions += 1 43 unless test 44 msg = msg.call if Proc === msg 45 raise MTest::Assertion, msg 46 end 47 true 48 end 49 50 alias assert_true assert 51 52 ## 53 # Fails unless +test+ is a false value 54 def assert_false test, msg = nil 55 msg = message(msg) { "Expected #{mu_pp(test)} to be false" } 56 assert test == false, msg 57 end 58 59 ## 60 # Fails unless the block returns a true value. 61 62 def assert_block msg = nil 63 msg = message(msg) { "Expected block to return true value" } 64 assert yield, msg 65 end 66 67 ## 68 # Fails unless +obj+ is empty. 69 70 def assert_empty obj, msg = nil 71 msg = message(msg) { "Expected #{mu_pp(obj)} to be empty" } 72 assert_respond_to obj, :empty? 73 assert obj.empty?, msg 74 end 75 76 ## 77 # Fails +obj+ is not empty. 78 79 def assert_not_empty obj, msg = nil 80 msg = message(msg) { "Expected #{mu_pp(obj)} to be not empty" } 81 assert_respond_to obj, :empty? 82 assert !obj.empty?, msg 83 end 84 85 ## 86 # Fails unless <tt>exp == act</tt> printing the difference between 87 # the two, if possible. 88 # 89 # If there is no visible difference but the assertion fails, you 90 # should suspect that your #== is buggy, or your inspect output is 91 # missing crucial details. 92 # 93 # For floats use assert_in_delta. 94 # 95 # See also: MiniTest::Assertions.diff 96 97 def assert_equal exp, act, msg = nil 98 msg = message(msg, "") { diff exp, act } 99 assert(exp == act, msg) 100 end 101 102 ## 103 # Fails exp == act 104 def assert_not_equal exp, act, msg = nil 105 msg = message(msg) { 106 "Expected #{mu_pp(exp)} to be not equal #{mu_pp(act)}" 107 } 108 assert(exp != act, msg) 109 end 110 111 ## 112 # For comparing Floats. Fails unless +exp+ and +act+ are within +delta+ 113 # of each other. 114 # 115 # assert_in_delta Math::PI, (22.0 / 7.0), 0.01 116 117 def assert_in_delta exp, act, delta = 0.001, msg = nil 118 n = (exp - act).abs 119 msg = message(msg) { "Expected #{exp} - #{act} (#{n}) to be < #{delta}" } 120 assert delta >= n, msg 121 end 122 123 ## 124 # For comparing Floats. Fails unless +exp+ and +act+ have a relative 125 # error less than +epsilon+. 126 127 def assert_in_epsilon a, b, epsilon = 0.001, msg = nil 128 assert_in_delta a, b, [a, b].min * epsilon, msg 129 end 130 131 ## 132 # Fails unless +collection+ includes +obj+. 133 134 def assert_include collection, obj, msg = nil 135 msg = message(msg) { 136 "Expected #{mu_pp(collection)} to include #{mu_pp(obj)}" 137 } 138 assert_respond_to collection, :include? 139 assert collection.include?(obj), msg 140 end 141 142 ## 143 # Fails +collection+ includes +obj+ 144 def assert_not_include collection, obj, msg = nil 145 msg = message(msg) { 146 "Expected #{mu_pp(collection)} to not include #{mu_pp(obj)}" 147 } 148 assert_respond_to collection, :include? 149 assert !collection.include?(obj), msg 150 end 151 152 ## 153 # Fails unless +obj+ is an instance of +cls+. 154 155 def assert_instance_of cls, obj, msg = nil 156 msg = message(msg) { 157 "Expected #{mu_pp(obj)} to be an instance of #{cls}, not #{obj.class}" 158 } 159 160 assert obj.instance_of?(cls), msg 161 end 162 163 ## 164 # Fails unless +obj+ is a kind of +cls+. 165 166 def assert_kind_of cls, obj, msg = nil # TODO: merge with instance_of 167 msg = message(msg) { 168 "Expected #{mu_pp(obj)} to be a kind of #{cls}, not #{obj.class}" } 169 170 assert obj.kind_of?(cls), msg 171 end 172 173 ## 174 # Fails unless +exp+ is <tt>=~</tt> +act+. 175 176 def assert_match exp, act, msg = nil 177 if Object.const_defined?(:Regexp) 178 msg = message(msg) { "Expected #{mu_pp(exp)} to match #{mu_pp(act)}" } 179 assert_respond_to act, :"=~" 180 exp = Regexp.new Regexp.escape exp if String === exp and String === act 181 assert exp =~ act, msg 182 else 183 raise MTest::Skip, "assert_match is not defined, because Regexp is not impl." 184 end 185 end 186 187 ## 188 # Fails unless +obj+ is nil 189 190 def assert_nil obj, msg = nil 191 msg = message(msg) { "Expected #{mu_pp(obj)} to be nil" } 192 assert obj.nil?, msg 193 end 194 195 ## 196 # For testing equality operators and so-forth. 197 # 198 # assert_operator 5, :<=, 4 199 200 def assert_operator o1, op, o2, msg = nil 201 msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" } 202 assert o1.__send__(op, o2), msg 203 end 204 205 ## 206 # Fails if stdout or stderr do not output the expected results. 207 # Pass in nil if you don't care about that streams output. Pass in 208 # "" if you require it to be silent. 209 # 210 # See also: #assert_silent 211 212 def assert_output stdout = nil, stderr = nil 213 out, err = capture_io do 214 yield 215 end 216 217 x = assert_equal stdout, out, "In stdout" if stdout 218 y = assert_equal stderr, err, "In stderr" if stderr 219 220 (!stdout || x) && (!stderr || y) 221 end 222 223 ## 224 # Fails unless the block raises one of +exp+ 225 226 def assert_raise *exp 227 msg = "#{exp.pop}\n" if String === exp.last 228 229 begin 230 yield 231 rescue MTest::Skip => e 232 return e if exp.include? MTest::Skip 233 raise e 234 rescue Exception => e 235 excepted = exp.any? do |ex| 236 if ex.instance_of?(Module) 237 e.kind_of?(ex) 238 else 239 e.instance_of?(ex) 240 end 241 end 242 243 assert excepted, exception_details(e, "#{msg}#{mu_pp(exp)} exception expected, not") 244 245 return e 246 end 247 exp = exp.first if exp.size == 1 248 flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised." 249 end 250 251 ## 252 # Fails unless +obj+ responds to +meth+. 253 254 def assert_respond_to obj, meth, msg = nil 255 msg = message(msg, '') { 256 "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}" 257 } 258 assert obj.respond_to?(meth), msg 259 end 260 261 ## 262 # Fails unless +exp+ and +act+ are #equal? 263 264 def assert_same exp, act, msg = nil 265 msg = message(msg) { 266 data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id] 267 "Expected %s (oid=%d) to be the same as %s (oid=%d)" % data 268 } 269 assert exp.equal?(act), msg 270 end 271 272 ## 273 # +send_ary+ is a receiver, message and arguments. 274 # 275 # Fails unless the call returns a true value 276 # TODO: I should prolly remove this from specs 277 278 def assert_send send_ary, m = nil 279 recv, msg, *args = send_ary 280 m = message(m) { 281 "Expected #{mu_pp(recv)}.#{msg}(*#{mu_pp(args)}) to return true" } 282 assert recv.__send__(msg, *args), m 283 end 284 285 ## 286 # Fails if the block outputs anything to stderr or stdout. 287 # 288 # See also: #assert_output 289 290 def assert_silent 291 assert_output "", "" do 292 yield 293 end 294 end 295 296 ## 297 # Fails unless the block throws +sym+ 298 299 def assert_throws sym, msg = nil 300 default = "Expected #{mu_pp(sym)} to have been thrown" 301 caught = true 302 catch(sym) do 303 begin 304 yield 305 rescue ArgumentError => e # 1.9 exception 306 default += ", not #{e.message.split(' ').last}" 307 rescue NameError => e # 1.8 exception 308 default += ", not #{e.name.inspect}" 309 end 310 caught = false 311 end 312 313 assert caught, message(msg) { default } 314 end 315 316 ## 317 # Returns a proc that will output +msg+ along with the default message. 318 319 def message msg = nil, ending = ".", &default 320 Proc.new{ 321 custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? 322 "#{custom_message}#{default.call}#{ending}" 323 } 324 end 325 326 ## 327 # used for counting assertions 328 329 def pass msg = nil 330 assert true 331 end 332 333 ## 334 # Skips the current test. Gets listed at the end of the run but 335 # doesn't cause a failure exit code. 336 337 # disable backtrace for mruby 338 339 def skip msg = nil 340 msg ||= "Skipped, no message given" 341 raise MTest::Skip, msg 342 end 343 344 ## 345 # Returns details for exception +e+ 346 347 # disable backtrace for mruby 348 349 def exception_details e, msg 350 [ 351 "#{msg}", 352 "Class: <#{e.class}>", 353 "Message: <#{e.message.inspect}>", 354# "---Backtrace---", 355# "#{MiniTest::filter_backtrace(e.backtrace).join("\n")}", 356# "---------------", 357 ].join "\n" 358 end 359 360 ## 361 # Fails with +msg+ 362 363 def flunk msg = nil 364 msg ||= "Epic Fail!" 365 assert false, msg 366 end 367 368 end 369 370 class Unit 371 attr_accessor :report, :failures, :errors, :skips 372 attr_accessor :test_count, :assertion_count 373 attr_accessor :start_time 374 attr_accessor :help 375 attr_accessor :verbose 376 attr_writer :options 377 378 def options 379 @options ||= {} 380 end 381 382 @@out = $stdout 383 @@runner = nil 384 385 def self.output 386 @@out 387 end 388 389 def self.output= stream 390 @@out = stream 391 end 392 393 def self.runnner= runner 394 @@runner = runnner 395 end 396 397 def self.runner 398 @@runner = self.new unless @@runner 399 @@runner 400 end 401 402 def output 403 self.class.output 404 end 405 406 def puts *a 407 output.puts(*a) 408 end 409 410 def print *a 411 output.print(*a) 412 end 413 414 def puke klass, meth, e 415 e = case e 416 when MTest::Skip 417 @skips += 1 418 "Skipped:\n#{meth}(#{klass}) #{e.inspect}\n" 419 when MTest::Assertion 420 @failures += 1 421 "Failure:\n#{meth}(#{klass}) #{e.inspect}\n" 422 else 423 @errors += 1 424 "Error:\n#{meth}(#{klass}): #{e.class}, #{e.inspect}\n" 425 end 426 @report << e 427 e[0, 1] 428 end 429 430 def initialize 431 @report = [] 432 @errors = @failures = @skips = 0 433 @verbose = false 434 end 435 436 def run args = [] 437 self.class.runner._run(args) 438 end 439 440 def mrbtest 441 suites = TestCase.send "test_suites" 442 return if suites.empty? 443 444 @test_cound, @assertion_count = 0, 0 445 446 results = _run_suites suites 447 448 @test_count = results.map{ |r| r[0] }.inject(0) { |sum, tc| sum + tc } 449 @assertion_count = results.map{ |r| r[1] }.inject(0) { |sum, ac| sum + ac } 450 451 $ok_test += (test_count.to_i - failures.to_i - errors.to_i - skips.to_i) 452 $ko_test += failures.to_i 453 $kill_test += errors.to_i 454 report.each_with_index do |msg, i| 455 $asserts << "MTest #{i+1}) #{msg}" 456 end 457 458 TestCase.reset 459 end 460 461 def _run args = [] 462 _run_tests 463 @test_count ||= 0 464 @test_count > 0 ? failures + errors : nil 465 end 466 467 def _run_tests 468 suites = TestCase.send "test_suites" 469 return if suites.empty? 470 471 start = Time.now 472 473 puts 474 puts "# Running tests:" 475 puts 476 477 @test_count, @assertion_count = 0, 0 478 479 results = _run_suites suites 480 481 @test_count = results.map{ |r| r[0] }.inject(0) { |sum, tc| sum + tc } 482 @assertion_count = results.map{ |r| r[1] }.inject(0) { |sum, ac| sum + ac } 483 484 t = Time.now - start 485 486 puts 487 puts 488 puts sprintf("Finished tests in %.6fs, %.4f tests/s, %.4f assertions/s.", 489 t, test_count / t, assertion_count / t) 490 491 report.each_with_index do |msg, i| 492 puts sprintf("\n%3d) %s", i+1, msg) 493 end 494 495 puts 496 497 status 498 end 499 500 def _run_suites suites 501 suites.map { |suite| _run_suite suite } 502 end 503 504 def _run_suite suite 505 header = "test_suite_header" 506 puts send(header, suite) if respond_to? header 507 508 assertions = suite.send("test_methods").map do |method| 509 inst = suite.new method 510 inst._assertions = 0 511 512 print "#{suite}##{method} = " if @verbose 513 514 @start_time = Time.now 515 result = inst.run self 516 time = Time.now - @start_time 517 518 print sprintf("%.2f s = ", time) if @verbose 519 print result 520 puts if @verbose 521 522 inst._assertions 523 end 524 525 return assertions.size, assertions.inject(0) { |sum, n| sum + n } 526 end 527 528 def status io = self.output 529 format = "%d tests, %d assertions, %d failures, %d errors, %d skips" 530 io.puts sprintf(format, test_count, assertion_count, failures, errors, skips) 531 end 532 533 class TestCase 534 attr_reader :__name__ 535 536 @@test_suites = {} 537 538 def run runner 539 result = "" 540 begin 541 @passed = nil 542 self.setup 543 self.run_setup_hooks 544 self.__send__ self.__name__ 545 result = "." unless io? 546 @passed = true 547 rescue Exception => e 548 @passed = false 549 result = runner.puke self.class, self.__name__, e 550 ensure 551 begin 552 self.run_teardown_hooks 553 self.teardown 554 rescue Exception => e 555 result = runner.puke self.class, self.__name__, e 556 end 557 end 558 result 559 end 560 561 def initialize name = self.to_s 562 @__name__ = name 563 @__io__ = nil 564 @passed = nil 565 end 566 567 def io 568 @__io__ = true 569 MTest::Unit.output 570 end 571 572 def io? 573 @__io__ 574 end 575 576 def self.reset 577 @@test_suites = {} 578 end 579 580 reset 581 582 def self.inherited klass 583 @@test_suites[klass] = true 584 klass.reset_setup_teardown_hooks 585 end 586 587 def self.test_order 588 :random 589 end 590 591 def self.test_suites 592 hash = {} 593 @@test_suites.keys.each{ |ts| hash[ts.to_s] = ts } 594 hash.keys.sort.map{ |key| hash[key] } 595 end 596 597 def self.test_methods # :nodoc: 598 methods = [] 599 self.new.methods(true).each do |m| 600 methods << m.to_s if m.to_s.index('test') == 0 601 end 602 603 case self.test_order 604 when :random then 605 max = methods.size 606 # TODO: methods.sort.sort_by { rand max } 607 methods 608 when :alpha, :sorted then 609 methods.sort 610 else 611 raise "Unknown test_order: #{self.test_order.inspect}" 612 end 613 end 614 615 616 def passed? 617 @passed 618 end 619 620 def setup; end 621 def teardown; end 622 def self.reset_setup_teardown_hooks 623 @setup_hooks = [] 624 @teardown_hooks = [] 625 end 626 reset_setup_teardown_hooks 627 628 def self.add_setup_hook arg=nil, &block 629 hook = arg || block 630 @setup_hooks << hook 631 end 632 633 def self.setup_hooks # :nodoc: 634 if superclass.respond_to? :setup_hooks then 635 superclass.setup_hooks 636 else 637 [] 638 end + @setup_hooks 639 end 640 641 def run_setup_hooks # :nodoc: 642 self.class.setup_hooks.each do |hook| 643 if hook.respond_to?(:arity) && hook.arity == 1 644 hook.call(self) 645 else 646 hook.call 647 end 648 end 649 end 650 651 def self.add_teardown_hook arg=nil, &block 652 hook = arg || block 653 @teardown_hooks << hook 654 end 655 656 def self.teardown_hooks # :nodoc: 657 if superclass.respond_to? :teardown_hooks then 658 superclass.teardown_hooks 659 else 660 [] 661 end + @teardown_hooks 662 end 663 664 def run_teardown_hooks # :nodoc: 665 self.class.teardown_hooks.reverse.each do |hook| 666 if hook.respond_to?(:arity) && hook.arity == 1 667 hook.call(self) 668 else 669 hook.call 670 end 671 end 672 end 673 674 675 include MTest::Assertions 676 end 677 end 678end 679 680if __FILE__ == $0 681 class Test4MTest < MTest::Unit::TestCase 682 def setup 683 puts '*setup' 684 end 685 686 def teardown 687 puts '*teardown' 688 end 689 690 def test_sample 691 puts '*test_sample' 692 assert(true, 'true sample test') 693 assert(true) 694 end 695 end 696 697 MTest::Unit.new.run 698end 699