oktest 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/README.md +287 -51
- data/Rakefile.rb +1 -1
- data/lib/oktest.rb +393 -19
- data/oktest.gemspec +2 -2
- data/test/assertion_test.rb +70 -5
- data/test/filter_test.rb +2 -2
- data/test/fixture_test.rb +1 -1
- data/test/generator_test.rb +1 -1
- data/test/helper_test.rb +1 -1
- data/test/initialize.rb +8 -1
- data/test/mainapp_test.rb +42 -5
- data/test/matcher_test.rb +424 -0
- data/test/misc_test.rb +1 -1
- data/test/node_test.rb +34 -1
- data/test/reporter_test.rb +34 -6
- data/test/runner_test.rb +90 -9
- data/test/util_test.rb +1 -1
- data/test/visitor_test.rb +1 -1
- metadata +3 -2
    
        data/Rakefile.rb
    CHANGED
    
    
    
        data/lib/oktest.rb
    CHANGED
    
    | @@ -1,16 +1,18 @@ | |
| 1 1 | 
             
            # -*- coding: utf-8 -*-
         | 
| 2 2 |  | 
| 3 3 | 
             
            ###
         | 
| 4 | 
            -
            ### $Release: 1. | 
| 4 | 
            +
            ### $Release: 1.1.1 $
         | 
| 5 5 | 
             
            ### $Copyright: copyright(c) 2011-2021 kuwata-lab.com all rights reserved $
         | 
| 6 6 | 
             
            ### $License: MIT License $
         | 
| 7 7 | 
             
            ###
         | 
| 8 8 |  | 
| 9 | 
            +
            require 'set'
         | 
| 10 | 
            +
             | 
| 9 11 |  | 
| 10 12 | 
             
            module Oktest
         | 
| 11 13 |  | 
| 12 14 |  | 
| 13 | 
            -
              VERSION = '$Release: 1. | 
| 15 | 
            +
              VERSION = '$Release: 1.1.1 $'.split()[1]
         | 
| 14 16 |  | 
| 15 17 |  | 
| 16 18 | 
             
              class OktestError < StandardError
         | 
| @@ -114,6 +116,10 @@ module Oktest | |
| 114 116 |  | 
| 115 117 | 
             
                def ===(expected)
         | 
| 116 118 | 
             
                  __done()
         | 
| 119 | 
            +
                  #; [!mjh4d] raises error when combination of 'not_ok()' and matcher object.
         | 
| 120 | 
            +
                  if @bool == false && @actual.is_a?(Matcher)
         | 
| 121 | 
            +
                    raise OktestError, "negative `===` is not available with matcher object."
         | 
| 122 | 
            +
                  end
         | 
| 117 123 | 
             
                  #; [!42f6a] raises assertion error when failed.
         | 
| 118 124 | 
             
                  #; [!vhvyu] is avaialbe with NOT.
         | 
| 119 125 | 
             
                  __assert(@bool == (@actual === expected)) {
         | 
| @@ -264,10 +270,10 @@ module Oktest | |
| 264 270 |  | 
| 265 271 | 
             
                def raise!(errcls=nil, errmsg=nil, &b)
         | 
| 266 272 | 
             
                  #; [!8k6ee] compares error class by '.is_a?' instead of '=='.
         | 
| 267 | 
            -
                  return raise?(errcls, errmsg,  | 
| 273 | 
            +
                  return raise?(errcls, errmsg, _subclass: true, &b)
         | 
| 268 274 | 
             
                end
         | 
| 269 275 |  | 
| 270 | 
            -
                def raise?(errcls=nil, errmsg=nil,  | 
| 276 | 
            +
                def raise?(errcls=nil, errmsg=nil, _subclass: false, &b)
         | 
| 271 277 | 
             
                  __done()
         | 
| 272 278 | 
             
                  #; [!2rnni] 1st argument can be error message string or rexp.
         | 
| 273 279 | 
             
                  if errmsg.nil? && ! errcls.nil? && ! (errcls.is_a?(Class) && errcls <= Exception)
         | 
| @@ -289,8 +295,8 @@ module Oktest | |
| 289 295 | 
             
                      #; [!lq6jv] compares error class with '==' operator, not '.is_a?'.
         | 
| 290 296 | 
             
                      elsif exc.class == errcls    # not `exc.is_a?(errcls)`
         | 
| 291 297 | 
             
                        nil
         | 
| 292 | 
            -
                      #; [!hwg0z] compares error class with '.is_a?' if ' | 
| 293 | 
            -
                      elsif  | 
| 298 | 
            +
                      #; [!hwg0z] compares error class with '.is_a?' if '_subclass: true' specified.
         | 
| 299 | 
            +
                      elsif _subclass && exc.class < errcls
         | 
| 294 300 | 
             
                        nil
         | 
| 295 301 | 
             
                      #; [!4n3ed] reraises if exception is not matched to specified error class.
         | 
| 296 302 | 
             
                      else
         | 
| @@ -328,8 +334,8 @@ module Oktest | |
| 328 334 | 
             
                      #; [!smprc] compares error class with '==' operator, not '.is_a?'.
         | 
| 329 335 | 
             
                      elsif exc.class == errcls    # not `exc.is_a?(errcls)`
         | 
| 330 336 | 
             
                        __assert(false) { "#{errcls.inspect} should not be raised but got #{exc.inspect}." }
         | 
| 331 | 
            -
                      #; [!34nd8] compares error class with '.is_a?' if ' | 
| 332 | 
            -
                      elsif  | 
| 337 | 
            +
                      #; [!34nd8] compares error class with '.is_a?' if '_subclass: true' specified.
         | 
| 338 | 
            +
                      elsif _subclass && exc.class < errcls
         | 
| 333 339 | 
             
                        __assert(false) { "#{errcls.inspect} should not be raised but got #{exc.inspect}." }
         | 
| 334 340 | 
             
                      #; [!shxne] reraises exception if different from specified error class.
         | 
| 335 341 | 
             
                      else
         | 
| @@ -548,6 +554,246 @@ module Oktest | |
| 548 554 | 
             
              end
         | 
| 549 555 |  | 
| 550 556 |  | 
| 557 | 
            +
              class Matcher
         | 
| 558 | 
            +
             | 
| 559 | 
            +
                def initialize(actual)
         | 
| 560 | 
            +
                  @actual = actual
         | 
| 561 | 
            +
                end
         | 
| 562 | 
            +
             | 
| 563 | 
            +
                def ===(expected)
         | 
| 564 | 
            +
                  #; [!spybn] raises NotImplementedError.
         | 
| 565 | 
            +
                  raise NotImplementedError.new("#{self.class.name}#===(): not implemented yet.")
         | 
| 566 | 
            +
                end
         | 
| 567 | 
            +
             | 
| 568 | 
            +
                def ==(expected)
         | 
| 569 | 
            +
                  #; [!ymt1b] raises OktestError.
         | 
| 570 | 
            +
                  raise OktestError, "JSON(): use `===` instead of `==`."
         | 
| 571 | 
            +
                end
         | 
| 572 | 
            +
             | 
| 573 | 
            +
                def fail(errmsg)
         | 
| 574 | 
            +
                  #; [!8qpsd] raises assertion error.
         | 
| 575 | 
            +
                  raise Oktest::FAIL_EXCEPTION, errmsg
         | 
| 576 | 
            +
                end
         | 
| 577 | 
            +
             | 
| 578 | 
            +
              end
         | 
| 579 | 
            +
             | 
| 580 | 
            +
             | 
| 581 | 
            +
              class JsonMatcher < Matcher
         | 
| 582 | 
            +
             | 
| 583 | 
            +
                def ===(expected)
         | 
| 584 | 
            +
                  #; [!4uf1o] raises assertion error when JSON not matched.
         | 
| 585 | 
            +
                  _compare([], @actual, expected)
         | 
| 586 | 
            +
                  #; [!0g0u4] returns true when JSON matched.
         | 
| 587 | 
            +
                  return true
         | 
| 588 | 
            +
                end
         | 
| 589 | 
            +
             | 
| 590 | 
            +
                private
         | 
| 591 | 
            +
             | 
| 592 | 
            +
                def _compare?(path, a, e)
         | 
| 593 | 
            +
                  #; [!nkvqo] returns true when nothing raised.
         | 
| 594 | 
            +
                  #; [!57m2j] returns false when assertion error raised.
         | 
| 595 | 
            +
                  _compare(path, a, e)
         | 
| 596 | 
            +
                  return true
         | 
| 597 | 
            +
                rescue FAIL_EXCEPTION
         | 
| 598 | 
            +
                  return false
         | 
| 599 | 
            +
                end
         | 
| 600 | 
            +
             | 
| 601 | 
            +
                def _compare(path, a, e)
         | 
| 602 | 
            +
                  if a.is_a?(Hash) && e.is_a?(Hash)
         | 
| 603 | 
            +
                    _compare_hash(path, a, e)
         | 
| 604 | 
            +
                  elsif a.is_a?(Array) && e.is_a?(Array)
         | 
| 605 | 
            +
                    _compare_array(path, a, e)
         | 
| 606 | 
            +
                  elsif e.is_a?(Enumerator)
         | 
| 607 | 
            +
                    _compare_enumerator(path, a, e)
         | 
| 608 | 
            +
                  elsif e.is_a?(OR)
         | 
| 609 | 
            +
                    _compare_or(path, a, e)
         | 
| 610 | 
            +
                  elsif e.is_a?(AND)
         | 
| 611 | 
            +
                    _compare_and(path, a, e)
         | 
| 612 | 
            +
                  else
         | 
| 613 | 
            +
                    _compare_value(path, a, e)
         | 
| 614 | 
            +
                  end
         | 
| 615 | 
            +
                end
         | 
| 616 | 
            +
             | 
| 617 | 
            +
                def _compare_value(path, a, e)
         | 
| 618 | 
            +
                  #; [!1ukbv] scalar value matches to integer, string, bool, and so son.
         | 
| 619 | 
            +
                  #; [!8o55d] class object matches to instance object.
         | 
| 620 | 
            +
                  #; [!s625d] regexp object matches to string value.
         | 
| 621 | 
            +
                  #; [!aqkk0] range object matches to scalar value.
         | 
| 622 | 
            +
                  #; [!a7bfs] Set object matches to enum value.
         | 
| 623 | 
            +
                  e === a  or fail <<"END"
         | 
| 624 | 
            +
            $<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
         | 
| 625 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 626 | 
            +
                $<expected>: #{e.inspect}
         | 
| 627 | 
            +
            END
         | 
| 628 | 
            +
                  #; [!4ymj2] fails when actual value is not matched to item class of range object.
         | 
| 629 | 
            +
                  if e.is_a?(Range)
         | 
| 630 | 
            +
                    expected_class = (e.begin || e.end).class
         | 
| 631 | 
            +
                    a.is_a?(expected_class)  or fail <<"END"
         | 
| 632 | 
            +
            $<JSON>#{_path(path)}: expected #{expected_class.name} value, but got #{a.class.name} value.
         | 
| 633 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 634 | 
            +
                $<expected>: #{e.inspect}
         | 
| 635 | 
            +
            END
         | 
| 636 | 
            +
                  end
         | 
| 637 | 
            +
                end
         | 
| 638 | 
            +
             | 
| 639 | 
            +
                def _compare_array(path, a, e)
         | 
| 640 | 
            +
                  #; [!bz74w] fails when array lengths are different.
         | 
| 641 | 
            +
                  a.length == e.length  or fail <<"END"
         | 
| 642 | 
            +
            $<JSON>#{_path(path)}: $<actual>.length == $<expected>.length : failed.
         | 
| 643 | 
            +
                $<actual>.length:   #{a.length}
         | 
| 644 | 
            +
                $<expected>.length: #{e.length}
         | 
| 645 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 646 | 
            +
                $<expected>: #{e.inspect}
         | 
| 647 | 
            +
            END
         | 
| 648 | 
            +
                  #; [!lh6d6] compares array items recursively.
         | 
| 649 | 
            +
                  path.push(nil)
         | 
| 650 | 
            +
                  i = -1
         | 
| 651 | 
            +
                  a.zip(e) do |a2, e2|
         | 
| 652 | 
            +
                    path[-1] = (i += 1)
         | 
| 653 | 
            +
                    _compare(path, a2, e2)
         | 
| 654 | 
            +
                  end
         | 
| 655 | 
            +
                  path.pop()
         | 
| 656 | 
            +
                end
         | 
| 657 | 
            +
             | 
| 658 | 
            +
                def _compare_hash(path, a, e)
         | 
| 659 | 
            +
                  #; [!rkv0z] compares two hashes with converting keys into string.
         | 
| 660 | 
            +
                  a2 = {}; a.each {|k, v| a2[k.to_s] = v }
         | 
| 661 | 
            +
                  e2 = {}; e.each {|k, v| e2[k.to_s] = v }
         | 
| 662 | 
            +
                  #; [!fmxyg] compares hash objects recursively.
         | 
| 663 | 
            +
                  path.push(nil)
         | 
| 664 | 
            +
                  a2.each_key do |k|
         | 
| 665 | 
            +
                    path[-1] = k
         | 
| 666 | 
            +
                    if e2.key?(k)
         | 
| 667 | 
            +
                      _compare(path, a2[k], e2[k])
         | 
| 668 | 
            +
                    #; [!jbyv6] key 'aaa?' represents optional key.
         | 
| 669 | 
            +
                    elsif e2.key?("#{k}?")
         | 
| 670 | 
            +
                      _compare(path, a2[k], e2["#{k}?"]) unless a2[k].nil?
         | 
| 671 | 
            +
                    #; [!uc4ag] key '*' matches to any key name.
         | 
| 672 | 
            +
                    elsif e2.key?("*")
         | 
| 673 | 
            +
                      _compare(path, a2[k], e2["*"])
         | 
| 674 | 
            +
                    #; [!mpbvu] fails when unexpected key exists in actual hash.
         | 
| 675 | 
            +
                    else
         | 
| 676 | 
            +
                      fail <<"END"
         | 
| 677 | 
            +
            $<JSON>#{_path(path)}: unexpected key.
         | 
| 678 | 
            +
                $<actual>:   #{a2[k].inspect}
         | 
| 679 | 
            +
            END
         | 
| 680 | 
            +
                    end
         | 
| 681 | 
            +
                  end
         | 
| 682 | 
            +
                  path.pop()
         | 
| 683 | 
            +
                  #; [!4oasq] fails when expected key not exist in actual hash.
         | 
| 684 | 
            +
                  (e2.keys - a2.keys).each do |k|
         | 
| 685 | 
            +
                    k =~ /\?\z/ || k == "*"  or fail <<"END"
         | 
| 686 | 
            +
            $<JSON>#{_path(path)}: key \"#{k}\" expected but not found.
         | 
| 687 | 
            +
                $<actual>.keys:   #{a2.keys.sort.inspect[1...-1]}
         | 
| 688 | 
            +
                $<expected>.keys: #{e2.keys.sort.inspect[1...-1]}
         | 
| 689 | 
            +
            END
         | 
| 690 | 
            +
                  end
         | 
| 691 | 
            +
                end
         | 
| 692 | 
            +
             | 
| 693 | 
            +
                def _compare_enumerator(path, a, e)
         | 
| 694 | 
            +
                  #; [!ljrmc] fails when expected is an Enumerator object and actual is not an array.
         | 
| 695 | 
            +
                  e2 = e.first
         | 
| 696 | 
            +
                  a.is_a?(Array)  or fail <<"END"
         | 
| 697 | 
            +
            $<JSON>#{_path(path)}: Array value expected but got #{a.class.name} value.
         | 
| 698 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 699 | 
            +
                $<expected>: [#{e2.inspect}].each
         | 
| 700 | 
            +
            END
         | 
| 701 | 
            +
                  #; [!sh5cg] Enumerator object matches to repeat of rule.
         | 
| 702 | 
            +
                  path.push(nil)
         | 
| 703 | 
            +
                  a.each_with_index do |a2, i|
         | 
| 704 | 
            +
                    path[-1] = i
         | 
| 705 | 
            +
                    _compare(path, a2, e2)
         | 
| 706 | 
            +
                  end
         | 
| 707 | 
            +
                  path.pop()
         | 
| 708 | 
            +
                end
         | 
| 709 | 
            +
             | 
| 710 | 
            +
                def _compare_or(path, a, e)
         | 
| 711 | 
            +
                  #; [!eqr3b] `OR()` matches to any of arguments.
         | 
| 712 | 
            +
                  #; [!5ybfg] `OR()` can contain `AND()`.
         | 
| 713 | 
            +
                  passed = e.items.any? {|e2| _compare?(path, a, e2) }
         | 
| 714 | 
            +
                  passed  or fail <<"END"
         | 
| 715 | 
            +
            $<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
         | 
| 716 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 717 | 
            +
                $<expected>: OR(#{e.items.collect(&:inspect).join(', ')})
         | 
| 718 | 
            +
            END
         | 
| 719 | 
            +
                end
         | 
| 720 | 
            +
             | 
| 721 | 
            +
                def _compare_and(path, a, e)
         | 
| 722 | 
            +
                  #; [!4hk96] `AND()` matches to all of arguments.
         | 
| 723 | 
            +
                  #; [!scx22] `AND()` can contain `OR()`.
         | 
| 724 | 
            +
                  failed = e.items.find {|e2| ! _compare?(path, a, e2) }
         | 
| 725 | 
            +
                  ! failed  or fail <<"END"
         | 
| 726 | 
            +
            $<JSON>#{_path(path)}: $<expected> === $<actual> : failed.
         | 
| 727 | 
            +
                $<actual>:   #{a.inspect}
         | 
| 728 | 
            +
                $<expected>: AND(#{failed.inspect})
         | 
| 729 | 
            +
            END
         | 
| 730 | 
            +
                end
         | 
| 731 | 
            +
             | 
| 732 | 
            +
                def _path(path)
         | 
| 733 | 
            +
                  #return path.collect {|x| "/#{x}" }.join()
         | 
| 734 | 
            +
                  return path.collect {|x| "[#{x.inspect}]" }.join()
         | 
| 735 | 
            +
                end
         | 
| 736 | 
            +
             | 
| 737 | 
            +
                protected
         | 
| 738 | 
            +
             | 
| 739 | 
            +
                class OR
         | 
| 740 | 
            +
                  def initialize(*items)
         | 
| 741 | 
            +
                    @items = items
         | 
| 742 | 
            +
                  end
         | 
| 743 | 
            +
                  attr_reader :items
         | 
| 744 | 
            +
                  def inspect()
         | 
| 745 | 
            +
                    #; [!2mu33] returns 'OR(...)' string.
         | 
| 746 | 
            +
                    return "OR(#{@items.collect(&:inspect).join(', ')})"
         | 
| 747 | 
            +
                  end
         | 
| 748 | 
            +
                end
         | 
| 749 | 
            +
             | 
| 750 | 
            +
                class AND
         | 
| 751 | 
            +
                  def initialize(*items)
         | 
| 752 | 
            +
                    @items = items
         | 
| 753 | 
            +
                  end
         | 
| 754 | 
            +
                  attr_reader :items
         | 
| 755 | 
            +
                  def inspect()
         | 
| 756 | 
            +
                    #; [!w43ag] returns 'AND(...)' string.
         | 
| 757 | 
            +
                    return "AND(#{@items.collect(&:inspect).join(', ')})"
         | 
| 758 | 
            +
                  end
         | 
| 759 | 
            +
                end
         | 
| 760 | 
            +
             | 
| 761 | 
            +
                class Enum < Set
         | 
| 762 | 
            +
                  alias === include?     # Ruby 2.4 or older doesn't have 'Set#==='.
         | 
| 763 | 
            +
                  def inspect()
         | 
| 764 | 
            +
                    #; [!fam11] returns 'Enum(...)' string.
         | 
| 765 | 
            +
                    return "Enum(#{self.collect(&:inspect).join(', ')})"
         | 
| 766 | 
            +
                  end
         | 
| 767 | 
            +
                end
         | 
| 768 | 
            +
             | 
| 769 | 
            +
                class Length
         | 
| 770 | 
            +
                  def initialize(expected)
         | 
| 771 | 
            +
                    @expected = expected
         | 
| 772 | 
            +
                  end
         | 
| 773 | 
            +
                  def ===(actual)
         | 
| 774 | 
            +
                    #; [!03ozi] compares length of actual value with expected value.
         | 
| 775 | 
            +
                    return @expected === actual.length
         | 
| 776 | 
            +
                  end
         | 
| 777 | 
            +
                  def inspect()
         | 
| 778 | 
            +
                    #; [!nwv3e] returns 'Length(n)' string.
         | 
| 779 | 
            +
                    return "Length(#{@expected.inspect})"
         | 
| 780 | 
            +
                  end
         | 
| 781 | 
            +
                end
         | 
| 782 | 
            +
             | 
| 783 | 
            +
                class Any
         | 
| 784 | 
            +
                  def ===(actual)
         | 
| 785 | 
            +
                    #; [!mzion] returns true in any case.
         | 
| 786 | 
            +
                    true
         | 
| 787 | 
            +
                  end
         | 
| 788 | 
            +
                  def inspect()
         | 
| 789 | 
            +
                    #; [!6f0yv] returns 'Any()' string.
         | 
| 790 | 
            +
                    return "Any()"
         | 
| 791 | 
            +
                  end
         | 
| 792 | 
            +
                end
         | 
| 793 | 
            +
             | 
| 794 | 
            +
              end
         | 
| 795 | 
            +
             | 
| 796 | 
            +
             | 
| 551 797 | 
             
              class Context
         | 
| 552 798 | 
             
                ## * Context class is separated from ScopeNode, TopicNode, and SpecLeaf.
         | 
| 553 799 | 
             
                ## * `topic()` and `spec()` creates subclass of Context class,
         | 
| @@ -891,11 +1137,20 @@ module Oktest | |
| 891 1137 | 
             
              end
         | 
| 892 1138 |  | 
| 893 1139 | 
             
              def self.scope(tag: nil, &block)
         | 
| 894 | 
            -
                #; [! | 
| 895 | 
            -
                #; [!rsimc] adds scope object as child of THE_GLOBAL_SCOPE.
         | 
| 1140 | 
            +
                #; [!kem4y] detects test script filename.
         | 
| 896 1141 | 
             
                location = caller(1).first  # caller() makes performance slower, but necessary.
         | 
| 897 1142 | 
             
                filename = location =~ /:\d+/ ? $` : nil
         | 
| 898 | 
            -
                filename  | 
| 1143 | 
            +
                #; [!6ullm] changes test script filename from absolute path to relative path.
         | 
| 1144 | 
            +
                if filename
         | 
| 1145 | 
            +
                  pwd = Dir.pwd()
         | 
| 1146 | 
            +
                  if filename.start_with?(pwd)
         | 
| 1147 | 
            +
                    filename = filename[pwd.length..-1].sub(/\A\//, '')
         | 
| 1148 | 
            +
                  elsif filename.start_with?('./')
         | 
| 1149 | 
            +
                    filename = filename[2..-1]
         | 
| 1150 | 
            +
                  end
         | 
| 1151 | 
            +
                end
         | 
| 1152 | 
            +
                #; [!vxoy1] creates new scope object.
         | 
| 1153 | 
            +
                #; [!rsimc] adds scope object as child of THE_GLOBAL_SCOPE.
         | 
| 899 1154 | 
             
                scope = ScopeNode.new(THE_GLOBAL_SCOPE, filename, tag: tag)
         | 
| 900 1155 | 
             
                #; [!jmc4q] raises error when nested called.
         | 
| 901 1156 | 
             
                self.__scope(scope, &block)
         | 
| @@ -1107,6 +1362,41 @@ module Oktest | |
| 1107 1362 | 
             
                  return Benry::Recorder.new
         | 
| 1108 1363 | 
             
                end
         | 
| 1109 1364 |  | 
| 1365 | 
            +
                def JSON(actual)
         | 
| 1366 | 
            +
                  #; [!n0k03] creates JsonMatcher object.
         | 
| 1367 | 
            +
                  return JsonMatcher.new(actual)
         | 
| 1368 | 
            +
                end
         | 
| 1369 | 
            +
             | 
| 1370 | 
            +
                def Enum(*values)
         | 
| 1371 | 
            +
                  #; [!fbfr0] creates Enum object which is a subclass of Set.
         | 
| 1372 | 
            +
                  return JsonMatcher::Enum.new(values)
         | 
| 1373 | 
            +
                end
         | 
| 1374 | 
            +
             | 
| 1375 | 
            +
                def Bool()
         | 
| 1376 | 
            +
                  #; [!vub5j] creates a set of true and false.
         | 
| 1377 | 
            +
                  return Enum(true, false)
         | 
| 1378 | 
            +
                end
         | 
| 1379 | 
            +
             | 
| 1380 | 
            +
                def OR(*args)
         | 
| 1381 | 
            +
                  #; [!9e8im] creates `OR` object.
         | 
| 1382 | 
            +
                  return JsonMatcher::OR.new(*args)
         | 
| 1383 | 
            +
                end
         | 
| 1384 | 
            +
             | 
| 1385 | 
            +
                def AND(*args)
         | 
| 1386 | 
            +
                  #; [!38jln] creates `AND` object.
         | 
| 1387 | 
            +
                  return JsonMatcher::AND.new(*args)
         | 
| 1388 | 
            +
                end
         | 
| 1389 | 
            +
             | 
| 1390 | 
            +
                def Length(n)
         | 
| 1391 | 
            +
                  #; [!qqas3] creates Length object.
         | 
| 1392 | 
            +
                  return JsonMatcher::Length.new(n)
         | 
| 1393 | 
            +
                end
         | 
| 1394 | 
            +
             | 
| 1395 | 
            +
                def Any()
         | 
| 1396 | 
            +
                  #; [!dlo1o] creates an 'Any' object.
         | 
| 1397 | 
            +
                  return JsonMatcher::Any.new
         | 
| 1398 | 
            +
                end
         | 
| 1399 | 
            +
             | 
| 1110 1400 | 
             
              end
         | 
| 1111 1401 |  | 
| 1112 1402 |  | 
| @@ -1206,11 +1496,25 @@ module Oktest | |
| 1206 1496 | 
             
                  @reporter.exit_all(self)
         | 
| 1207 1497 | 
             
                end
         | 
| 1208 1498 |  | 
| 1499 | 
            +
                def _spec_first(node)
         | 
| 1500 | 
            +
                  a1, a2 = node.each_child.partition {|c|
         | 
| 1501 | 
            +
                    c.is_a?(SpecLeaf) || (c.is_a?(TopicNode) && c._prefix != '*')
         | 
| 1502 | 
            +
                  }
         | 
| 1503 | 
            +
                  return a1+a2
         | 
| 1504 | 
            +
                end
         | 
| 1505 | 
            +
                private :_spec_first
         | 
| 1506 | 
            +
             | 
| 1209 1507 | 
             
                def visit_scope(scope, depth, parent)
         | 
| 1210 1508 | 
             
                  @reporter.enter_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
         | 
| 1211 1509 | 
             
                  #; [!5anr7] calls before_all and after_all blocks.
         | 
| 1212 1510 | 
             
                  call_before_all_block(scope)
         | 
| 1213 | 
            -
                   | 
| 1511 | 
            +
                  #; [!c5cw0] run specs and case_when in advance of specs and topics when SimpleReporter.
         | 
| 1512 | 
            +
                  if @reporter.order_policy() == :spec_first
         | 
| 1513 | 
            +
                    _spec_first(scope).each {|c| c.accept_visitor(self, depth+1, scope) }
         | 
| 1514 | 
            +
                  else
         | 
| 1515 | 
            +
                    scope.each_child {|c| c.accept_visitor(self, depth+1, scope) }
         | 
| 1516 | 
            +
                  end
         | 
| 1517 | 
            +
                  #
         | 
| 1214 1518 | 
             
                  call_after_all_block(scope)
         | 
| 1215 1519 | 
             
                  @reporter.exit_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
         | 
| 1216 1520 | 
             
                end
         | 
| @@ -1219,7 +1523,13 @@ module Oktest | |
| 1219 1523 | 
             
                  @reporter.enter_topic(topic, depth)
         | 
| 1220 1524 | 
             
                  #; [!i3yfv] calls 'before_all' and 'after_all' blocks.
         | 
| 1221 1525 | 
             
                  call_before_all_block(topic)
         | 
| 1222 | 
            -
                   | 
| 1526 | 
            +
                  #; [!p3a5o] run specs and case_when in advance of specs and topics when SimpleReporter.
         | 
| 1527 | 
            +
                  if @reporter.order_policy() == :spec_first
         | 
| 1528 | 
            +
                    _spec_first(topic).each {|c| c.accept_visitor(self, depth+1, topic) }
         | 
| 1529 | 
            +
                  else
         | 
| 1530 | 
            +
                    topic.each_child {|c| c.accept_visitor(self, depth+1, topic) }
         | 
| 1531 | 
            +
                  end
         | 
| 1532 | 
            +
                  #
         | 
| 1223 1533 | 
             
                  call_after_all_block(topic)
         | 
| 1224 1534 | 
             
                  @reporter.exit_topic(topic, depth)
         | 
| 1225 1535 | 
             
                end
         | 
| @@ -1420,6 +1730,7 @@ module Oktest | |
| 1420 1730 | 
             
                def exit_spec(spec, depth, status, error, parent); end
         | 
| 1421 1731 | 
             
                #
         | 
| 1422 1732 | 
             
                def counts; {}; end
         | 
| 1733 | 
            +
                def order_policy(); nil; end     # :spec_first or nil
         | 
| 1423 1734 |  | 
| 1424 1735 | 
             
              end
         | 
| 1425 1736 |  | 
| @@ -1430,7 +1741,7 @@ module Oktest | |
| 1430 1741 | 
             
                CHARS  = { :PASS=>'.', :FAIL=>'f', :ERROR=>'E', :SKIP=>'s', :TODO=>'t' }
         | 
| 1431 1742 |  | 
| 1432 1743 |  | 
| 1433 | 
            -
                def initialize
         | 
| 1744 | 
            +
                def initialize()
         | 
| 1434 1745 | 
             
                  @exceptions = []
         | 
| 1435 1746 | 
             
                  @counts = {}
         | 
| 1436 1747 | 
             
                end
         | 
| @@ -1573,6 +1884,10 @@ module Oktest | |
| 1573 1884 |  | 
| 1574 1885 | 
             
                LABELS = { :PASS=>'pass', :FAIL=>'Fail', :ERROR=>'ERROR', :SKIP=>'Skip', :TODO=>'TODO' }
         | 
| 1575 1886 |  | 
| 1887 | 
            +
                def enter_scope(scope)
         | 
| 1888 | 
            +
                  puts "## #{scope.filename}"
         | 
| 1889 | 
            +
                end
         | 
| 1890 | 
            +
             | 
| 1576 1891 | 
             
                def enter_topic(topic, depth)
         | 
| 1577 1892 | 
             
                  super
         | 
| 1578 1893 | 
             
                  puts "#{'  ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}"
         | 
| @@ -1606,6 +1921,64 @@ module Oktest | |
| 1606 1921 |  | 
| 1607 1922 |  | 
| 1608 1923 | 
             
              class SimpleReporter < BaseReporter
         | 
| 1924 | 
            +
                #; [!jxa1b] reports topics and progress.
         | 
| 1925 | 
            +
             | 
| 1926 | 
            +
                def initialize()
         | 
| 1927 | 
            +
                  super
         | 
| 1928 | 
            +
                  @_nl = true
         | 
| 1929 | 
            +
                end
         | 
| 1930 | 
            +
             | 
| 1931 | 
            +
                def order_policy()
         | 
| 1932 | 
            +
                  :spec_first
         | 
| 1933 | 
            +
                end
         | 
| 1934 | 
            +
             | 
| 1935 | 
            +
                def _nl()
         | 
| 1936 | 
            +
                  (puts(); @_nl = true) unless @_nl
         | 
| 1937 | 
            +
                end
         | 
| 1938 | 
            +
                private :_nl
         | 
| 1939 | 
            +
             | 
| 1940 | 
            +
                def _nl_off()
         | 
| 1941 | 
            +
                  @_nl = false
         | 
| 1942 | 
            +
                end
         | 
| 1943 | 
            +
                private :_nl_off
         | 
| 1944 | 
            +
             | 
| 1945 | 
            +
                def enter_scope(scope)
         | 
| 1946 | 
            +
                  _nl()
         | 
| 1947 | 
            +
                  puts "## #{scope.filename}"
         | 
| 1948 | 
            +
                end
         | 
| 1949 | 
            +
             | 
| 1950 | 
            +
                def exit_scope(scope)
         | 
| 1951 | 
            +
                  _nl()
         | 
| 1952 | 
            +
                  print_exceptions()
         | 
| 1953 | 
            +
                end
         | 
| 1954 | 
            +
             | 
| 1955 | 
            +
                def enter_topic(topic, depth)
         | 
| 1956 | 
            +
                  super
         | 
| 1957 | 
            +
                  return if topic._prefix == '-'
         | 
| 1958 | 
            +
                  _nl()
         | 
| 1959 | 
            +
                  print "#{'  ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}: "
         | 
| 1960 | 
            +
                  $stdout.flush()
         | 
| 1961 | 
            +
                  _nl_off()
         | 
| 1962 | 
            +
                end
         | 
| 1963 | 
            +
             | 
| 1964 | 
            +
                def exit_topic(topic, depth)
         | 
| 1965 | 
            +
                  super
         | 
| 1966 | 
            +
                  return if topic._prefix == '-'
         | 
| 1967 | 
            +
                  _nl()
         | 
| 1968 | 
            +
                  print_exceptions()
         | 
| 1969 | 
            +
                end
         | 
| 1970 | 
            +
             | 
| 1971 | 
            +
                def exit_spec(spec, depth, status, error, parent)
         | 
| 1972 | 
            +
                  super
         | 
| 1973 | 
            +
                  print Color.status(status, CHARS[status] || '?')
         | 
| 1974 | 
            +
                  $stdout.flush
         | 
| 1975 | 
            +
                  _nl_off()
         | 
| 1976 | 
            +
                end
         | 
| 1977 | 
            +
             | 
| 1978 | 
            +
              end
         | 
| 1979 | 
            +
             | 
| 1980 | 
            +
             | 
| 1981 | 
            +
              class CompactReporter < BaseReporter
         | 
| 1609 1982 | 
             
                #; [!xfd5o] reports filename.
         | 
| 1610 1983 |  | 
| 1611 1984 | 
             
                def enter_scope(scope)
         | 
| @@ -1672,14 +2045,13 @@ module Oktest | |
| 1672 2045 | 
             
              REPORTER_CLASSES = {
         | 
| 1673 2046 | 
             
                'verbose' => VerboseReporter,  'v' => VerboseReporter,
         | 
| 1674 2047 | 
             
                'simple'  => SimpleReporter,   's' => SimpleReporter,
         | 
| 2048 | 
            +
                'compact' => CompactReporter,  'c' => CompactReporter,
         | 
| 1675 2049 | 
             
                'plain'   => PlainReporter,    'p' => PlainReporter,
         | 
| 1676 2050 | 
             
                'quiet'   => QuietReporter,    'q' => QuietReporter,
         | 
| 1677 2051 | 
             
              }
         | 
| 1678 2052 |  | 
| 1679 2053 |  | 
| 1680 2054 | 
             
              def self.run(reporter: nil, style: nil)
         | 
| 1681 | 
            -
                #; [!kfi8b] do nothing when 'Oktest.scope()' not called.
         | 
| 1682 | 
            -
                return unless THE_GLOBAL_SCOPE.has_child?
         | 
| 1683 2055 | 
             
                #; [!6xn3t] creates reporter object according to 'style:' keyword arg.
         | 
| 1684 2056 | 
             
                klass = (style ? REPORTER_CLASSES[style] : REPORTER_CLASS)  or
         | 
| 1685 2057 | 
             
                  raise ArgumentError, "#{style.inspect}: unknown style."
         | 
| @@ -2145,7 +2517,7 @@ END | |
| 2145 2517 | 
             
                    return 0
         | 
| 2146 2518 | 
             
                  end
         | 
| 2147 2519 | 
             
                  #; [!65vdx] prints help message if no arguments specified.
         | 
| 2148 | 
            -
                  if filenames.empty?
         | 
| 2520 | 
            +
                  if filenames.empty? && !THE_GLOBAL_SCOPE.has_child?
         | 
| 2149 2521 | 
             
                    puts help_message()
         | 
| 2150 2522 | 
             
                    return 0
         | 
| 2151 2523 | 
             
                  end
         | 
| @@ -2176,7 +2548,8 @@ END | |
| 2176 2548 | 
             
                  Config.auto_run = false
         | 
| 2177 2549 | 
             
                  #; [!18qpe] runs test scripts.
         | 
| 2178 2550 | 
             
                  #; [!0qd92] '-s verbose' or '-sv' option prints test results in verbose mode.
         | 
| 2179 | 
            -
                  #; [! | 
| 2551 | 
            +
                  #; [!zfdr5] '-s simple' or '-ss' option prints test results in simple mode.
         | 
| 2552 | 
            +
                  #; [!ef5v7] '-s compact' or '-sc' option prints test results in compact mode.
         | 
| 2180 2553 | 
             
                  #; [!244te] '-s plain' or '-sp' option prints test results in plain mode.
         | 
| 2181 2554 | 
             
                  #; [!ai61w] '-s quiet' or '-sq' option prints test results in quiet mode.
         | 
| 2182 2555 | 
             
                  n_errors = Oktest.run(:style=>opts.style)
         | 
| @@ -2207,6 +2580,7 @@ END | |
| 2207 2580 | 
             
                  }
         | 
| 2208 2581 | 
             
                  parser.on('-F PATTERN') {|val|
         | 
| 2209 2582 | 
             
                    #; [!71h2x] '-F ...' option will be error.
         | 
| 2583 | 
            +
                    #; [!j01y7] if filerting by '-F' matched nothing, then prints zero result.
         | 
| 2210 2584 | 
             
                    val =~ /\A(topic|spec|tag|sid)(=|!=)/  or
         | 
| 2211 2585 | 
             
                      raise OptionParser::InvalidArgument, val
         | 
| 2212 2586 | 
             
                    opts.filter = val
         | 
| @@ -2237,7 +2611,7 @@ END | |
| 2237 2611 | 
             
            Usage: %{command} [<options>] [<file-or-directory>...]
         | 
| 2238 2612 | 
             
              -h, --help             : show help
         | 
| 2239 2613 | 
             
                  --version          : print version
         | 
| 2240 | 
            -
              -s <STYLE> | 
| 2614 | 
            +
              -s <REPORT-STYLE>      : verbose/simple/compact/plain/quiet, or v/s/c/p/q
         | 
| 2241 2615 | 
             
              -F <PATTERN>           : filter topic or spec with pattern (see below)
         | 
| 2242 2616 | 
             
                  --color[={on|off}] : enable/disable output coloring forcedly
         | 
| 2243 2617 | 
             
              -C, --create           : print test code skeleton
         |