oktest 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/oktest.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  ###
4
- ### $Release: 1.0.1 $
4
+ ### $Release: 1.2.0 $
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.0.1 $'.split()[1]
15
+ VERSION = '$Release: 1.2.0 $'.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, subclass: true, &b)
273
+ return raise?(errcls, errmsg, _subclass: true, &b)
268
274
  end
269
275
 
270
- def raise?(errcls=nil, errmsg=nil, subclass: false, &b)
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 'subclass: true' specified.
293
- elsif subclass && exc.class < errcls
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 'subclass: true' specified.
332
- elsif subclass && exc.class < errcls
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,261 @@ 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
+
797
+ module UtilHelper
798
+
799
+ def partial_regexp(pattern, begin_='\A', end_='\z', mark="{== ==}")
800
+ #; [!9drtn] is available in both topic and spec blocks.
801
+ return Util.partial_regexp(pattern, begin_, end_, mark)
802
+ end
803
+
804
+ def partial_regexp!(pattern, begin_='\A', end_='\z', mark="{== ==}")
805
+ #; [!wo4hp] is available in both topic and spec blocks.
806
+ return Util.partial_regexp!(pattern, begin_, end_, mark)
807
+ end
808
+
809
+ end
810
+
811
+
551
812
  class Context
552
813
  ## * Context class is separated from ScopeNode, TopicNode, and SpecLeaf.
553
814
  ## * `topic()` and `spec()` creates subclass of Context class,
@@ -555,6 +816,8 @@ module Oktest
555
816
  ## * `scope()` instanciates those subclasses, and run spec blocks
556
817
  ## in that instance objects.
557
818
 
819
+ extend UtilHelper
820
+
558
821
  class << self
559
822
  attr_accessor :__node
560
823
  end
@@ -587,26 +850,29 @@ module Oktest
587
850
  return to
588
851
  end
589
852
 
590
- def self.spec(desc, tag: nil, &block)
853
+ def self.spec(desc, tag: nil, fixture: nil, &block)
591
854
  node = @__node
592
855
  node.is_a?(Node) or raise "internal error: node=#{node.inspect}" # for debug
856
+ #; [!4vkbl] error when `fixture:` keyword arg is not a Hash object.
857
+ fixture.nil? || fixture.is_a?(Hash) or
858
+ raise ArgumentError, "spec(fixture: #{fixture.inspect}): fixture argument should be a Hash object, but got #{fixture.class.name} object."
593
859
  #; [!ala78] provides raising TodoException block if block not given.
594
860
  block ||= proc { raise TodoException, "not implemented yet" }
595
861
  #; [!x48db] keeps called location only when block has parameters.
596
862
  if block.parameters.empty?
597
863
  location = nil
598
864
  else
599
- location = caller(1).first # caller() makes performance slower, but necessary.
865
+ location = caller_locations(1, 1).first
600
866
  end
601
867
  #; [!c8c8o] creates new spec object.
602
- spec = SpecLeaf.new(node, desc, tag: tag, location: location, &block)
868
+ spec = SpecLeaf.new(node, desc, tag: tag, fixture: fixture, location: location, &block)
603
869
  return spec
604
870
  end
605
871
 
606
872
  def self.fixture(name, &block)
607
873
  #; [!8wfrq] registers fixture factory block.
608
874
  #; [!y3ks3] retrieves block parameter names.
609
- location = caller(1).first # caller() makes performance slower, but necessary.
875
+ location = caller_locations(1, 1).first
610
876
  @__node.register_fixture_block(name, location, &block)
611
877
  self
612
878
  end
@@ -730,6 +996,7 @@ module Oktest
730
996
  context = @context_class.new()
731
997
  #; [!9hbxn] context object has 'ok()' method.
732
998
  context.extend SpecHelper
999
+ context.extend UtilHelper
733
1000
  return context
734
1001
  end
735
1002
 
@@ -758,7 +1025,7 @@ module Oktest
758
1025
  return @hooks[key]
759
1026
  end
760
1027
 
761
- def _repr(depth=0, buf="")
1028
+ def _repr(depth=0, buf=String.new)
762
1029
  #; [!bt5j8] builds debug string.
763
1030
  if depth < 0
764
1031
  id_str = "%x" % self.object_id
@@ -833,16 +1100,17 @@ module Oktest
833
1100
 
834
1101
  class SpecLeaf < Item
835
1102
 
836
- def initialize(parent, desc, tag: nil, location: nil, &block)
1103
+ def initialize(parent, desc, tag: nil, fixture: nil, location: nil, &block)
837
1104
  #@parent = parent # not keep parent node to avoid recursive reference
838
1105
  @desc = desc
839
1106
  @tag = tag
1107
+ @fixture = fixture
840
1108
  @location = location # necessary when raising fixture not found error
841
1109
  @block = block
842
1110
  parent.add_child(self) if parent
843
1111
  end
844
1112
 
845
- attr_reader :desc, :tag, :location, :block
1113
+ attr_reader :desc, :tag, :fixture, :location, :block
846
1114
 
847
1115
  def _prefix
848
1116
  '-'
@@ -863,7 +1131,7 @@ module Oktest
863
1131
  nil
864
1132
  end
865
1133
 
866
- def _repr(depth=0, buf="") #:nodoc:
1134
+ def _repr(depth=0, buf=String.new) #:nodoc:
867
1135
  #; [!6nsgy] builds debug string.
868
1136
  buf << " " * depth << "- #{@desc}"
869
1137
  buf << " (tag: #{@tag.inspect})" if @tag
@@ -892,8 +1160,8 @@ module Oktest
892
1160
 
893
1161
  def self.scope(tag: nil, &block)
894
1162
  #; [!kem4y] detects test script filename.
895
- location = caller(1).first # caller() makes performance slower, but necessary.
896
- filename = location =~ /:\d+/ ? $` : nil
1163
+ location = caller_locations(1, 1).first
1164
+ filename = location.path.to_s
897
1165
  #; [!6ullm] changes test script filename from absolute path to relative path.
898
1166
  if filename
899
1167
  pwd = Dir.pwd()
@@ -929,7 +1197,7 @@ module Oktest
929
1197
  #; [!bc3l2] records invoked location.
930
1198
  #; [!mqtdy] not record invoked location when `Config.ok_location == false`.
931
1199
  if Config.ok_location
932
- location = caller(1).first # caller() makes performance slower, but necessary.
1200
+ location = caller_locations(1, 1).first
933
1201
  else
934
1202
  location = nil
935
1203
  end
@@ -944,7 +1212,7 @@ module Oktest
944
1212
  #; [!agmx8] records invoked location.
945
1213
  #; [!a9508] not record invoked location when `Config.ok_location == false`.
946
1214
  if Config.ok_location
947
- location = caller(1).first # caller() makes performance slower, but necessary.
1215
+ location = caller_locations(1, 1).first
948
1216
  else
949
1217
  location = nil
950
1218
  end
@@ -970,7 +1238,7 @@ module Oktest
970
1238
  #; [!wxcsp] raises error when fixture not found.
971
1239
  unless tuple
972
1240
  exc = FixtureNotFoundError.new("`#{name.inspect}`: fixture not found.")
973
- exc.set_backtrace([caller(1).first])
1241
+ exc.set_backtrace([caller_locations(1, 1).first.to_s])
974
1242
  raise exc
975
1243
  end
976
1244
  #; [!m4ava] calls fixture block and returns result of it.
@@ -980,7 +1248,7 @@ module Oktest
980
1248
  end
981
1249
 
982
1250
  def TODO()
983
- location = caller(1).first # ex: "foo_test.rb:123:in ...."
1251
+ location = caller_locations(1, 1).first # ex: "foo_test.rb:123:in ...."
984
1252
  @__TODO = location
985
1253
  end
986
1254
 
@@ -1116,6 +1384,41 @@ module Oktest
1116
1384
  return Benry::Recorder.new
1117
1385
  end
1118
1386
 
1387
+ def JSON(actual)
1388
+ #; [!n0k03] creates JsonMatcher object.
1389
+ return JsonMatcher.new(actual)
1390
+ end
1391
+
1392
+ def Enum(*values)
1393
+ #; [!fbfr0] creates Enum object which is a subclass of Set.
1394
+ return JsonMatcher::Enum.new(values)
1395
+ end
1396
+
1397
+ def Bool()
1398
+ #; [!vub5j] creates a set of true and false.
1399
+ return Enum(true, false)
1400
+ end
1401
+
1402
+ def OR(*args)
1403
+ #; [!9e8im] creates `OR` object.
1404
+ return JsonMatcher::OR.new(*args)
1405
+ end
1406
+
1407
+ def AND(*args)
1408
+ #; [!38jln] creates `AND` object.
1409
+ return JsonMatcher::AND.new(*args)
1410
+ end
1411
+
1412
+ def Length(n)
1413
+ #; [!qqas3] creates Length object.
1414
+ return JsonMatcher::Length.new(n)
1415
+ end
1416
+
1417
+ def Any()
1418
+ #; [!dlo1o] creates an 'Any' object.
1419
+ return JsonMatcher::Any.new
1420
+ end
1421
+
1119
1422
  end
1120
1423
 
1121
1424
 
@@ -1215,11 +1518,25 @@ module Oktest
1215
1518
  @reporter.exit_all(self)
1216
1519
  end
1217
1520
 
1521
+ def _spec_first(node)
1522
+ a1, a2 = node.each_child.partition {|c|
1523
+ c.is_a?(SpecLeaf) || (c.is_a?(TopicNode) && c._prefix != '*')
1524
+ }
1525
+ return a1+a2
1526
+ end
1527
+ private :_spec_first
1528
+
1218
1529
  def visit_scope(scope, depth, parent)
1219
1530
  @reporter.enter_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
1220
1531
  #; [!5anr7] calls before_all and after_all blocks.
1221
1532
  call_before_all_block(scope)
1222
- scope.each_child {|c| c.accept_visitor(self, depth+1, scope) }
1533
+ #; [!c5cw0] run specs and case_when in advance of specs and topics when SimpleReporter.
1534
+ if @reporter.order_policy() == :spec_first
1535
+ _spec_first(scope).each {|c| c.accept_visitor(self, depth+1, scope) }
1536
+ else
1537
+ scope.each_child {|c| c.accept_visitor(self, depth+1, scope) }
1538
+ end
1539
+ #
1223
1540
  call_after_all_block(scope)
1224
1541
  @reporter.exit_scope(scope) unless scope.equal?(THE_GLOBAL_SCOPE)
1225
1542
  end
@@ -1228,7 +1545,13 @@ module Oktest
1228
1545
  @reporter.enter_topic(topic, depth)
1229
1546
  #; [!i3yfv] calls 'before_all' and 'after_all' blocks.
1230
1547
  call_before_all_block(topic)
1231
- topic.each_child {|c| c.accept_visitor(self, depth+1, topic) }
1548
+ #; [!p3a5o] run specs and case_when in advance of specs and topics when SimpleReporter.
1549
+ if @reporter.order_policy() == :spec_first
1550
+ _spec_first(topic).each {|c| c.accept_visitor(self, depth+1, topic) }
1551
+ else
1552
+ topic.each_child {|c| c.accept_visitor(self, depth+1, topic) }
1553
+ end
1554
+ #
1232
1555
  call_after_all_block(topic)
1233
1556
  @reporter.exit_topic(topic, depth)
1234
1557
  end
@@ -1246,7 +1569,8 @@ module Oktest
1246
1569
  begin
1247
1570
  params = Util.required_param_names_of_block(spec.block)
1248
1571
  values = params.nil? || params.empty? ? [] \
1249
- : get_fixture_values(params, node, spec, context)
1572
+ : get_fixture_values(params, node, spec, context, spec.location,
1573
+ spec.fixture ? spec.fixture.dup : {})
1250
1574
  spec.run_block_in_context_object(context, *values)
1251
1575
  rescue NoMemoryError => exc; raise exc
1252
1576
  rescue SignalException => exc; raise exc
@@ -1271,7 +1595,7 @@ module Oktest
1271
1595
  exc = TODO_EXCEPTION.new("#{exc.class} raised because not implemented yet")
1272
1596
  end
1273
1597
  location = context.__TODO
1274
- exc.set_backtrace([location])
1598
+ exc.set_backtrace([location.to_s])
1275
1599
  end
1276
1600
  #; [!dihkr] calls 'at_end' blocks, even when exception raised.
1277
1601
  begin
@@ -1285,8 +1609,8 @@ module Oktest
1285
1609
 
1286
1610
  private
1287
1611
 
1288
- def get_fixture_values(names, node, spec, context)
1289
- return THE_FIXTURE_MANAGER.get_fixture_values(names, node, spec, context)
1612
+ def get_fixture_values(names, node, spec, context, location=nil, resolved=nil)
1613
+ return THE_FIXTURE_MANAGER.get_fixture_values(names, node, spec, context, location, resolved)
1290
1614
  end
1291
1615
 
1292
1616
  def _call_blocks_parent_first(node, name, context)
@@ -1342,51 +1666,53 @@ module Oktest
1342
1666
 
1343
1667
  class FixtureManager
1344
1668
 
1345
- def get_fixture_values(names, node, spec, context, location=nil, _resolved={}, _resolving=[])
1669
+ def get_fixture_values(names, node, spec, context, location, resolved={}, _resolving=[])
1346
1670
  #; [!w6ffs] resolves 'this_topic' fixture name as target objec of current topic.
1347
- _resolved[:this_topic] = node.target if !_resolved.key?(:this_topic) && node.topic?
1671
+ resolved[:this_topic] = node.target if !resolved.key?(:this_topic) && node.topic?
1348
1672
  #; [!ja2ew] resolves 'this_spec' fixture name as description of current spec.
1349
- _resolved[:this_spec] = spec.desc if !_resolved.key?(:this_spec)
1673
+ resolved[:this_spec] = spec.desc if !resolved.key?(:this_spec)
1350
1674
  #; [!v587k] resolves fixtures.
1351
- location ||= spec.location
1352
1675
  return names.collect {|name|
1353
1676
  #; [!np4p9] raises error when loop exists in dependency.
1354
1677
  ! _resolving.include?(name) or
1355
1678
  raise _looped_dependency_error(name, _resolving, location)
1356
- get_fixture_value(name, node, spec, context, location, _resolved, _resolving)
1679
+ get_fixture_value(name, node, spec, context, location, resolved, _resolving)
1357
1680
  }
1358
1681
  end
1359
1682
 
1360
- def get_fixture_value(name, node, spec, context, location=nil, _resolved={}, _resolving=[])
1361
- return _resolved[name] if _resolved.key?(name)
1362
- location ||= spec.location
1683
+ def get_fixture_value(name, node, spec, context, location, resolved={}, _resolving=[])
1684
+ return resolved[name] if resolved.key?(name)
1363
1685
  tuple = node.get_fixture_block(name)
1364
1686
  if tuple
1365
1687
  block, param_names, location = tuple
1366
1688
  #; [!2esaf] resolves fixture dependencies.
1367
1689
  if param_names
1368
1690
  _resolving << name
1369
- args = get_fixture_values(param_names, node, spec, context, location, _resolved, _resolving)
1691
+ args = get_fixture_values(param_names, node, spec, context, location, resolved, _resolving)
1370
1692
  (popped = _resolving.pop) == name or
1371
1693
  raise "** assertion failed: name=#{name.inspect}, resolvng[-1]=#{popped.inspect}"
1372
- #; [!4xghy] calls fixture block with context object as self.
1373
- val = context.instance_exec(*args, &block)
1374
1694
  else
1375
- val = context.instance_eval(&block)
1695
+ args = []
1376
1696
  end
1697
+ #; [!gyyst] overwrites keyword params by fixture values.
1698
+ kwnames = Util.keyword_param_names_of_block(block)
1699
+ kwargs = {}
1700
+ kwnames.each {|name| kwargs[name] = resolved[name] if resolved.key?(name) }
1701
+ #; [!4xghy] calls fixture block with context object as self.
1702
+ val = context.instance_exec(*args, **kwargs, &block)
1377
1703
  #; [!8t3ul] caches fixture value to call fixture block only once per spec.
1378
- _resolved[name] = val
1704
+ resolved[name] = val
1379
1705
  return val
1380
1706
  elsif node.parent
1381
1707
  #; [!4chb9] traverses parent topics if fixture not found in current topic.
1382
- return get_fixture_value(name, node.parent, spec, context, location, _resolved, _resolving)
1708
+ return get_fixture_value(name, node.parent, spec, context, location, resolved, _resolving)
1383
1709
  elsif ! node.equal?(THE_GLOBAL_SCOPE)
1384
1710
  #; [!wt3qk] suports global scope.
1385
- return get_fixture_value(name, THE_GLOBAL_SCOPE, spec, context, location, _resolved, _resolving)
1711
+ return get_fixture_value(name, THE_GLOBAL_SCOPE, spec, context, location, resolved, _resolving)
1386
1712
  else
1387
1713
  #; [!nr79z] raises error when fixture not found.
1388
1714
  exc = FixtureNotFoundError.new("#{name}: fixture not found. (spec: #{spec.desc})")
1389
- exc.set_backtrace([location]) if location
1715
+ exc.set_backtrace([location.to_s]) if location
1390
1716
  raise exc
1391
1717
  end
1392
1718
  end
@@ -1401,7 +1727,7 @@ module Oktest
1401
1727
  loop = s1.empty? ? s2 : "#{s1}->#{s2}"
1402
1728
  #location = $1 if location =~ /(.*:\d+)/
1403
1729
  exc = LoopedDependencyError.new("fixture dependency is looped: #{loop}")
1404
- exc.set_backtrace([location])
1730
+ exc.set_backtrace([location.to_s])
1405
1731
  return exc
1406
1732
  end
1407
1733
 
@@ -1429,6 +1755,7 @@ module Oktest
1429
1755
  def exit_spec(spec, depth, status, error, parent); end
1430
1756
  #
1431
1757
  def counts; {}; end
1758
+ def order_policy(); nil; end # :spec_first or nil
1432
1759
 
1433
1760
  end
1434
1761
 
@@ -1439,7 +1766,7 @@ module Oktest
1439
1766
  CHARS = { :PASS=>'.', :FAIL=>'f', :ERROR=>'E', :SKIP=>'s', :TODO=>'t' }
1440
1767
 
1441
1768
 
1442
- def initialize
1769
+ def initialize()
1443
1770
  @exceptions = []
1444
1771
  @counts = {}
1445
1772
  end
@@ -1544,7 +1871,7 @@ module Oktest
1544
1871
  end
1545
1872
  lines = []
1546
1873
  msg.each_line {|line| lines << line }
1547
- puts lines.shift.chomp
1874
+ puts Color.errmsg(lines.shift.chomp)
1548
1875
  puts lines.join.chomp unless lines.empty?
1549
1876
  puts exc.diff if exc.respond_to?(:diff) && exc.diff # for oktest.rb
1550
1877
  end
@@ -1582,6 +1909,10 @@ module Oktest
1582
1909
 
1583
1910
  LABELS = { :PASS=>'pass', :FAIL=>'Fail', :ERROR=>'ERROR', :SKIP=>'Skip', :TODO=>'TODO' }
1584
1911
 
1912
+ def enter_scope(scope)
1913
+ puts "## #{scope.filename}"
1914
+ end
1915
+
1585
1916
  def enter_topic(topic, depth)
1586
1917
  super
1587
1918
  puts "#{' ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}"
@@ -1606,15 +1937,73 @@ module Oktest
1606
1937
  $stdout.flush
1607
1938
  end
1608
1939
  label = Color.status(status, LABELS[status] || '???')
1609
- msg = "#{' ' * (depth - 1)}- [#{label}] #{spec.desc}"
1940
+ msg = ["#{' ' * (depth - 1)}- [#{label}] #{spec.desc}"]
1610
1941
  msg << " " << Color.reason("(reason: #{error.message})") if status == :SKIP
1611
- puts msg
1942
+ puts msg.join()
1612
1943
  end
1613
1944
 
1614
1945
  end
1615
1946
 
1616
1947
 
1617
1948
  class SimpleReporter < BaseReporter
1949
+ #; [!jxa1b] reports topics and progress.
1950
+
1951
+ def initialize()
1952
+ super
1953
+ @_nl = true
1954
+ end
1955
+
1956
+ def order_policy()
1957
+ :spec_first
1958
+ end
1959
+
1960
+ def _nl()
1961
+ (puts(); @_nl = true) unless @_nl
1962
+ end
1963
+ private :_nl
1964
+
1965
+ def _nl_off()
1966
+ @_nl = false
1967
+ end
1968
+ private :_nl_off
1969
+
1970
+ def enter_scope(scope)
1971
+ _nl()
1972
+ puts "## #{scope.filename}"
1973
+ end
1974
+
1975
+ def exit_scope(scope)
1976
+ _nl()
1977
+ print_exceptions()
1978
+ end
1979
+
1980
+ def enter_topic(topic, depth)
1981
+ super
1982
+ return if topic._prefix == '-'
1983
+ _nl()
1984
+ print "#{' ' * (depth - 1)}#{topic._prefix} #{Color.topic(topic.target)}: "
1985
+ $stdout.flush()
1986
+ _nl_off()
1987
+ end
1988
+
1989
+ def exit_topic(topic, depth)
1990
+ super
1991
+ return if topic._prefix == '-'
1992
+ _nl()
1993
+ print_exceptions()
1994
+ end
1995
+
1996
+ def exit_spec(spec, depth, status, error, parent)
1997
+ super
1998
+ print Color.status(status, CHARS[status] || '?')
1999
+ $stdout.flush
2000
+ _nl_off()
2001
+ end
2002
+
2003
+ end
2004
+
2005
+
2006
+ class CompactReporter < BaseReporter
1618
2007
  #; [!xfd5o] reports filename.
1619
2008
 
1620
2009
  def enter_scope(scope)
@@ -1681,14 +2070,13 @@ module Oktest
1681
2070
  REPORTER_CLASSES = {
1682
2071
  'verbose' => VerboseReporter, 'v' => VerboseReporter,
1683
2072
  'simple' => SimpleReporter, 's' => SimpleReporter,
2073
+ 'compact' => CompactReporter, 'c' => CompactReporter,
1684
2074
  'plain' => PlainReporter, 'p' => PlainReporter,
1685
2075
  'quiet' => QuietReporter, 'q' => QuietReporter,
1686
2076
  }
1687
2077
 
1688
2078
 
1689
2079
  def self.run(reporter: nil, style: nil)
1690
- #; [!kfi8b] do nothing when 'Oktest.scope()' not called.
1691
- return unless THE_GLOBAL_SCOPE.has_child?
1692
2080
  #; [!6xn3t] creates reporter object according to 'style:' keyword arg.
1693
2081
  klass = (style ? REPORTER_CLASSES[style] : REPORTER_CLASS) or
1694
2082
  raise ArgumentError, "#{style.inspect}: unknown style."
@@ -1737,6 +2125,13 @@ module Oktest
1737
2125
  return param_names
1738
2126
  end
1739
2127
 
2128
+ def keyword_param_names_of_block(block)
2129
+ #; [!p6qqp] returns keyword param names of proc object.
2130
+ names = []
2131
+ block.parameters.each {|kind, name| names << name if kind == :key }
2132
+ return names
2133
+ end
2134
+
1740
2135
  def strfold(str, width=80, mark='...')
1741
2136
  #; [!wb7m8] returns string as it is if string is not long.
1742
2137
  return str if str.bytesize <= width
@@ -1837,6 +2232,70 @@ module Oktest
1837
2232
  end
1838
2233
  end
1839
2234
 
2235
+ class PartialRegexp < Regexp
2236
+ attr_accessor :pattern_string, :begin, :end, :mark
2237
+ def inspect()
2238
+ #; [!uyh31] returns function call style string if @pattern_string is set.
2239
+ if @pattern_string
2240
+ c = @pattern_string.end_with?("\n") ? "" : ".chomp"
2241
+ p = @pattern_string.chomp
2242
+ b = @begin == '\A' ? "'\\A'" : @begin.inspect
2243
+ e = @end == '\z' ? "'\\z'" : @end.inspect
2244
+ m = mark == "{== ==}" ? "" : ", #{mark.inspect}"
2245
+ return "partial_regexp(<<PREXP#{c}, #{b}, #{e}#{m})\n#{p}\nPREXP\n"
2246
+ #; [!ts9v4] returns regexp literal style string if @pattern_string is not set.
2247
+ else
2248
+ s = super
2249
+ s = s.gsub(/([^\\](?:\\\\)*)((?:\\n)+)/) {
2250
+ $1 + ("\\n\n" * ($2.length / 2))
2251
+ }
2252
+ if s =~ /\n/
2253
+ s = s.sub(/\A\/(\\A)?/, "/\\1\n")
2254
+ s = s + "x" # `/.../x` means multiline regexp
2255
+ end
2256
+ return s
2257
+ end
2258
+ end
2259
+ end
2260
+
2261
+ def partial_regexp!(pattern, begin_='\A', end_='\z', mark="{== ==}")
2262
+ #; [!peyu4] returns PartialRegexp object which inspect string is function call styel.
2263
+ regexp = partial_regexp(pattern, begin_, end_, mark)
2264
+ regexp.pattern_string = pattern
2265
+ regexp.begin = begin_; regexp.end = end_; regexp.mark = mark
2266
+ return regexp
2267
+ end
2268
+
2269
+ def partial_regexp(pattern, begin_='\A', end_='\z', mark="{== ==}")
2270
+ mark_rexp = PARTIAL_REGEXP_CACHE[mark]
2271
+ if mark_rexp.nil?
2272
+ #; [!ostkw] raises error if mark has no space or has more than two spaces.
2273
+ pair = mark.split()
2274
+ pair.length == 2 or
2275
+ raise ArgumentError.new("#{mark.inspect}: mark should contain only one space (ex: `{== ==}`).")
2276
+ open = Regexp.escape(pair[0])
2277
+ close = Regexp.escape(pair[1])
2278
+ mark_rexp = Regexp.compile("#{open}(.*?)#{close}")
2279
+ PARTIAL_REGEXP_CACHE[mark] = mark_rexp
2280
+ end
2281
+ #; [!wn524] returns PartialRegexp object which inspect string is regexp literal style.
2282
+ pos = 0
2283
+ buf = []
2284
+ buf << begin_ if begin_
2285
+ pattern.scan(mark_rexp) do
2286
+ m = Regexp.last_match
2287
+ text = pattern[pos, m.begin(0) - pos]
2288
+ buf << Regexp.escape(text) << $1.strip()
2289
+ pos = m.end(0)
2290
+ end
2291
+ rest = pos == 0 ? pattern : pattern[pos..-1]
2292
+ buf << Regexp.escape(rest) unless rest.empty?
2293
+ buf << end_ if end_
2294
+ return PartialRegexp.compile(buf.join())
2295
+ end
2296
+
2297
+ PARTIAL_REGEXP_CACHE = {} # :nodoc:
2298
+
1840
2299
  end
1841
2300
 
1842
2301
 
@@ -1979,24 +2438,35 @@ module Oktest
1979
2438
  module_function
1980
2439
 
1981
2440
  def normal s; return s; end
1982
- def bold s; return "\e[0;1m#{s}\e[22m"; end
1983
- def black s; return "\e[1;30m#{s}\e[0m"; end
1984
- def red s; return "\e[1;31m#{s}\e[0m"; end
1985
- def green s; return "\e[1;32m#{s}\e[0m"; end
1986
- def yellow s; return "\e[1;33m#{s}\e[0m"; end
1987
- def blue s; return "\e[1;34m#{s}\e[0m"; end
1988
- def magenta s; return "\e[1;35m#{s}\e[0m"; end
1989
- def cyan s; return "\e[1;36m#{s}\e[0m"; end
1990
- def white s; return "\e[1;37m#{s}\e[0m"; end
2441
+ def bold s; return "\e[0;1m#{s}\e[0m"; end
2442
+
2443
+ def black s; return "\e[0;30m#{s}\e[0m"; end
2444
+ def red s; return "\e[0;31m#{s}\e[0m"; end
2445
+ def green s; return "\e[0;32m#{s}\e[0m"; end
2446
+ def yellow s; return "\e[0;33m#{s}\e[0m"; end
2447
+ def blue s; return "\e[0;34m#{s}\e[0m"; end
2448
+ def magenta s; return "\e[0;35m#{s}\e[0m"; end
2449
+ def cyan s; return "\e[0;36m#{s}\e[0m"; end
2450
+ def white s; return "\e[0;37m#{s}\e[0m"; end
2451
+
2452
+ def black_b s; return "\e[1;30m#{s}\e[0m"; end # bold
2453
+ def red_b s; return "\e[1;31m#{s}\e[0m"; end # bold
2454
+ def green_b s; return "\e[1;32m#{s}\e[0m"; end # bold
2455
+ def yellow_b s; return "\e[1;33m#{s}\e[0m"; end # bold
2456
+ def blue_b s; return "\e[1;34m#{s}\e[0m"; end # bold
2457
+ def magenta_b s; return "\e[1;35m#{s}\e[0m"; end # bold
2458
+ def cyan_b s; return "\e[1;36m#{s}\e[0m"; end # bold
2459
+ def white_b s; return "\e[1;37m#{s}\e[0m"; end # bold
1991
2460
 
1992
2461
  def topic s; Config.color_enabled ? bold(s) : s; end
1993
2462
  def spec s; Config.color_enabled ? normal(s) : s; end
1994
- def pass s; Config.color_enabled ? blue(s) : s; end
2463
+ def pass s; Config.color_enabled ? cyan(s) : s; end
1995
2464
  def fail s; Config.color_enabled ? red(s) : s; end
1996
- def error s; Config.color_enabled ? red(s) : s; end
2465
+ def error s; Config.color_enabled ? red_b(s) : s; end # bold
1997
2466
  def skip s; Config.color_enabled ? yellow(s) : s; end
1998
2467
  def todo s; Config.color_enabled ? yellow(s) : s; end
1999
2468
  def reason s; Config.color_enabled ? yellow(s) : s; end
2469
+ def errmsg s; Config.color_enabled ? red(s) : s; end
2000
2470
 
2001
2471
  def status(status, s)
2002
2472
  #; [!yev5y] returns string containing color escape sequence.
@@ -2131,6 +2601,11 @@ END
2131
2601
  color_enabled = nil
2132
2602
  opts = Options.new
2133
2603
  parser = option_parser(opts)
2604
+ #; [!v5xie] parses $OKTEST_RB environment variable.
2605
+ if ENV.key?('OKTEST_RB')
2606
+ parser.parse(ENV['OKTEST_RB'].split())
2607
+ end
2608
+ #
2134
2609
  filenames = parser.parse(args)
2135
2610
  #; [!9973n] '-h' or '--help' option prints help message.
2136
2611
  if opts.help
@@ -2142,8 +2617,8 @@ END
2142
2617
  puts VERSION
2143
2618
  return 0
2144
2619
  end
2145
- #; [!dk8eg] '-C' or '--create' option prints test code skeleton.
2146
- if opts.create
2620
+ #; [!dk8eg] '-S' or '--skeleton' option prints test code skeleton.
2621
+ if opts.skeleton
2147
2622
  print SKELETON
2148
2623
  return 0
2149
2624
  end
@@ -2154,7 +2629,7 @@ END
2154
2629
  return 0
2155
2630
  end
2156
2631
  #; [!65vdx] prints help message if no arguments specified.
2157
- if filenames.empty?
2632
+ if filenames.empty? && !THE_GLOBAL_SCOPE.has_child?
2158
2633
  puts help_message()
2159
2634
  return 0
2160
2635
  end
@@ -2185,7 +2660,8 @@ END
2185
2660
  Config.auto_run = false
2186
2661
  #; [!18qpe] runs test scripts.
2187
2662
  #; [!0qd92] '-s verbose' or '-sv' option prints test results in verbose mode.
2188
- #; [!ef5v7] '-s simple' or '-ss' option prints test results in simple mode.
2663
+ #; [!zfdr5] '-s simple' or '-ss' option prints test results in simple mode.
2664
+ #; [!ef5v7] '-s compact' or '-sc' option prints test results in compact mode.
2189
2665
  #; [!244te] '-s plain' or '-sp' option prints test results in plain mode.
2190
2666
  #; [!ai61w] '-s quiet' or '-sq' option prints test results in quiet mode.
2191
2667
  n_errors = Oktest.run(:style=>opts.style)
@@ -2201,7 +2677,7 @@ END
2201
2677
  private
2202
2678
 
2203
2679
  class Options #:nodoc:
2204
- attr_accessor :help, :version, :style, :filter, :color, :create, :generate, :faster
2680
+ attr_accessor :help, :version, :style, :filter, :color, :skeleton, :generate, :faster
2205
2681
  end
2206
2682
 
2207
2683
  def option_parser(opts)
@@ -2216,6 +2692,7 @@ END
2216
2692
  }
2217
2693
  parser.on('-F PATTERN') {|val|
2218
2694
  #; [!71h2x] '-F ...' option will be error.
2695
+ #; [!j01y7] if filerting by '-F' matched nothing, then prints zero result.
2219
2696
  val =~ /\A(topic|spec|tag|sid)(=|!=)/ or
2220
2697
  raise OptionParser::InvalidArgument, val
2221
2698
  opts.filter = val
@@ -2227,7 +2704,7 @@ END
2227
2704
  #; [!dptgn] '--color' is same as '--color=on'.
2228
2705
  opts.color = val || 'on'
2229
2706
  }
2230
- parser.on('-C', '--create') { opts.create = true }
2707
+ parser.on('-S', '--skeleton') { opts.skeleton = true }
2231
2708
  parser.on('-G', '--generate[=styleoption]') {|val|
2232
2709
  val.nil? || val == 'unaryop' or
2233
2710
  raise OptionParser::InvalidArgument, val
@@ -2242,16 +2719,16 @@ END
2242
2719
  return HELP_MESSAGE % {command: command}
2243
2720
  end
2244
2721
 
2245
- HELP_MESSAGE = <<'END'
2722
+ HELP_MESSAGE = <<'END'.gsub(/^#.*\n/, '')
2246
2723
  Usage: %{command} [<options>] [<file-or-directory>...]
2247
2724
  -h, --help : show help
2248
2725
  --version : print version
2249
- -s <STYLE> : report style (verbose/simple/plain/quiet, or v/s/p/q)
2726
+ -s <REPORT-STYLE> : verbose/simple/compact/plain/quiet, or v/s/c/p/q
2250
2727
  -F <PATTERN> : filter topic or spec with pattern (see below)
2251
2728
  --color[={on|off}] : enable/disable output coloring forcedly
2252
- -C, --create : print test code skeleton
2729
+ -S, --skeleton : print test code skeleton
2253
2730
  -G, --generate : generate test code skeleton from ruby file
2254
- --faster : make 'ok{}' faster (for very large project)
2731
+ # --faster : make 'ok{}' faster (for very large project)
2255
2732
 
2256
2733
  Filter examples:
2257
2734
  $ oktest -F topic=Hello # filter by topic