aquarium 0.1.5 → 0.1.6

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.
@@ -70,10 +70,11 @@ module Aquarium
70
70
  init_specification options
71
71
  init_candidate_types
72
72
  init_candidate_objects
73
+ init_candidate_join_points
73
74
  init_join_points
74
75
  end
75
76
 
76
- attr_reader :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects
77
+ attr_reader :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects, :candidate_join_points
77
78
 
78
79
  # Two Considered equivalent only if the same join points matched and not_matched sets are equal,
79
80
  # the specifications are equal, and the candidate types and candidate objects are equal.
@@ -111,15 +112,17 @@ module Aquarium
111
112
 
112
113
  protected
113
114
 
114
- attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects
115
+ attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects, :candidate_join_points
115
116
 
116
117
  def init_specification options
117
118
  @specification = {}
118
119
  options ||= {}
120
+ validate_options options
119
121
  @specification[:method_options] = Set.new(make_array(options[:method_options]))
120
122
  @specification[:attribute_options] = Set.new(make_array(options[:attribute_options]) )
121
123
  @specification[:types] = Set.new(make_array(options[:types], options[:type]))
122
124
  @specification[:objects] = Set.new(make_array(options[:objects], options[:object]))
125
+ @specification[:join_points] = Set.new(make_array(options[:join_points], options[:join_point]))
123
126
  @specification[:default_object] = Set.new(make_array(options[:default_object]))
124
127
  use_default_object_if_defined unless (types_given? || objects_given?)
125
128
  @specification[:attributes] = Set.new(make_array(options[:attributes], options[:attribute]))
@@ -131,6 +134,12 @@ module Aquarium
131
134
  @specification[:methods] = Set.new(make_array(options[:methods], options[:method]))
132
135
  @specification[:methods].add(:all) if @specification[:methods].empty? and @specification[:attributes].empty?
133
136
  end
137
+
138
+ def validate_options options
139
+ knowns = %w[object objects type types join_point join_points method methods attribute attributes method_options attribute_options default_object].map {|x| x.intern}
140
+ unknowns = options.keys - knowns
141
+ raise Aquarium::Utils::InvalidOptions.new("Unknown options specified: #{unknowns.inspect}") if unknowns.size > 0
142
+ end
134
143
 
135
144
  def self.read_only attribute_options
136
145
  read_option(attribute_options) && !write_option(attribute_options)
@@ -148,7 +157,7 @@ module Aquarium
148
157
  attribute_options.include?(:writers) || attribute_options.include?(:writer)
149
158
  end
150
159
 
151
- %w[types objects methods attributes method_options attribute_options].each do |name|
160
+ %w[types objects join_points methods attributes method_options attribute_options].each do |name|
152
161
  class_eval(<<-EOF, __FILE__, __LINE__)
153
162
  def #{name}_given
154
163
  @specification[:#{name}]
@@ -164,7 +173,7 @@ module Aquarium
164
173
 
165
174
  def init_candidate_types
166
175
  explicit_types, type_regexps_or_names = @specification[:types].partition do |type|
167
- type.kind_of?(Module) || type.kind_of?(Class)
176
+ is_type? type
168
177
  end
169
178
  @candidate_types = Aquarium::Finders::TypeFinder.new.find :types => type_regexps_or_names
170
179
  @candidate_types.append_matched(make_hash(explicit_types) {|x| Set.new([])}) # Append already-known types
@@ -175,33 +184,53 @@ module Aquarium
175
184
  @specification[:objects].each {|o| object_hash[o] = Set.new([])}
176
185
  @candidate_objects = Aquarium::Finders::FinderResult.new object_hash
177
186
  end
187
+
188
+ def init_candidate_join_points
189
+ @candidate_join_points = Aquarium::Finders::FinderResult.new
190
+ @specification[:join_points].each do |jp|
191
+ if jp.exists?
192
+ @candidate_join_points.matched[jp] = Set.new([])
193
+ else
194
+ @candidate_join_points.not_matched[jp] = Set.new([])
195
+ end
196
+ end
197
+ end
178
198
 
179
199
  def init_join_points
180
200
  @join_points_matched = Set.new
181
201
  @join_points_not_matched = Set.new
182
- results = find_methods_for_types make_all_method_names
183
- add_join_points @join_points_matched, results.matched, :type
184
- add_join_points @join_points_not_matched, results.not_matched, :type
185
- results = find_methods_for_objects make_all_method_names
186
- add_join_points @join_points_matched, results.matched, :object
187
- add_join_points @join_points_not_matched, results.not_matched, :object
202
+ find_join_points_for :type, candidate_types, make_all_method_names
203
+ find_join_points_for :object, candidate_objects, make_all_method_names
204
+ add_join_points_for_candidate_join_points
188
205
  end
189
206
 
190
- def find_methods_for_types which_methods
191
- return Aquarium::Finders::FinderResult::NIL_OBJECT if candidate_types.matched.size == 0
192
- Aquarium::Finders::MethodFinder.new.find :types => candidate_types.matched_keys,
193
- :methods => which_methods,
194
- :options => @specification[:method_options].to_a
207
+ def is_type? candidate_type
208
+ candidate_type.kind_of?(Module) || candidate_type.kind_of?(Class)
195
209
  end
196
-
197
- def find_methods_for_objects which_methods
198
- return Aquarium::Finders::FinderResult::NIL_OBJECT if candidate_objects.matched.size == 0
199
- Aquarium::Finders::MethodFinder.new.find :objects => candidate_objects.matched_keys,
210
+
211
+ def add_join_points_for_candidate_join_points
212
+ @join_points_matched += @candidate_join_points.matched.keys
213
+ @join_points_not_matched += @candidate_join_points.not_matched.keys
214
+ end
215
+
216
+ def find_join_points_for type_or_object_sym, candidates, method_names
217
+ results = find_methods_for type_or_object_sym, candidates, method_names
218
+ add_join_points results, type_or_object_sym
219
+ end
220
+
221
+ def find_methods_for type_or_object_sym, candidates, which_methods
222
+ return Aquarium::Finders::FinderResult::NIL_OBJECT if candidates.matched.size == 0
223
+ Aquarium::Finders::MethodFinder.new.find type_or_object_sym => candidates.matched_keys,
200
224
  :methods => which_methods,
201
225
  :options => @specification[:method_options].to_a
202
226
  end
203
227
 
204
- def add_join_points which_join_points_list, results_hash, type_or_object_sym
228
+ def add_join_points search_results, type_or_object_sym
229
+ add_join_points_to @join_points_matched, search_results.matched, type_or_object_sym
230
+ add_join_points_to @join_points_not_matched, search_results.not_matched, type_or_object_sym
231
+ end
232
+
233
+ def add_join_points_to which_join_points_list, results_hash, type_or_object_sym
205
234
  instance_method = @specification[:method_options].include?(:class) ? false : true
206
235
  results_hash.each_pair do |type_or_object, method_name_list|
207
236
  method_name_list.each do |method_name|
@@ -30,8 +30,9 @@ module Aquarium
30
30
  message = handle_message_arg args
31
31
  Aspect.new make_args(:around, *args) do |jp, *params|
32
32
  DesignByContract.test_condition "invariant failure (before invocation): #{message}", jp, *params, &contract_block
33
- jp.proceed
33
+ result = jp.proceed
34
34
  DesignByContract.test_condition "invariant failure (after invocation): #{message}", jp, *params, &contract_block
35
+ result
35
36
  end
36
37
  end
37
38
 
@@ -29,6 +29,26 @@ module Aquarium
29
29
  #
30
30
  # Actually, there is actually no difference between <tt>:types</tt>,
31
31
  # <tt>:type</tt>, <tt>:names</tt>, and <tt>:name</tt>. The extra forms are "sugar"...
32
+ #
33
+ # Because of the special sigificance of the module ("namespace") separator "::", the rules
34
+ # for the regular expressions are as follows. Assume that "subexp" is a "sub regular
35
+ # expression" that results if you split on the separator "::".
36
+ #
37
+ # A full regexp with no "::"::
38
+ # Allow partial matches, <i>i.e.</i>, as if you wrote <tt>/^.*#{regexp}.*$/.</tt>
39
+ #
40
+ # A subexp before the first "::"::
41
+ # It behaves as <tt>/^.*#{subexp}::.../</tt>, meaning that the end of "subexp"
42
+ # must be followed by "::".
43
+ #
44
+ # A subexp after the last "::"::
45
+ # It behaves as <tt>/...::#{subexp}$/</tt>, meaning that the beginning of "subexp"
46
+ # must immediately follow a "::".
47
+ #
48
+ # For a subexp between two "::"::
49
+ # It behaves as <tt>/...::#{subexp}::.../</tt>, meaning that the subexp must match
50
+ # the whole name between the "::" exactly.
51
+ #
32
52
  def find options = {}
33
53
  result = Aquarium::Finders::FinderResult.new
34
54
  unknown_options = []
@@ -41,7 +61,7 @@ module Aquarium
41
61
  end
42
62
  end
43
63
  raise Aquarium::Utils::InvalidOptions.new("Unknown options: #{unknown_options.inspect}.") if unknown_options.size > 0
44
- return result
64
+ result
45
65
  end
46
66
 
47
67
  # For a name (not a regular expression), return the corresponding type.
@@ -68,11 +88,10 @@ module Aquarium
68
88
  expressions.each do |expression|
69
89
  expr = strip expression
70
90
  next if empty expr
71
- result_for_expression = find_namespace_matched expr
72
- if result_for_expression.size > 0
73
- result.append_matched result_for_expression
91
+ if expr.kind_of? Regexp
92
+ result << find_namespace_matched(expr)
74
93
  else
75
- result.append_not_matched({expression => Set.new([])})
94
+ result << find_by_name(expr)
76
95
  end
77
96
  end
78
97
  result
@@ -94,24 +113,33 @@ module Aquarium
94
113
  end
95
114
 
96
115
  def find_namespace_matched expression
97
- return {} if expression.nil?
116
+ expr = expression.kind_of?(Regexp) ? expression.source : expression.to_s
117
+ return nil if expr.empty?
98
118
  found_types = [Module]
99
- expr = expression.class.eql?(Regexp) ? expression.source : expression.to_s
100
- return {} if expr.empty?
101
- expr.split("::").each do |subexp|
102
- found_types = find_next_types found_types, subexp
119
+ split_expr = expr.split("::")
120
+ split_expr.each_with_index do |subexp, index|
121
+ next if subexp.size == 0
122
+ found_types = find_next_types found_types, subexp, (index == 0), (index == (split_expr.size - 1))
103
123
  break if found_types.size == 0
104
124
  end
105
- make_return_hash found_types, []
125
+ if found_types.size > 0
126
+ Aquarium::Finders::FinderResult.new make_return_hash(found_types, [])
127
+ else
128
+ Aquarium::Finders::FinderResult.new :not_matched => {expression => Set.new([])}
129
+ end
106
130
  end
107
131
 
108
- def find_next_types parent_types, subname
109
- # grep <parent>.constants because "subname" may be a regexp string!.
132
+ def find_next_types enclosing_types, subexp, suppress_lh_ctrl_a, suppress_rh_ctrl_z
133
+ # grep <parent>.constants because "subexp" may be a regexp string!.
110
134
  # Then use const_get to get the type itself.
111
135
  found_types = []
112
- parent_types.each do |parent|
113
- matched = parent.constants.grep(/^#{subname}$/)
114
- matched.each {|m| found_types << parent.const_get(m)}
136
+ lhs = suppress_lh_ctrl_a ? "" : "\\A"
137
+ rhs = suppress_rh_ctrl_z ? "" : "\\Z"
138
+ regexp = /#{lhs}#{subexp}#{rhs}/
139
+ enclosing_types.each do |parent|
140
+ parent.constants.grep(regexp).each do |m|
141
+ found_types << get_type_from_parent(parent, m, regexp)
142
+ end
115
143
  end
116
144
  found_types
117
145
  end
@@ -122,6 +150,17 @@ module Aquarium
122
150
  found.each {|x| h[x] = Set.new([])}
123
151
  h
124
152
  end
153
+
154
+ protected
155
+ def get_type_from_parent parent, name, regexp
156
+ begin
157
+ parent.const_get(name)
158
+ rescue => e
159
+ msg = "ERROR: for enclosing type '#{parent.inspect}', #{parent.inspect}.constants.grep(/#{regexp.source}/) returned a list including #{name.inspect}."
160
+ msg += "However, #{parent.inspect}.const_get('#{name}') raised exception #{e}. Please report this bug to the Aquarium team. Thanks."
161
+ raise e.exception(msg)
162
+ end
163
+ end
125
164
  end
126
165
  end
127
166
  end
@@ -37,7 +37,7 @@ module Aquarium
37
37
  limits = class_or_instance_only.nil? ? [:instance_method_only, :class_method_only] : [class_or_instance_only]
38
38
  meta_method_suffixes = []
39
39
  limits.each do |limit|
40
- if (type_or_instance.kind_of?(Class) || type_or_instance.kind_of?(Module))
40
+ if (is_type? type_or_instance)
41
41
  meta_method_suffixes << "instance_methods" if limit == :instance_method_only
42
42
  meta_method_suffixes << "methods" if limit == :class_method_only
43
43
  else
@@ -47,6 +47,10 @@ module Aquarium
47
47
  meta_method_suffixes
48
48
  end
49
49
 
50
+ def self.is_type? type_or_instance
51
+ type_or_instance.kind_of?(Class) || type_or_instance.kind_of?(Module)
52
+ end
53
+
50
54
  def self.find_method2 type_or_instance, method_sym, meta_method
51
55
  type_or_instance.send(meta_method, method_sym.to_s)
52
56
  end
@@ -9,7 +9,7 @@ module Aquarium
9
9
  unless defined? MAJOR
10
10
  MAJOR = 0
11
11
  MINOR = 1
12
- TINY = 5
12
+ TINY = 6
13
13
  RELEASE_CANDIDATE = nil
14
14
 
15
15
  # RANDOM_TOKEN: 0.598704893979657
@@ -72,6 +72,18 @@ describe Aspect, "#new arguments for specifying the types and methods" do
72
72
  aspect1.advice.should eql(aspect2.advice)
73
73
  aspect1.unadvise
74
74
  end
75
+
76
+ it "should advise an equivalent join point when :type => T and :method => m is used or :pointcut => join_point is used, where join_point matches :type => T and :method => m." do
77
+ # pending "working on Pointcut.new first."
78
+ advice = proc {|jp,*args| "advice"}
79
+ aspect1 = Aspect.new :after, :type => Watchful, :method => :public_watchful_method, &advice
80
+ join_point = Aquarium::Aspects::JoinPoint.new :type => Watchful, :method => :public_watchful_method
81
+ aspect2 = Aspect.new :after, :pointcut => join_point, &advice
82
+ aspect1.join_points_matched.should eql(aspect2.join_points_matched)
83
+ aspect1.advice.should eql(aspect2.advice)
84
+ aspect1.unadvise
85
+ end
86
+
75
87
  end
76
88
 
77
89
  describe Aspect, "#new arguments for specifying the objects and methods" do
@@ -805,17 +805,75 @@ describe Aspect, "#new with :around advice" do
805
805
  end
806
806
 
807
807
  it "should advise subclass invocations of methods advised in the superclass." do
808
+ module AdvisingSuperClass
809
+ class SuperClass
810
+ def public_method *args
811
+ yield *args if block_given?
812
+ end
813
+ protected
814
+ def protected_method *args
815
+ yield *args if block_given?
816
+ end
817
+ private
818
+ def private_method *args
819
+ yield *args if block_given?
820
+ end
821
+ end
822
+ class SubClass < SuperClass
823
+ end
824
+ end
825
+
808
826
  context = nil
809
- @aspect = Aspect.new :around, :pointcut => {:type => Watchful, :methods => [:public_watchful_method]} do |jp, *args|
827
+ @aspect = Aspect.new :around, :pointcut => {:type => AdvisingSuperClass::SuperClass, :methods => [:public_method]} do |jp, *args|
810
828
  context = jp.context
811
829
  end
812
- child = WatchfulChild.new
830
+ child = AdvisingSuperClass::SubClass.new
813
831
  public_block_called = false
814
832
  protected_block_called = false
815
833
  private_block_called = false
816
- child.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| fail }
817
- child.send(:protected_watchful_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
818
- child.send(:private_watchful_method, :c1, :c2, :c3) {|*args| private_block_called = true}
834
+ child.public_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| fail }
835
+ child.send(:protected_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
836
+ child.send(:private_method, :c1, :c2, :c3) {|*args| private_block_called = true}
837
+ public_block_called.should be_false # proceed is never called!
838
+ protected_block_called.should be_true
839
+ private_block_called.should be_true
840
+ context.advised_object.should == child
841
+ context.advice_kind.should == :around
842
+ context.returned_value.should == nil
843
+ context.parameters.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
844
+ context.raised_exception.should == nil
845
+ end
846
+
847
+ it "should advise subclass invocations of methods advised in the subclass that are defined in the superclass." do
848
+ module AdvisingSubClass
849
+ class SuperClass
850
+ def public_method *args
851
+ yield *args if block_given?
852
+ end
853
+ protected
854
+ def protected_method *args
855
+ yield *args if block_given?
856
+ end
857
+ private
858
+ def private_method *args
859
+ yield *args if block_given?
860
+ end
861
+ end
862
+ class SubClass < SuperClass
863
+ end
864
+ end
865
+
866
+ context = nil
867
+ @aspect = Aspect.new :around, :pointcut => {:type => AdvisingSubClass::SuperClass, :methods => [:public_method]} do |jp, *args|
868
+ context = jp.context
869
+ end
870
+ child = AdvisingSubClass::SubClass.new
871
+ public_block_called = false
872
+ protected_block_called = false
873
+ private_block_called = false
874
+ child.public_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| fail }
875
+ child.send(:protected_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
876
+ child.send(:private_method, :c1, :c2, :c3) {|*args| private_block_called = true}
819
877
  public_block_called.should be_false # proceed is never called!
820
878
  protected_block_called.should be_true
821
879
  private_block_called.should be_true
@@ -874,7 +932,7 @@ describe Aspect, "#new with :around advice" do
874
932
  watchful.public_watchful_method_args.should == [:a4, :a5, :a6]
875
933
  end
876
934
 
877
- it "should return the value returned by the advice" do
935
+ it "should return the value returned by the advice, NOT the value returned by the advised join point!" do
878
936
  class ReturningValue
879
937
  def doit args
880
938
  args + ["d"]
@@ -883,9 +941,28 @@ describe Aspect, "#new with :around advice" do
883
941
  ary = %w[a b c]
884
942
  ReturningValue.new.doit(ary).should == %w[a b c d]
885
943
  @aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, *args|
886
- %w[aa] + jp.proceed + %w[e]
944
+ jp.proceed
945
+ %w[aa bb cc]
887
946
  end
888
- ReturningValue.new.doit(ary).should == %w[aa a b c d e]
947
+ ReturningValue.new.doit(ary).should == %w[aa bb cc]
948
+ end
949
+
950
+ it "should return the value returned by the advised join point only if the advice returns the value" do
951
+ class ReturningValue
952
+ def doit args
953
+ args + ["d"]
954
+ end
955
+ end
956
+ ary = %w[a b c]
957
+ ReturningValue.new.doit(ary).should == %w[a b c d]
958
+ @aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, *args|
959
+ begin
960
+ jp.proceed
961
+ ensure
962
+ %w[aa bb cc]
963
+ end
964
+ end
965
+ ReturningValue.new.doit(ary).should == %w[a b c d]
889
966
  end
890
967
 
891
968
  def do_around_spec *args_passed_to_proceed
@@ -0,0 +1,49 @@
1
+
2
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
3
+ require File.dirname(__FILE__) + '/../spec_example_classes'
4
+ require 'aquarium/aspects'
5
+
6
+ include Aquarium::Aspects
7
+
8
+ # Explicitly check that advising subtypes works correctly.
9
+
10
+ module SubTypeAspects
11
+ class Base
12
+ attr_reader :base_state
13
+ def doit *args
14
+ @base_state = args
15
+ yield args
16
+ end
17
+ end
18
+
19
+ class Derived < Base
20
+ attr_reader :derived_state
21
+ def doit *args
22
+ @derived_state = args
23
+ super
24
+ yield args
25
+ end
26
+ end
27
+ end
28
+
29
+ describe Aspect, " when advising overridden methods that call super" do
30
+ after(:each) do
31
+ @aspect.unadvise if @aspect
32
+ end
33
+
34
+ it "should correctly invoke and advise subclass and superclass methods." do
35
+ advised_types = []
36
+ @aspect = Aspect.new :before, :pointcut => {:types => /SubTypeAspects::.*/, :methods => :doit} do |jp, *args|
37
+ advised_types << jp.target_type
38
+ end
39
+ derived = SubTypeAspects::Derived.new
40
+ block_called = 0
41
+ derived.doit(:a1, :a2, :a3) { |*args| block_called += 1 }
42
+ block_called.should == 2
43
+ advised_types.should == [SubTypeAspects::Derived, SubTypeAspects::Base]
44
+ derived.base_state.should == [:a1, :a2, :a3]
45
+ derived.derived_state.should == [:a1, :a2, :a3]
46
+ end
47
+ end
48
+
49
+