aquarium 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+