aquarium 0.4.4 → 0.5.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.
- 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
|