aquarium 0.4.4 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +31 -6
- data/README +4 -1
- data/RELEASE-PLAN +2 -0
- data/Rakefile +20 -30
- data/UPGRADE +14 -4
- data/lib/aquarium/aspects/advice.rb +25 -10
- data/lib/aquarium/aspects/aspect.rb +8 -7
- data/lib/aquarium/aspects/join_point.rb +15 -5
- data/lib/aquarium/aspects/pointcut.rb +4 -4
- data/lib/aquarium/extensions.rb +0 -1
- data/lib/aquarium/finders/method_finder.rb +3 -10
- data/lib/aquarium/finders/pointcut_finder.rb +15 -5
- data/lib/aquarium/finders/type_finder.rb +0 -1
- data/lib/aquarium/utils/array_utils.rb +0 -1
- data/lib/aquarium/utils/method_utils.rb +13 -2
- data/lib/aquarium/utils/options_utils.rb +1 -0
- data/lib/aquarium/utils/type_utils.rb +21 -5
- data/lib/aquarium/version.rb +2 -2
- data/spec/aquarium/aspects/advice_chain_node_spec.rb +0 -1
- data/spec/aquarium/aspects/advice_spec.rb +80 -45
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +66 -31
- data/spec/aquarium/aspects/aspect_spec.rb +88 -91
- data/spec/aquarium/aspects/concurrent_aspects_spec.rb +1 -1
- data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +3 -1
- data/spec/aquarium/aspects/join_point_spec.rb +0 -1
- data/spec/aquarium/aspects/pointcut_spec.rb +21 -18
- data/spec/aquarium/extensions/hash_spec.rb +211 -219
- data/spec/aquarium/extensions/set_spec.rb +1 -1
- data/spec/aquarium/extras/design_by_contract_spec.rb +1 -1
- data/spec/aquarium/finders/finder_result_spec.rb +4 -4
- data/spec/aquarium/finders/method_finder_spec.rb +6 -9
- data/spec/aquarium/finders/type_finder_spec.rb +2 -2
- data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +12 -10
- data/spec/aquarium/spec_example_types.rb +2 -2
- data/spec/aquarium/spec_helper.rb +1 -1
- data/spec/aquarium/utils/array_utils_spec.rb +32 -5
- data/spec/aquarium/utils/hash_utils_spec.rb +1 -0
- data/spec/aquarium/utils/method_utils_spec.rb +18 -0
- data/spec/aquarium/utils/options_utils_spec.rb +16 -20
- data/spec/aquarium/utils/type_utils_sample_classes.rb +10 -1
- data/spec/aquarium/utils/type_utils_spec.rb +9 -7
- metadata +29 -35
- data/lib/aquarium/extensions/symbol.rb +0 -22
- data/spec/aquarium/extensions/symbol_spec.rb +0 -37
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
2
|
require 'aquarium/spec_example_types'
|
3
3
|
require 'aquarium/aspects'
|
4
|
+
require 'aquarium/utils'
|
4
5
|
require 'logger'
|
5
6
|
|
6
7
|
include Aquarium::Aspects
|
@@ -23,10 +24,10 @@ end
|
|
23
24
|
|
24
25
|
|
25
26
|
describe Aspect, " cannot advise the private implementation methods inserted by other aspects" do
|
27
|
+
class WithAspectLikeMethod
|
28
|
+
def _aspect_foo; end
|
29
|
+
end
|
26
30
|
it "should have no affect." do
|
27
|
-
class WithAspectLikeMethod
|
28
|
-
def _aspect_foo; end
|
29
|
-
end
|
30
31
|
aspect = Aspect.new(:after, :pointcut => {:type => WithAspectLikeMethod, :methods => :_aspect_foo}) {|jp, obj, *args| fail}
|
31
32
|
WithAspectLikeMethod.new._aspect_foo
|
32
33
|
aspect.unadvise
|
@@ -40,7 +41,7 @@ describe Aspect, " when advising a type" do
|
|
40
41
|
after(:each) do
|
41
42
|
@aspect.unadvise
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
it "should not add new public instance or class methods that the advised type responds to." do
|
45
46
|
all_public_methods_before = all_public_methods_of_type Watchful
|
46
47
|
@aspect = Aspect.new :after, :pointcut => {:type => Watchful, :method_options => :exclude_ancestor_methods}, :advice => @advice
|
@@ -153,26 +154,25 @@ describe Aspect, " with :after advice" do
|
|
153
154
|
do_watchful_public_protected_private
|
154
155
|
end
|
155
156
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
args + ["d"]
|
160
|
-
end
|
157
|
+
class ReturningValue
|
158
|
+
def doit args
|
159
|
+
args + ["d"]
|
161
160
|
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should ignore the value returned by the advice" do
|
162
164
|
ary = %w[a b c]
|
163
165
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
166
|
+
advise_called = false
|
164
167
|
@aspect = Aspect.new :after, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
168
|
+
advise_called = true
|
165
169
|
%w[aa] + jp.context.returned_value + %w[e]
|
166
170
|
end
|
167
171
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
172
|
+
advise_called.should == true
|
168
173
|
end
|
169
174
|
|
170
|
-
it "should
|
171
|
-
class ReturningValue
|
172
|
-
def doit args
|
173
|
-
args + ["d"]
|
174
|
-
end
|
175
|
-
end
|
175
|
+
it "should allow the advice to assign a new return value" do
|
176
176
|
ary = %w[a b c]
|
177
177
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
178
178
|
@aspect = Aspect.new :after, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
@@ -223,26 +223,25 @@ describe Aspect, " with :after_returning advice" do
|
|
223
223
|
do_watchful_public_protected_private
|
224
224
|
end
|
225
225
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
args + ["d"]
|
230
|
-
end
|
226
|
+
class ReturningValue
|
227
|
+
def doit args
|
228
|
+
args + ["d"]
|
231
229
|
end
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should ignore the value returned by the advice" do
|
232
233
|
ary = %w[a b c]
|
233
234
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
235
|
+
advice_called = false
|
234
236
|
@aspect = Aspect.new :after_returning, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
237
|
+
advice_called = true
|
235
238
|
%w[aa] + jp.context.returned_value + %w[e]
|
236
239
|
end
|
237
240
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
241
|
+
advice_called.should == true
|
238
242
|
end
|
239
243
|
|
240
244
|
it "should allow the advice to change the returned value" do
|
241
|
-
class ReturningValue
|
242
|
-
def doit args
|
243
|
-
args + ["d"]
|
244
|
-
end
|
245
|
-
end
|
246
245
|
ary = %w[a b c]
|
247
246
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
248
247
|
@aspect = Aspect.new :after_returning, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
@@ -567,25 +566,25 @@ describe Aspect, " with :around advice" do
|
|
567
566
|
advice_called.should be_true
|
568
567
|
end
|
569
568
|
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
# yield *args if block_given?
|
575
|
-
end
|
576
|
-
protected
|
577
|
-
def protected_method *args
|
578
|
-
yield *args if block_given?
|
579
|
-
end
|
580
|
-
private
|
581
|
-
def private_method *args
|
582
|
-
yield *args if block_given?
|
583
|
-
end
|
569
|
+
module AdvisingSuperClass
|
570
|
+
class SuperClass
|
571
|
+
def public_method *args
|
572
|
+
# yield *args if block_given?
|
584
573
|
end
|
585
|
-
|
574
|
+
protected
|
575
|
+
def protected_method *args
|
576
|
+
yield *args if block_given?
|
577
|
+
end
|
578
|
+
private
|
579
|
+
def private_method *args
|
580
|
+
yield *args if block_given?
|
586
581
|
end
|
587
582
|
end
|
588
|
-
|
583
|
+
class SubClass < SuperClass
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
it "should advise subclass invocations of methods advised in the superclass." do
|
589
588
|
child = AdvisingSuperClass::SubClass.new
|
590
589
|
advice_called = false
|
591
590
|
@aspect = Aspect.new :around, :pointcut => {:type => AdvisingSuperClass::SuperClass, :methods => [:public_method]} do |jp, obj, *args|
|
@@ -608,25 +607,25 @@ describe Aspect, " with :around advice" do
|
|
608
607
|
advice_called.should be_true
|
609
608
|
end
|
610
609
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
# yield *args if block_given?
|
616
|
-
end
|
617
|
-
protected
|
618
|
-
def protected_method *args
|
619
|
-
yield *args if block_given?
|
620
|
-
end
|
621
|
-
private
|
622
|
-
def private_method *args
|
623
|
-
yield *args if block_given?
|
624
|
-
end
|
610
|
+
module AdvisingSubClass
|
611
|
+
class SuperClass
|
612
|
+
def public_method *args
|
613
|
+
# yield *args if block_given?
|
625
614
|
end
|
626
|
-
|
615
|
+
protected
|
616
|
+
def protected_method *args
|
617
|
+
yield *args if block_given?
|
618
|
+
end
|
619
|
+
private
|
620
|
+
def private_method *args
|
621
|
+
yield *args if block_given?
|
627
622
|
end
|
628
623
|
end
|
629
|
-
|
624
|
+
class SubClass < SuperClass
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
it "should advise subclass invocations of methods advised in the subclass that are defined in the superclass." do
|
630
629
|
child = AdvisingSubClass::SubClass.new
|
631
630
|
advice_called = false
|
632
631
|
@aspect = Aspect.new :around, :pointcut => {:type => AdvisingSubClass::SuperClass, :methods => [:public_method]} do |jp, obj, *args|
|
@@ -649,18 +648,19 @@ describe Aspect, " with :around advice" do
|
|
649
648
|
advice_called.should be_true
|
650
649
|
end
|
651
650
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
yield(*args) if block_given?
|
657
|
-
end
|
658
|
-
attr_reader :override_called
|
659
|
-
def initialize
|
660
|
-
super
|
661
|
-
@override_called = false
|
662
|
-
end
|
651
|
+
class WatchfulChild2 < Watchful
|
652
|
+
def public_watchful_method *args
|
653
|
+
@override_called = true
|
654
|
+
yield(*args) if block_given?
|
663
655
|
end
|
656
|
+
attr_reader :override_called
|
657
|
+
def initialize
|
658
|
+
super
|
659
|
+
@override_called = false
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
it "should not advise subclass overrides of superclass methods, when advising superclasses (but calls to superclass methods are advised)." do
|
664
664
|
@aspect = Aspect.new(:around, :pointcut => {:type => Watchful, :methods => [:public_watchful_method]}) {|jp, obj, *args| fail}
|
665
665
|
child = WatchfulChild2.new
|
666
666
|
public_block_called = false
|
@@ -697,12 +697,13 @@ describe Aspect, " with :around advice" do
|
|
697
697
|
watchful.public_watchful_method_args.should == [:a4, :a5, :a6]
|
698
698
|
end
|
699
699
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
args + ["d"]
|
704
|
-
end
|
700
|
+
class ReturningValue
|
701
|
+
def doit args
|
702
|
+
args + ["d"]
|
705
703
|
end
|
704
|
+
end
|
705
|
+
|
706
|
+
it "should return the value returned by the advice, NOT the value returned by the advised join point!" do
|
706
707
|
ary = %w[a b c]
|
707
708
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
708
709
|
@aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
@@ -713,11 +714,6 @@ describe Aspect, " with :around advice" do
|
|
713
714
|
end
|
714
715
|
|
715
716
|
it "should return the value returned by the advised join point only if the advice returns the value" do
|
716
|
-
class ReturningValue
|
717
|
-
def doit args
|
718
|
-
args + ["d"]
|
719
|
-
end
|
720
|
-
end
|
721
717
|
ary = %w[a b c]
|
722
718
|
ReturningValue.new.doit(ary).should == %w[a b c d]
|
723
719
|
@aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, obj, *args|
|
@@ -923,8 +919,9 @@ end
|
|
923
919
|
%w[public protected private].each do |protection|
|
924
920
|
describe Aspect, " when advising and unadvising #{protection} methods" do
|
925
921
|
it "should keep the protection level of the advised methods unchanged." do
|
926
|
-
meta = "#{protection}_instance_methods"
|
927
|
-
|
922
|
+
meta = :"#{protection}_instance_methods"
|
923
|
+
method_name = "#{protection}_watchful_method"
|
924
|
+
method = Aquarium::Utils::MethodUtils.to_name method_name
|
928
925
|
Watchful.send(meta).should include(method)
|
929
926
|
aspect = Aspect.new(:after, :type => Watchful, :method => method.intern, :method_options => [protection.intern]) {|jp, obj, *args| true }
|
930
927
|
Watchful.send(meta).should include(method)
|
@@ -935,13 +932,18 @@ end
|
|
935
932
|
end
|
936
933
|
|
937
934
|
|
935
|
+
class TypeDefinedMethodClass
|
936
|
+
def inititalize; @called = false; end
|
937
|
+
def m; @called = true; end
|
938
|
+
attr_reader :called
|
939
|
+
end
|
940
|
+
|
941
|
+
class InstanceDefinedMethodClass
|
942
|
+
def inititalize; @called = false; end
|
943
|
+
attr_reader :called
|
944
|
+
end
|
945
|
+
|
938
946
|
describe Aspect, " when unadvising methods for instance-type pointcuts for type-defined methods" do
|
939
|
-
class TypeDefinedMethodClass
|
940
|
-
def inititalize; @called = false; end
|
941
|
-
def m; @called = true; end
|
942
|
-
attr_reader :called
|
943
|
-
end
|
944
|
-
|
945
947
|
it "should cause the object to respond to the type's original method." do
|
946
948
|
object = TypeDefinedMethodClass.new
|
947
949
|
aspect = Aspect.new(:before, :object => object, :method => :m) {true}
|
@@ -952,11 +954,6 @@ describe Aspect, " when unadvising methods for instance-type pointcuts for type-
|
|
952
954
|
end
|
953
955
|
|
954
956
|
describe Aspect, " when unadvising methods for instance-type pointcuts for instance-defined methods" do
|
955
|
-
class InstanceDefinedMethodClass
|
956
|
-
def inititalize; @called = false; end
|
957
|
-
attr_reader :called
|
958
|
-
end
|
959
|
-
|
960
957
|
it "should cause the object to respond to the object's original method." do
|
961
958
|
object = TypeDefinedMethodClass.new
|
962
959
|
def object.m; @called = true; end
|
@@ -37,7 +37,9 @@ describe "Advising an object join point and then the corresponding type join poi
|
|
37
37
|
rescue ConcurrentlyAccessed::Error
|
38
38
|
fail unless advice_kind == :after_raising
|
39
39
|
end
|
40
|
-
@invoked.
|
40
|
+
@invoked.length.should == 2
|
41
|
+
@invoked.should include(:object)
|
42
|
+
@invoked.should include(:type)
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -95,8 +95,11 @@ def before_pointcut_module_spec
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def ignored_join_point jp
|
98
|
-
# Ignore any
|
99
|
-
|
98
|
+
# Ignore any type ambiguities introduced by Ruby 1.9.X.
|
99
|
+
# Igore types introduced by RSpec, other Aquarium types,
|
100
|
+
# and the "pretty printer" module (which Rake uses?)
|
101
|
+
jp.target_type.name =~ /^(Basic)?Object/ or
|
102
|
+
jp.target_type.name =~ /^R?Spec/ or
|
100
103
|
jp.target_type.name =~ /^Aquarium::(Aspects|Extras|Utils|PointcutFinderTestClasses)/ or
|
101
104
|
jp.target_type.name =~ /^PP/ or
|
102
105
|
jp.target_type.name =~ /InstanceExecHelper/
|
@@ -286,7 +289,7 @@ describe Pointcut, "methods" do
|
|
286
289
|
|
287
290
|
it "should match classes specified and their ancestor and descendent modules and classes." do
|
288
291
|
pc = Pointcut.new :types_and_ancestors => /^Class(Including|DerivedFrom).*Method/, :types_and_descendents => /^Class(Including|DerivedFrom).*Method/, :methods => :all, :method_options => :exclude_ancestor_methods
|
289
|
-
expected_types = @example_modules_with_public_instance_method + [Kernel, Module
|
292
|
+
expected_types = @example_modules_with_public_instance_method + [Kernel, Module]
|
290
293
|
pc.join_points_matched.each do |jp|
|
291
294
|
next if ignored_join_point(jp)
|
292
295
|
expected_types.should include(jp.target_type)
|
@@ -555,15 +558,14 @@ describe Pointcut, "methods" do
|
|
555
558
|
expected_types = [
|
556
559
|
ClassDerivedFromClassIncludingModuleWithPublicInstanceMethod,
|
557
560
|
ClassIncludingModuleWithPublicInstanceMethod,
|
558
|
-
Kernel
|
559
|
-
Object]
|
561
|
+
Kernel]
|
560
562
|
found_types = {}
|
561
563
|
pc.join_points_matched.each do |jp|
|
562
564
|
next if ignored_join_point(jp)
|
563
565
|
expected_types.should include(jp.target_type)
|
564
566
|
found_types[jp.target_type] = true
|
565
567
|
end
|
566
|
-
found_types.size.should ==
|
568
|
+
found_types.size.should == 3
|
567
569
|
not_expected_types = @expected_modules_not_matched_jps.map {|jp| jp.target_type}
|
568
570
|
pc.join_points_not_matched.each do |jp|
|
569
571
|
next if ignored_join_point(jp)
|
@@ -583,14 +585,14 @@ describe Pointcut, "methods" do
|
|
583
585
|
end
|
584
586
|
|
585
587
|
def check_module_descendents pc
|
586
|
-
expected_types = [Kernel
|
588
|
+
expected_types = [Kernel]
|
587
589
|
found_types = {}
|
588
590
|
pc.join_points_matched.each do |jp|
|
589
591
|
next if ignored_join_point(jp)
|
590
592
|
expected_types.should include(jp.target_type)
|
591
593
|
found_types[jp.target_type] = true
|
592
594
|
end
|
593
|
-
found_types.size.should ==
|
595
|
+
found_types.size.should == 1
|
594
596
|
not_expected_types = @expected_modules_not_matched_jps.map {|jp| jp.target_type}
|
595
597
|
pc.join_points_not_matched.each do |jp|
|
596
598
|
next if ignored_join_point(jp)
|
@@ -637,14 +639,14 @@ describe Pointcut, "methods" do
|
|
637
639
|
end
|
638
640
|
|
639
641
|
def check_class_descendents pc
|
640
|
-
expected_types = [Kernel, ModuleIncludingModuleWithPublicInstanceMethod, ModuleWithPublicInstanceMethod
|
642
|
+
expected_types = [Kernel, ModuleIncludingModuleWithPublicInstanceMethod, ModuleWithPublicInstanceMethod]
|
641
643
|
found_types = {}
|
642
644
|
pc.join_points_matched.each do |jp|
|
643
645
|
next if ignored_join_point(jp)
|
644
646
|
expected_types.should include(jp.target_type)
|
645
647
|
found_types[jp.target_type] = true
|
646
648
|
end
|
647
|
-
found_types.size.should ==
|
649
|
+
found_types.size.should == 3
|
648
650
|
not_expected_types = @expected_modules_not_matched_jps.map {|jp| jp.target_type}
|
649
651
|
pc.join_points_not_matched.each do |jp|
|
650
652
|
next if ignored_join_point(jp)
|
@@ -1423,15 +1425,15 @@ describe Pointcut, "methods" do
|
|
1423
1425
|
end
|
1424
1426
|
end
|
1425
1427
|
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
end
|
1428
|
+
class ClassWithFunkyMethodNames
|
1429
|
+
def huh?; true; end
|
1430
|
+
def yes!; true; end
|
1431
|
+
def x= other; false; end
|
1432
|
+
def == other; false; end
|
1433
|
+
def =~ other; false; end
|
1434
|
+
end
|
1434
1435
|
|
1436
|
+
describe Pointcut, ".new (methods that end in non-alphanumeric characters)" do
|
1435
1437
|
before(:each) do
|
1436
1438
|
@funky = ClassWithFunkyMethodNames.new
|
1437
1439
|
end
|
@@ -1725,6 +1727,7 @@ describe Pointcut, "methods" do
|
|
1725
1727
|
pc1 = Pointcut.new :types => /Class.*Method/, :methods => /_test_method$/
|
1726
1728
|
pc2 = Pointcut.new :types => /Class.*Method/, :methods => /_test_method$/
|
1727
1729
|
pc3 = Pointcut.new :objects => [ClassWithPublicInstanceMethod.new, ClassWithPublicInstanceMethod.new]
|
1730
|
+
|
1728
1731
|
pc1.should be_eql(pc1)
|
1729
1732
|
pc1.should be_eql(pc2)
|
1730
1733
|
pc1.should_not eql(pc3)
|
@@ -19,260 +19,252 @@ class CC
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
@c1 = CC.new(1)
|
24
|
-
@c2 = CC.new(2)
|
25
|
-
@c3 = CC.new(3)
|
26
|
-
@cc1 = [@c1, @c2]
|
27
|
-
@cc2 = [@c2, @c3]
|
28
|
-
@hash = {:a => ['a1'], :b => [:b1, :b2], :c => @cc1}
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "intersection of hashes", :shared => true do
|
32
|
-
include Aquarium::Utils::ArrayUtils
|
33
|
-
include Aquarium::Utils::HashUtils
|
34
|
-
|
22
|
+
describe "hash extensions" do
|
35
23
|
before(:each) do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
it "should return the same hash if intersected with an equivalent hash." do
|
44
|
-
@hash.and({:a => ['a1'], :b => [:b1, :b2], :c => @cc1}).should == @hash
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should return an empty hash if one of the input hashes is empty." do
|
48
|
-
{}.and(@hash).should == {}
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should return the common subset hash for two, if the values respond to #&." do
|
52
|
-
hash2 = {:b => [:b1], :c => @cc2, :d => ['d']}
|
53
|
-
@hash.and(hash2).should == {:b => [:b1], :c => [@c2]}
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should return the common subset of hash values for partially-overlapping keys as specified by a given block." do
|
57
|
-
hash2 = {:b =>:b1, :c => @cc2, :d => 'd'}
|
58
|
-
@hash.and(hash2){|value1, value2| Set.new(make_array(value1)).intersection(Set.new(make_array(value2)))}.should == {:b => Set.new([:b1]), :c => Set.new([@c2])}
|
24
|
+
@c1 = CC.new(1)
|
25
|
+
@c2 = CC.new(2)
|
26
|
+
@c3 = CC.new(3)
|
27
|
+
@cc1 = [@c1, @c2]
|
28
|
+
@cc2 = [@c2, @c3]
|
29
|
+
@hash = {:a => ['a1'], :b => [:b1, :b2], :c => @cc1}
|
59
30
|
end
|
60
|
-
end
|
61
31
|
|
62
|
-
|
63
|
-
|
64
|
-
|
32
|
+
shared_examples_for "intersection of hashes" do
|
33
|
+
include Aquarium::Utils::ArrayUtils
|
34
|
+
include Aquarium::Utils::HashUtils
|
65
35
|
|
66
|
-
|
67
|
-
|
68
|
-
end
|
36
|
+
it "should return the same hash if intersected with itself." do
|
37
|
+
@hash.and(@hash).should == @hash
|
38
|
+
end
|
69
39
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
it "should support operator-style semantics" do
|
74
|
-
({:a => ['a1', 'a2'], :c => @cc1} & {:a => ['a1'], :b => [:b1, :b2], :c => @cc2}).should == {:a => ['a1'], :c => [@c2]}
|
75
|
-
end
|
76
|
-
end
|
40
|
+
it "should return the same hash if intersected with an equivalent hash." do
|
41
|
+
@hash.and({:a => ['a1'], :b => [:b1, :b2], :c => @cc1}).should == @hash
|
42
|
+
end
|
77
43
|
|
78
|
-
|
79
|
-
|
80
|
-
|
44
|
+
it "should return an empty hash if one of the input hashes is empty." do
|
45
|
+
{}.and(@hash).should == {}
|
46
|
+
end
|
81
47
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
it "should return the same hash if unioned with itself." do
|
87
|
-
@hash.union(@hash).should == @hash
|
88
|
-
end
|
89
|
-
|
90
|
-
it "should return the same hash if unioned with an equivalent hash." do
|
91
|
-
@hash.union({:a => ['a1'], :b => [:b1, :b2], :c => @cc1}).should == @hash
|
92
|
-
end
|
93
|
-
|
94
|
-
it "should return a hash that is equivalent to the non-empty hash if the other hash is empty." do
|
95
|
-
{}.union(@hash).should == @hash
|
96
|
-
@hash.union({}).should == @hash
|
97
|
-
end
|
98
|
-
|
99
|
-
it "should return the same hash if unioned with nil." do
|
100
|
-
@hash.union(nil).should == @hash
|
101
|
-
end
|
102
|
-
|
103
|
-
it "should return the combined hash value, if the values respond to #|." do
|
104
|
-
hash2 = {:b => [:b3], :c => @cc2, :d => ['d']}
|
105
|
-
@hash.union(hash2).should == {:a => ['a1'], :b => [:b1, :b2, :b3], :c => [@c1, @c2, @c3], :d => ['d']}
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should return a hash equivalent to the output of Hash#merge for two, non-equivalent hashes, with no block given and values don't respond to #|." do
|
109
|
-
hash2 = {:b => :b3, :c => @cc2, :d => 'd'}
|
110
|
-
@hash.union(hash2).should == {:a => ['a1'], :b => :b3, :c => [@c1, @c2, @c3], :d => 'd'}
|
111
|
-
end
|
112
|
-
|
113
|
-
it "should return the combined hash values as specified by a given block." do
|
114
|
-
hash2 = {:b => :b3, :c => @cc2, :d => 'd'}
|
115
|
-
@hash.union(hash2){|value1, value2| Set.new(make_array(value1)).union(Set.new(make_array(value2)))}.should == {:a => Set.new(['a1']), :b => Set.new([:b1, :b2, :b3]), :c => Set.new([@c1, @c2, @c3]), :d => Set.new(['d'])}
|
116
|
-
end
|
117
|
-
end
|
48
|
+
it "should return the common subset hash for two, if the values respond to #&." do
|
49
|
+
hash2 = {:b => [:b1], :c => @cc2, :d => ['d']}
|
50
|
+
@hash.and(hash2).should == {:b => [:b1], :c => [@c2]}
|
51
|
+
end
|
118
52
|
|
119
|
-
|
120
|
-
|
121
|
-
|
53
|
+
it "should return the common subset of hash values for partially-overlapping keys as specified by a given block." do
|
54
|
+
hash2 = {:b =>:b1, :c => @cc2, :d => 'd'}
|
55
|
+
@hash.and(hash2){|value1, value2| Set.new(make_array(value1)).intersection(Set.new(make_array(value2)))}.should == {:b => Set.new([:b1]), :c => Set.new([@c2])}
|
56
|
+
end
|
57
|
+
end
|
122
58
|
|
123
|
-
describe Hash, "#
|
124
|
-
|
125
|
-
end
|
59
|
+
describe Hash, "#intersection" do
|
60
|
+
it_should_behave_like "intersection of hashes"
|
61
|
+
end
|
126
62
|
|
127
|
-
describe Hash, "
|
128
|
-
|
129
|
-
|
130
|
-
it "should support operator-style semantics" do
|
131
|
-
({:a => ['a1'], :d => ['d']} | {:a => ['a2'], :b => [:b1, :b2], :c => @cc2}).should == {:a => ['a1', 'a2'], :b => [:b1, :b2], :c => @cc2, :d => ['d']}
|
63
|
+
describe Hash, "#and" do
|
64
|
+
it_should_behave_like "intersection of hashes"
|
132
65
|
end
|
133
|
-
end
|
134
66
|
|
67
|
+
describe Hash, "#&" do
|
68
|
+
it_should_behave_like "intersection of hashes"
|
69
|
+
|
70
|
+
it "should support operator-style semantics" do
|
71
|
+
({:a => ['a1', 'a2'], :c => @cc1} & {:a => ['a1'], :b => [:b1, :b2], :c => @cc2}).should == {:a => ['a1'], :c => [@c2]}
|
72
|
+
end
|
73
|
+
end
|
135
74
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
75
|
+
shared_examples_for "union of hashes" do
|
76
|
+
include Aquarium::Utils::ArrayUtils
|
77
|
+
include Aquarium::Utils::HashUtils
|
78
|
+
|
79
|
+
it "should return the same hash if unioned with itself." do
|
80
|
+
@hash.union(@hash).should == @hash
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should return the same hash if unioned with an equivalent hash." do
|
84
|
+
@hash.union({:a => ['a1'], :b => [:b1, :b2], :c => @cc1}).should == @hash
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return a hash that is equivalent to the non-empty hash if the other hash is empty." do
|
88
|
+
{}.union(@hash).should == @hash
|
89
|
+
@hash.union({}).should == @hash
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return the same hash if unioned with nil." do
|
93
|
+
@hash.union(nil).should == @hash
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return the combined hash value, if the values respond to #|." do
|
97
|
+
hash2 = {:b => [:b3], :c => @cc2, :d => ['d']}
|
98
|
+
@hash.union(hash2).should == {:a => ['a1'], :b => [:b1, :b2, :b3], :c => [@c1, @c2, @c3], :d => ['d']}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return a hash equivalent to the output of Hash#merge for two, non-equivalent hashes, with no block given and values don't respond to #|." do
|
102
|
+
hash2 = {:b => :b3, :c => @cc2, :d => 'd'}
|
103
|
+
@hash.union(hash2).should == {:a => ['a1'], :b => :b3, :c => [@c1, @c2, @c3], :d => 'd'}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should return the combined hash values as specified by a given block." do
|
107
|
+
hash2 = {:b => :b3, :c => @cc2, :d => 'd'}
|
108
|
+
@hash.union(hash2){|value1, value2| Set.new(make_array(value1)).union(Set.new(make_array(value2)))}.should == {:a => Set.new(['a1']), :b => Set.new([:b1, :b2, :b3]), :c => Set.new([@c1, @c2, @c3]), :d => Set.new(['d'])}
|
109
|
+
end
|
144
110
|
end
|
145
|
-
|
146
|
-
it "should return an empty hash if subtracted from itself." do
|
147
|
-
(@hash - @hash).should be_empty
|
148
|
-
end
|
149
111
|
|
150
|
-
|
151
|
-
|
152
|
-
end
|
112
|
+
describe Hash, "#union" do
|
113
|
+
it_should_behave_like "union of hashes"
|
114
|
+
end
|
153
115
|
|
154
|
-
|
155
|
-
|
156
|
-
end
|
116
|
+
describe Hash, "#or" do
|
117
|
+
it_should_behave_like "union of hashes"
|
118
|
+
end
|
157
119
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
|
166
|
-
it "should return a hash with all keys in the second hash removed, independent of the corresponding values, if no block is given." do
|
167
|
-
hash2 = {:b =>:b3, :c => 'c', :d => 'd'}
|
168
|
-
(@hash - hash2).should == {:a => ['a1', 'a2']}
|
169
|
-
end
|
170
|
-
|
171
|
-
it "should return a hash with the values subtraced for partially-overlapping keys as specified by a given block." do
|
172
|
-
hash2 = {:b =>:b2, :c => @cc2, :d => 'd'}
|
173
|
-
@hash.minus(hash2) do |value1, value2|
|
174
|
-
Set.new(make_array(value1)) - Set.new(make_array(value2))
|
175
|
-
end.should == {:a => Set.new(['a1', 'a2']), :b => Set.new([:b1]), :c => Set.new([@c1])}
|
176
|
-
end
|
177
|
-
|
178
|
-
it "should be not associative." do
|
179
|
-
hash1 = {:a => :a1, :b =>:b1, :c => :c1, :d => :d1}
|
180
|
-
hash2 = {:b =>:b3, :c => 'c'}
|
181
|
-
hash3 = {:a =>:a4, :c => 'c'}
|
182
|
-
((hash1 - hash2) - hash3).should == {:d => :d1}
|
183
|
-
(hash1 - (hash2 - hash3)).should == {:a => :a1, :c => :c1, :d => :d1}
|
184
|
-
end
|
185
|
-
end
|
120
|
+
describe Hash, "#|" do
|
121
|
+
it_should_behave_like "union of hashes"
|
122
|
+
|
123
|
+
it "should support operator-style semantics" do
|
124
|
+
({:a => ['a1'], :d => ['d']} | {:a => ['a2'], :b => [:b1, :b2], :c => @cc2}).should == {:a => ['a1', 'a2'], :b => [:b1, :b2], :c => @cc2, :d => ['d']}
|
125
|
+
end
|
126
|
+
end
|
186
127
|
|
187
|
-
describe Hash, "#minus" do
|
188
|
-
it_should_behave_like "subtraction of hashes"
|
189
|
-
end
|
190
128
|
|
191
|
-
|
192
|
-
|
129
|
+
shared_examples_for "subtraction of hashes" do
|
130
|
+
include Aquarium::Utils::ArrayUtils
|
131
|
+
include Aquarium::Utils::HashUtils
|
132
|
+
|
133
|
+
before(:each) do
|
134
|
+
# override value:
|
135
|
+
@hash = {:a => ['a1', 'a2'], :b => [:b1, :b2], :c => @cc1}
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should return an empty hash if subtracted from itself." do
|
139
|
+
(@hash - @hash).should be_empty
|
140
|
+
end
|
193
141
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
hash3 = {:a =>:a4, :c => 'c'}
|
198
|
-
((hash1 - hash2) - hash3).should == {:d => :d1}
|
199
|
-
(hash1 - (hash2 - hash3)).should == {:a => :a1, :c => :c1, :d => :d1}
|
200
|
-
end
|
201
|
-
end
|
142
|
+
it "should return an empty hash if an equivalent hash is subtracted from it." do
|
143
|
+
(@hash - {:a => ['a1', 'a2'], :b => [:b1, :b2], :c => @cc1}).should be_empty
|
144
|
+
end
|
202
145
|
|
203
|
-
|
204
|
-
|
205
|
-
|
146
|
+
it "should return an equivalent hash if the second hash is empty." do
|
147
|
+
(@hash - {}).should == @hash
|
148
|
+
end
|
206
149
|
|
207
|
-
|
208
|
-
|
209
|
-
|
150
|
+
it "should return an equivalent hash if the second hash is nil." do
|
151
|
+
(@hash - nil).should == @hash
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should return an empty hash if the first hash is empty, independent of the second hash." do
|
155
|
+
({} - @hash).should be_empty
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should return a hash with all keys in the second hash removed, independent of the corresponding values, if no block is given." do
|
159
|
+
hash2 = {:b =>:b3, :c => 'c', :d => 'd'}
|
160
|
+
(@hash - hash2).should == {:a => ['a1', 'a2']}
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should return a hash with the values subtraced for partially-overlapping keys as specified by a given block." do
|
164
|
+
hash2 = {:b =>:b2, :c => @cc2, :d => 'd'}
|
165
|
+
@hash.minus(hash2) do |value1, value2|
|
166
|
+
Set.new(make_array(value1)) - Set.new(make_array(value2))
|
167
|
+
end.should == {:a => Set.new(['a1', 'a2']), :b => Set.new([:b1]), :c => Set.new([@c1])}
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should be not associative." do
|
171
|
+
hash1 = {:a => :a1, :b =>:b1, :c => :c1, :d => :d1}
|
172
|
+
hash2 = {:b =>:b3, :c => 'c'}
|
173
|
+
hash3 = {:a =>:a4, :c => 'c'}
|
174
|
+
((hash1 - hash2) - hash3).should == {:d => :d1}
|
175
|
+
(hash1 - (hash2 - hash3)).should == {:a => :a1, :c => :c1, :d => :d1}
|
176
|
+
end
|
210
177
|
end
|
211
178
|
|
212
|
-
|
213
|
-
|
214
|
-
h2={"1" => :a1, "2" => :a2, "3" => :a3}
|
215
|
-
h1.eql_when_keys_compared?(h2).should == true
|
179
|
+
describe Hash, "#minus" do
|
180
|
+
it_should_behave_like "subtraction of hashes"
|
216
181
|
end
|
217
182
|
|
218
|
-
|
219
|
-
|
220
|
-
h2={"1" => :a1, "2" => :a2, "3" => /b/}
|
221
|
-
h1.eql_when_keys_compared?(h2).should == false
|
222
|
-
end
|
183
|
+
describe Hash, "#-" do
|
184
|
+
it_should_behave_like "subtraction of hashes"
|
223
185
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
186
|
+
it "should support operator-style semantics" do
|
187
|
+
hash1 = {:a => :a1, :b =>:b1, :c => :c1, :d => :d1}
|
188
|
+
hash2 = {:b =>:b3, :c => 'c'}
|
189
|
+
hash3 = {:a =>:a4, :c => 'c'}
|
190
|
+
((hash1 - hash2) - hash3).should == {:d => :d1}
|
191
|
+
(hash1 - (hash2 - hash3)).should == {:a => :a1, :c => :c1, :d => :d1}
|
192
|
+
end
|
229
193
|
end
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
194
|
+
|
195
|
+
describe Hash, "#eql_when_keys_compared?" do
|
196
|
+
include Aquarium::Utils::ArrayUtils
|
197
|
+
include Aquarium::Utils::HashUtils
|
198
|
+
|
199
|
+
it "should return true when comparing a hash to itself." do
|
200
|
+
h1={"1" => :a1, "2" => :a2, "3" => :a3}
|
201
|
+
h1.eql_when_keys_compared?(h1).should == true
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should return true for hashes with string keys that satisfy String#==." do
|
205
|
+
h1={"1" => :a1, "2" => :a2, "3" => :a3}
|
206
|
+
h2={"1" => :a1, "2" => :a2, "3" => :a3}
|
207
|
+
h1.eql_when_keys_compared?(h2).should == true
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should return false for hashes with matching keys, but different values." do
|
211
|
+
h1={"1" => :a1, "2" => :a2, "3" => /a/}
|
212
|
+
h2={"1" => :a1, "2" => :a2, "3" => /b/}
|
213
|
+
h1.eql_when_keys_compared?(h2).should == false
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should return false for hashes where one hash is a subset of the other." do
|
217
|
+
h1={"1" => :a1, "2" => :a2}
|
218
|
+
h2={"1" => :a1, "2" => :a2, "3" => :a3}
|
219
|
+
h1.eql_when_keys_compared?(h2).should == false
|
220
|
+
h2.eql_when_keys_compared?(h1).should == false
|
243
221
|
end
|
244
222
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
223
|
+
it "should return true for hashes with Object keys that define a #<=> method, while Hash#eql? would return false." do
|
224
|
+
class Key
|
225
|
+
def initialize key
|
226
|
+
@key = key
|
227
|
+
end
|
228
|
+
attr_reader :key
|
229
|
+
def eql? other
|
230
|
+
key.eql? other.key
|
231
|
+
end
|
232
|
+
def <=> other
|
233
|
+
key <=> other.key
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
h1 = {}; h2 = {}
|
238
|
+
(0...4).each do |index|
|
239
|
+
h1[Key.new(index)] = {index.to_s => [index, index+1]}
|
240
|
+
h2[Key.new(index)] = {index.to_s => [index, index+1]}
|
241
|
+
end
|
242
|
+
h1.eql_when_keys_compared?(h2).should == true
|
243
|
+
h1.eql?(h2).should == false
|
249
244
|
end
|
250
|
-
h1.eql_when_keys_compared?(h2).should == true
|
251
|
-
h1.eql?(h2).should == false
|
252
245
|
end
|
253
|
-
end
|
254
246
|
|
255
|
-
describe Hash, "#equivalent_key" do
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
247
|
+
describe Hash, "#equivalent_key" do
|
248
|
+
it "should return the key in the hash for which input_value#==(key) is true." do
|
249
|
+
class Key
|
250
|
+
def initialize key
|
251
|
+
@key = key
|
252
|
+
end
|
253
|
+
attr_reader :key
|
254
|
+
def eql? other
|
255
|
+
key.eql? other.key
|
256
|
+
end
|
257
|
+
alias :== :eql?
|
260
258
|
end
|
261
|
-
|
262
|
-
|
263
|
-
|
259
|
+
|
260
|
+
h1 = {}; h2 = {}
|
261
|
+
(0...4).each do |index|
|
262
|
+
h1[Key.new(index)] = {index.to_s => [index, index+1]}
|
263
|
+
h2[Key.new(index)] = {index.to_s => [index, index+1]}
|
264
264
|
end
|
265
|
-
|
265
|
+
h1[Key.new(0)].should be_nil
|
266
|
+
h1.equivalent_key(Key.new(0)).should_not be_nil
|
267
|
+
h1.equivalent_key(Key.new(5)).should be_nil
|
266
268
|
end
|
267
|
-
|
268
|
-
h1 = {}; h2 = {}
|
269
|
-
(0...4).each do |index|
|
270
|
-
h1[Key.new(index)] = {index.to_s => [index, index+1]}
|
271
|
-
h2[Key.new(index)] = {index.to_s => [index, index+1]}
|
272
|
-
end
|
273
|
-
h1[Key.new(0)].should be_nil
|
274
|
-
h1.equivalent_key(Key.new(0)).should_not be_nil
|
275
|
-
h1.equivalent_key(Key.new(5)).should be_nil
|
276
269
|
end
|
277
|
-
|
278
270
|
end
|