C0 code coverage information
Generated on Sun Oct 26 11:18:05 -0500 2008 with rcov 0.8.1.2
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 require File.dirname(__FILE__) + '/../spec_helper'
2 require 'aquarium/spec_example_types'
3 require 'aquarium/aspects'
4 require 'logger'
5
6 include Aquarium::Aspects
7
8 class MyError1 < StandardError; end
9 class MyError2 < StandardError; end
10 class MyError3 < StandardError; end
11 class ClassThatRaises
12 class CTRException < StandardError; end
13 def raises
14 raise CTRException
15 end
16 end
17 class ClassThatRaisesString
18 class CTRException < StandardError; end
19 def raises
20 raise "A string exception."
21 end
22 end
23
24
25 describe Aspect, " cannot advise the private implementation methods inserted by other aspects" do
26 it "should have no affect." do
27 class WithAspectLikeMethod
28 def _aspect_foo; end
29 end
30 aspect = Aspect.new(:after, :pointcut => {:type => WithAspectLikeMethod, :methods => :_aspect_foo}) {|jp, obj, *args| fail}
31 WithAspectLikeMethod.new._aspect_foo
32 aspect.unadvise
33 end
34 end
35
36 describe Aspect, " when advising a type" do
37 before(:all) do
38 @advice = Proc.new {}
39 end
40 after(:each) do
41 @aspect.unadvise
42 end
43
44 it "should not add new public instance or class methods that the advised type responds to." do
45 all_public_methods_before = all_public_methods_of_type Watchful
46 @aspect = Aspect.new :after, :pointcut => {:type => Watchful, :method_options => :exclude_ancestor_methods}, :advice => @advice
47 (all_public_methods_of_type(Watchful) - all_public_methods_before).should == []
48 end
49
50 it "should not add new protected instance methods that the advised type responds to." do
51 all_protected_methods_before = all_protected_methods_of_type Watchful
52 @aspect = Aspect.new :after, :pointcut => {:type => Watchful, :method_options => :exclude_ancestor_methods}, :advice => @advice
53 (all_protected_methods_of_type(Watchful) - all_protected_methods_before).should == []
54 end
55 end
56
57 describe Aspect, " when advising an object" do
58 before(:all) do
59 @advice = Proc.new {}
60 end
61 after(:each) do
62 @aspect.unadvise
63 end
64
65 it "should not add new public instance or class methods that the advised object responds to." do
66 watchful = Watchful.new
67 all_public_methods_before = all_public_methods_of_object Watchful
68 @aspect = Aspect.new :after, :pointcut => {:object => watchful, :method_options => :exclude_ancestor_methods}, :advice => @advice
69 (all_public_methods_of_object(Watchful) - all_public_methods_before).should == []
70 end
71
72 it "should not add new protected instance or class methods that the advised object responds to." do
73 watchful = Watchful.new
74 all_protected_methods_before = all_protected_methods_of_object Watchful
75 @aspect = Aspect.new :after, :pointcut => {:object => watchful, :method_options => :exclude_ancestor_methods}, :advice => @advice
76 (all_protected_methods_of_object(Watchful) - all_protected_methods_before).should == []
77 end
78 end
79
80 describe Aspect, " with :before advice" do
81 after(:each) do
82 @aspect.unadvise if @aspect
83 end
84
85 it "should pass the context information to the advice, including self and the method parameters." do
86 watchful = Watchful.new
87 advice_called = false
88 @aspect = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
89 advice_called = true
90 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
91 jp.context.advice_kind.should == :before
92 jp.context.advised_object.should == watchful
93 jp.context.returned_value.should == nil
94 jp.context.raised_exception.should == nil
95 end
96 block_called = 0
97 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }
98 block_called.should == 1
99 advice_called.should be_true
100 end
101
102 it "should evaluate the advice before the method body and its block (if any)." do
103 @aspect = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
104 @advice_called += 1
105 end
106 do_watchful_public_protected_private
107 end
108 end
109
110 describe Aspect, " with :after advice" do
111 after(:each) do
112 @aspect.unadvise if @aspect
113 end
114
115 it "should pass the context information to the advice, including self, the method parameters, and the return value when the method returns normally." do
116 watchful = Watchful.new
117 advice_called = false
118 @aspect = Aspect.new :after, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
119 advice_called = true
120 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
121 jp.context.advice_kind.should == :after
122 jp.context.advised_object.should == watchful
123 jp.context.returned_value.should == 1
124 jp.context.raised_exception.should == nil
125 end
126 block_called = 0
127 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }
128 block_called.should == 1
129 advice_called.should be_true
130 end
131
132 it "should pass the context information to the advice, including self, the method parameters, and the rescued exception when an exception is raised." do
133 watchful = Watchful.new
134 context = nil
135 @aspect = Aspect.new :after, :pointcut => {:type => Watchful, :methods => /public_watchful_method/} do |jp, obj, *args|
136 context = jp.context
137 end
138 block_called = 0
139 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') do |*args|
140 block_called += 1
141 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
142 end }.should raise_error(Watchful::WatchfulError)
143 block_called.should == 1
144 context.advised_object.should == watchful
145 context.returned_value.should == nil
146 context.raised_exception.kind_of?(Watchful::WatchfulError).should be_true
147 end
148
149 it "should evaluate the advice after the method body and its block (if any)." do
150 @aspect = Aspect.new :after, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
151 @advice_called += 1
152 end
153 do_watchful_public_protected_private
154 end
155
156 it "should ignore the value returned by the advice" do
157 class ReturningValue
158 def doit args
159 args + ["d"]
160 end
161 end
162 ary = %w[a b c]
163 ReturningValue.new.doit(ary).should == %w[a b c d]
164 @aspect = Aspect.new :after, :type => ReturningValue, :method => :doit do |jp, obj, *args|
165 %w[aa] + jp.context.returned_value + %w[e]
166 end
167 ReturningValue.new.doit(ary).should == %w[a b c d]
168 end
169
170 it "should all the advice to assign a new return value" do
171 class ReturningValue
172 def doit args
173 args + ["d"]
174 end
175 end
176 ary = %w[a b c]
177 ReturningValue.new.doit(ary).should == %w[a b c d]
178 @aspect = Aspect.new :after, :type => ReturningValue, :method => :doit do |jp, obj, *args|
179 jp.context.returned_value = %w[aa] + jp.context.returned_value + %w[e]
180 end
181 ReturningValue.new.doit(ary).should == %w[aa a b c d e]
182 end
183
184 it "should allow advice to change the exception raised" do
185 aspect_advice_invoked = false
186 @aspect = Aspect.new :after, :pointcut => {:type => ClassThatRaises, :methods => :raises} do |jp, obj, *args|
187 aspect_advice_invoked = true
188 jp.context.raised_exception = MyError1
189 end
190 aspect_advice_invoked.should be_false
191 ctr = ClassThatRaises.new
192 lambda {ctr.raises}.should raise_error(MyError1)
193 aspect_advice_invoked.should be_true
194 end
195 end
196
197 describe Aspect, " with :after_returning advice" do
198 after(:each) do
199 @aspect.unadvise if @aspect
200 end
201
202 it "should pass the context information to the advice, including self, the method parameters, and the return value." do
203 watchful = Watchful.new
204 advice_called = false
205 @aspect = Aspect.new :after_returning, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
206 advice_called = true
207 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
208 jp.context.advice_kind.should == :after_returning
209 jp.context.advised_object.should == watchful
210 jp.context.returned_value.should == 1
211 jp.context.raised_exception.should == nil
212 end
213 block_called = 0
214 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }
215 block_called.should == 1
216 advice_called.should be_true
217 end
218
219 it "should evaluate the advice after the method body and its block (if any)." do
220 @aspect = Aspect.new :after_returning, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
221 @advice_called += 1
222 end
223 do_watchful_public_protected_private
224 end
225
226 it "should ignore the value returned by the advice" do
227 class ReturningValue
228 def doit args
229 args + ["d"]
230 end
231 end
232 ary = %w[a b c]
233 ReturningValue.new.doit(ary).should == %w[a b c d]
234 @aspect = Aspect.new :after_returning, :type => ReturningValue, :method => :doit do |jp, obj, *args|
235 %w[aa] + jp.context.returned_value + %w[e]
236 end
237 ReturningValue.new.doit(ary).should == %w[a b c d]
238 end
239
240 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 ary = %w[a b c]
247 ReturningValue.new.doit(ary).should == %w[a b c d]
248 @aspect = Aspect.new :after_returning, :type => ReturningValue, :method => :doit do |jp, obj, *args|
249 jp.context.returned_value = %w[aa] + jp.context.returned_value + %w[e]
250 end
251 ReturningValue.new.doit(ary).should == %w[aa a b c d e]
252 end
253 end
254
255 describe Aspect, " with :after_raising advice" do
256 after(:each) do
257 @aspect.unadvise if @aspect
258 end
259
260 it "should pass the context information to the advice, including self, the method parameters, and the rescued exception." do
261 watchful = Watchful.new
262 advice_called = false
263 @aspect = Aspect.new :after_raising, :pointcut => {:type => Watchful, :methods => /public_watchful_method/} do |jp, obj, *args|
264 advice_called = true
265 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
266 jp.context.advised_object.should == watchful
267 jp.context.advice_kind.should == :after_raising
268 jp.context.returned_value.should == nil
269 jp.context.raised_exception.kind_of?(Watchful::WatchfulError).should be_true
270 end
271 block_called = 0
272 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }}.should raise_error(Watchful::WatchfulError)
273 block_called.should == 1
274 advice_called.should be_true
275 end
276
277 it "should evaluate the advice after the method body and its block (if any)." do
278 @aspect = Aspect.new :after_raising, :pointcut => {:type => Watchful, :methods => /public_watchful_method/} do |jp, obj, *args|
279 @advice_called += 1
280 end
281 do_watchful_public_protected_private true
282 end
283
284 it "should invoke advice when exceptions of the specified type are raised" do
285 aspect_advice_invoked = false
286 @aspect = Aspect.new(:after_raising => Watchful::WatchfulError, :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
287 block_invoked = false
288 watchful = Watchful.new
289 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
290 aspect_advice_invoked.should be_true
291 block_invoked.should be_true
292 end
293
294 it "should invoke advice when exceptions of the specified type are raised, which were specified with :exceptions => ..." do
295 aspect_advice_invoked = false
296 @aspect = Aspect.new(:after_raising, :exceptions => Watchful::WatchfulError, :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
297 block_invoked = false
298 watchful = Watchful.new
299 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
300 aspect_advice_invoked.should be_true
301 block_invoked.should be_true
302 end
303
304 it "should not invoke advice when exceptions of types that don't match the specified exception type are raised" do
305 aspect_advice_invoked = false
306 @aspect = Aspect.new(:after_raising => MyError1, :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
307 block_invoked = false
308 watchful = Watchful.new
309 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
310 aspect_advice_invoked.should be_false
311 block_invoked.should be_true
312 end
313
314 it "should not invoke advice when exceptions of types that don't match the specified exception type are raised, which were specified with :exceptions => ..." do
315 aspect_advice_invoked = false
316 @aspect = Aspect.new(:after_raising, :exceptions => MyError1, :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
317 block_invoked = false
318 watchful = Watchful.new
319 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
320 aspect_advice_invoked.should be_false
321 block_invoked.should be_true
322 end
323
324 it "should invoke advice when one exception in the list of the specified types is raised" do
325 aspect_advice_invoked = false
326 @aspect = Aspect.new(:after_raising => [Watchful::WatchfulError, MyError1], :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
327 block_invoked = false
328 watchful = Watchful.new
329 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
330 aspect_advice_invoked.should be_true
331 block_invoked.should be_true
332 end
333
334 it "should invoke advice when an exception that subclasses a specified exception type is raised" do
335 aspect_advice_invoked = false
336 @aspect = Aspect.new(:after_raising => StandardError, :pointcut => {:type => ClassThatRaises, :methods => :raises}) {|jp, obj, *args| aspect_advice_invoked = true}
337 ctr = ClassThatRaises.new
338 lambda {ctr.raises}.should raise_error(ClassThatRaises::CTRException)
339 aspect_advice_invoked.should be_true
340 end
341
342 it "should not invoke advice when exceptions of types that don't match the specified list of exception types are raised" do
343 aspect_advice_invoked = false
344 @aspect = Aspect.new(:after_raising => [MyError1, MyError2], :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
345 block_invoked = false
346 watchful = Watchful.new
347 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
348 aspect_advice_invoked.should be_false
349 block_invoked.should be_true
350 end
351
352 it "should not invoke advice when exceptions of types that don't match the specified list of exception types are raised, which were specified with :exceptions => ..." do
353 aspect_advice_invoked = false
354 @aspect = Aspect.new(:after_raising, :exceptions => [MyError1, MyError2], :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
355 block_invoked = false
356 watchful = Watchful.new
357 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
358 aspect_advice_invoked.should be_false
359 block_invoked.should be_true
360 end
361
362 it "should treat :exception as a synonym for :exceptions" do
363 aspect_advice_invoked = false
364 @aspect = Aspect.new(:after_raising, :exception => [MyError1, MyError2], :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
365 block_invoked = false
366 watchful = Watchful.new
367 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3) {|*args| block_invoked = true}}.should raise_error(Watchful::WatchfulError)
368 aspect_advice_invoked.should be_false
369 block_invoked.should be_true
370 end
371
372 it "should merge exceptions specified with :exception(s) and :after_raising" do
373 aspect_advice_invoked = false
374 @aspect = Aspect.new(:after_raising => MyError1, :exception => [MyError2, MyError3], :pointcut => {:type => Watchful, :methods => /public_watchful_method/}) {|jp, obj, *args| aspect_advice_invoked = true}
375 @aspect.specification[:after_raising].should eql(Set.new([MyError1, MyError2, MyError3]))
376 end
377
378 it "should advise all methods that raise exceptions when no specific exceptions are specified" do
379 aspect_advice_invoked = false
380 @aspect = Aspect.new :after_raising, :pointcut => {:type => ClassThatRaises, :methods => :raises} do |jp, obj, *args|
381 aspect_advice_invoked = true
382 end
383 aspect_advice_invoked.should be_false
384 ctr = ClassThatRaises.new
385 lambda {ctr.raises}.should raise_error(ClassThatRaises::CTRException)
386 aspect_advice_invoked.should be_true
387 end
388
389 it "should advise all methods that raise strings (which are converted to RuntimeError) when no specific exceptions are specified" do
390 aspect_advice_invoked = false
391 @aspect = Aspect.new :after_raising, :pointcut => {:type => ClassThatRaisesString, :methods => :raises} do |jp, obj, *args|
392 aspect_advice_invoked = true
393 end
394 aspect_advice_invoked.should be_false
395 ctr = ClassThatRaisesString.new
396 lambda {ctr.raises}.should raise_error(RuntimeError)
397 aspect_advice_invoked.should be_true
398 end
399
400 it "should allow advice to change the exception raised" do
401 aspect_advice_invoked = false
402 @aspect = Aspect.new :after_raising, :pointcut => {:type => ClassThatRaises, :methods => :raises} do |jp, obj, *args|
403 aspect_advice_invoked = true
404 jp.context.raised_exception = MyError1
405 end
406 aspect_advice_invoked.should be_false
407 ctr = ClassThatRaises.new
408 lambda {ctr.raises}.should raise_error(MyError1)
409 aspect_advice_invoked.should be_true
410 end
411 end
412
413 describe Aspect, " with :before and :after advice" do
414 after(:each) do
415 @aspect.unadvise if @aspect
416 end
417
418 it "should pass the context information to the advice, including self and the method parameters, plus the return value for the after-advice case." do
419 # We record the contexts seen and the volatile advice kinds and returned values separately, as those values will have been
420 # reset in the contexts by the time we return from the advised method.
421 contexts = []
422 advice_kinds = []
423 returned_values = []
424 @aspect = Aspect.new :before, :after, :pointcut => {:type => Watchful, :methods => [:public_watchful_method]} do |jp, obj, *args|
425 contexts << jp.context
426 advice_kinds << jp.context.advice_kind
427 returned_values << jp.context.returned_value
428 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
429 end
430 watchful = Watchful.new
431 public_block_called = 0
432 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') do |*args|
433 public_block_called += 1
434 end
435 public_block_called.should == 1
436 contexts.size.should == 2
437 advice_kinds[0].should == :before
438 advice_kinds[1].should == :after
439 returned_values[0].should == nil
440 returned_values[1].should == 1
441 contexts.each do |context|
442 context.advised_object.should == watchful
443 context.raised_exception.should == nil
444 end
445
446 %w[protected private].each do |protection|
447 block_called = 0
448 watchful.send("#{protection}_watchful_method", :b1, :b2, :b3) {|*args| block_called += 1}
449 block_called.should == 1
450 contexts.size.should == 2
451 end
452 end
453
454 it "should evaluate the advice before and after the method body and its block (if any)." do
455 @aspect = Aspect.new :before, :after, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
456 @advice_called += 1
457 end
458 do_watchful_public_protected_private false, 2
459 end
460 end
461
462 describe Aspect, " with :before and :after_returning advice" do
463 after(:each) do
464 @aspect.unadvise if @aspect
465 end
466
467 it "should pass the context information to the advice, including self and the method parameters, plus the return value for the after-advice case." do
468 watchful = Watchful.new
469 # We record the contexts seen and the volatile advice kinds and returned values separately, as those values will have been
470 # reset in the contexts by the time we return from the advised method.
471 contexts = []
472 advice_kinds = []
473 returned_values = []
474 @aspect = Aspect.new :before, :after_returning, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
475 contexts << jp.context
476 advice_kinds << jp.context.advice_kind
477 returned_values << jp.context.returned_value
478 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
479 end
480 block_called = 0
481 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }
482 block_called.should == 1
483 contexts.size.should == 2
484 advice_kinds[0].should == :before
485 advice_kinds[1].should == :after_returning
486 returned_values[0].should == nil
487 returned_values[1].should == 1
488 contexts.each do |context|
489 context.advised_object.should == watchful
490 context.raised_exception.should == nil
491 end
492 end
493
494 it "should evaluate the advice before and after the method body and its block (if any)." do
495 @aspect = Aspect.new :before, :after_returning, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
496 @advice_called += 1
497 end
498 do_watchful_public_protected_private false, 2
499 end
500 end
501
502 describe Aspect, " with :before and :after_raising advice" do
503 after(:each) do
504 @aspect.unadvise if @aspect
505 end
506
507 it "should pass the context information to the advice, including self and the method parameters, plus the raised exception for the after-advice case." do
508 watchful = Watchful.new
509 # We record the contexts seen and the volatile advice kinds and raised exceptions separately, as those values will have been
510 # reset in the contexts by the time we return from the advised method.
511 contexts = []
512 advice_kinds = []
513 raised_exceptions = []
514 @aspect = Aspect.new :before, :after_raising, :pointcut => {:type => Watchful, :methods => :public_watchful_method_that_raises} do |jp, obj, *args|
515 contexts << jp.context
516 advice_kinds << jp.context.advice_kind
517 raised_exceptions << jp.context.raised_exception
518 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
519 end
520 block_called = 0
521 lambda {watchful.public_watchful_method_that_raises(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| block_called += 1 }}.should raise_error(Watchful::WatchfulError)
522 block_called.should == 1
523 contexts.size.should == 2
524 advice_kinds[0].should == :before
525 advice_kinds[1].should == :after_raising
526 raised_exceptions[0].should == nil
527 raised_exceptions[1].kind_of?(Watchful::WatchfulError).should be_true
528 contexts.each do |context|
529 context.advised_object.should == watchful
530 context.returned_value.should == nil
531 end
532 end
533
534 it "should evaluate the advice before and after the method body and its block (if any)." do
535 @aspect = Aspect.new :before, :after_raising, :pointcut => {:type => Watchful, :methods => :public_watchful_method_that_raises} do |jp, obj, *args|
536 @advice_called += 1
537 end
538 do_watchful_public_protected_private true, 2
539 end
540 end
541
542 describe Aspect, " with :around advice" do
543 after(:each) do
544 @aspect.unadvise if @aspect
545 end
546
547 it "should pass the context information to the advice, including the object, advice kind, the method invocation parameters, etc." do
548 watchful = Watchful.new
549 advice_called = false
550 @aspect = Aspect.new :around, :pointcut => {:type => Watchful, :methods => [:public_watchful_method]} do |jp, obj, *args|
551 advice_called = true
552 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
553 jp.context.advised_object.should == watchful
554 jp.context.advice_kind.should == :around
555 jp.context.returned_value.should == nil
556 jp.context.raised_exception.should == nil
557 end
558 public_block_called = false
559 protected_block_called = false
560 private_block_called = false
561 watchful.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| public_block_called = true }
562 watchful.send(:protected_watchful_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
563 watchful.send(:private_watchful_method, :c1, :c2, :c3) {|*args| private_block_called = true}
564 public_block_called.should be_false # proceed is never called!
565 protected_block_called.should be_true
566 private_block_called.should be_true
567 advice_called.should be_true
568 end
569
570 it "should advise subclass invocations of methods advised in the superclass." do
571 module AdvisingSuperClass
572 class SuperClass
573 def public_method *args
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
584 end
585 class SubClass < SuperClass
586 end
587 end
588
589 child = AdvisingSuperClass::SubClass.new
590 advice_called = false
591 @aspect = Aspect.new :around, :pointcut => {:type => AdvisingSuperClass::SuperClass, :methods => [:public_method]} do |jp, obj, *args|
592 advice_called = true
593 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
594 jp.context.advised_object.should == child
595 jp.context.advice_kind.should == :around
596 jp.context.returned_value.should == nil
597 jp.context.raised_exception.should == nil
598 end
599 public_block_called = false
600 protected_block_called = false
601 private_block_called = false
602 child.public_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| fail }
603 child.send(:protected_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
604 child.send(:private_method, :c1, :c2, :c3) {|*args| private_block_called = true}
605 public_block_called.should be_false # proceed is never called!
606 protected_block_called.should be_true
607 private_block_called.should be_true
608 advice_called.should be_true
609 end
610
611 it "should advise subclass invocations of methods advised in the subclass that are defined in the superclass." do
612 module AdvisingSubClass
613 class SuperClass
614 def public_method *args
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
625 end
626 class SubClass < SuperClass
627 end
628 end
629
630 child = AdvisingSubClass::SubClass.new
631 advice_called = false
632 @aspect = Aspect.new :around, :pointcut => {:type => AdvisingSubClass::SuperClass, :methods => [:public_method]} do |jp, obj, *args|
633 advice_called = true
634 args.should == [:a1, :a2, :a3, {:h1 => 'h1', :h2 => 'h2'}]
635 jp.context.advised_object.should == child
636 jp.context.advice_kind.should == :around
637 jp.context.returned_value.should == nil
638 jp.context.raised_exception.should == nil
639 end
640 public_block_called = false
641 protected_block_called = false
642 private_block_called = false
643 child.public_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| fail }
644 child.send(:protected_method, :b1, :b2, :b3) {|*args| protected_block_called = true}
645 child.send(:private_method, :c1, :c2, :c3) {|*args| private_block_called = true}
646 public_block_called.should be_false # proceed is never called!
647 protected_block_called.should be_true
648 private_block_called.should be_true
649 advice_called.should be_true
650 end
651
652 it "should not advise subclass overrides of superclass methods, when advising superclasses (but calls to superclass methods are advised)." do
653 class WatchfulChild2 < Watchful
654 def public_watchful_method *args
655 @override_called = true
656 yield(*args) if block_given?
657 end
658 attr_reader :override_called
659 def initialize
660 super
661 @override_called = false
662 end
663 end
664 @aspect = Aspect.new(:around, :pointcut => {:type => Watchful, :methods => [:public_watchful_method]}) {|jp, obj, *args| fail}
665 child = WatchfulChild2.new
666 public_block_called = false
667 child.public_watchful_method(:a1, :a2, :a3, :h1 => 'h1', :h2 => 'h2') { |*args| public_block_called = true }
668 public_block_called.should be_true # advice never called
669 end
670
671 it "should evaluate the advice and only evaluate the method body and its block (if any) when JoinPoint#proceed is called." do
672 do_around_spec
673 end
674
675 it "should pass the block that was passed to the method by default if the block is not specified explicitly in the around advice in the call to JoinPoint#proceed." do
676 do_around_spec
677 end
678
679 it "should pass the parameters and block that were passed to the method by default if JoinPoint#proceed is invoked without parameters and a block." do
680 do_around_spec
681 end
682
683 it "should pass parameters passed explicitly to JoinPoint#proceed, rather than the original method parameters, but also pass the original block if a new block is not specified." do
684 do_around_spec :a4, :a5, :a6
685 end
686
687 it "should pass parameters and a block passed explicitly to JoinPoint#proceed, rather than the original method parameters and block." do
688 override_block_called = false
689 @aspect = Aspect.new :around, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
690 jp.proceed(:a4, :a5, :a6) {|*args| override_block_called = true}
691 end
692 watchful = Watchful.new
693 orig_block_called = false
694 watchful.public_watchful_method(:a1, :a2, :a3) {|*args| orig_block_called = true}
695 override_block_called.should be_true
696 orig_block_called.should be_false
697 watchful.public_watchful_method_args.should == [:a4, :a5, :a6]
698 end
699
700 it "should return the value returned by the advice, NOT the value returned by the advised join point!" do
701 class ReturningValue
702 def doit args
703 args + ["d"]
704 end
705 end
706 ary = %w[a b c]
707 ReturningValue.new.doit(ary).should == %w[a b c d]
708 @aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, obj, *args|
709 jp.proceed
710 %w[aa bb cc]
711 end
712 ReturningValue.new.doit(ary).should == %w[aa bb cc]
713 end
714
715 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 ary = %w[a b c]
722 ReturningValue.new.doit(ary).should == %w[a b c d]
723 @aspect = Aspect.new :around, :type => ReturningValue, :method => :doit do |jp, obj, *args|
724 begin
725 jp.proceed
726 ensure
727 %w[aa bb cc]
728 end
729 end
730 ReturningValue.new.doit(ary).should == %w[a b c d]
731 end
732
733 def do_around_spec *args_passed_to_proceed
734 @aspect = Aspect.new :around, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do |jp, obj, *args|
735 @advice_called += 1
736 returned_value = args_passed_to_proceed.empty? ? jp.proceed : jp.proceed(*args_passed_to_proceed)
737 @advice_called += 1
738 returned_value
739 end
740 do_watchful_public_protected_private false, 2, (args_passed_to_proceed.empty? ? nil : args_passed_to_proceed)
741 end
742 end
743
744 describe Aspect, " with advice that calls JoinPoint#invoke_original_join_point" do
745 class AdvicesInvocationCounter
746 def initialize; @counter = 0; end
747 def increment; @counter += 1; end
748 def counter; @counter; end
749 end
750
751 it "should not call the intermediate advices" do
752 aspect1 = Aspect.new :around, :type => AdvicesInvocationCounter, :method => :increment do |jp, obj, *args|; fail; end
753 aspect2 = Aspect.new :around, :type => AdvicesInvocationCounter, :method => :increment do |jp, obj, *args|
754 jp.invoke_original_join_point
755 end
756 aic = AdvicesInvocationCounter.new
757 aic.increment
758 aic.counter.should == 1
759 aspect1.unadvise
760 aspect2.unadvise
761 end
762 end
763
764 describe Aspect, "#unadvise called more than once on the same aspect" do
765 before(:all) do
766 @advice = Proc.new {}
767 end
768
769 it "should do nothing on the second invocation." do
770 aspect = Aspect.new :around, :type => Watchful, :method => /does_not_exist/, :advice => @advice, :severity => Logger::Severity::ERROR
771 aspect.unadvise
772 lambda {aspect.unadvise}.should_not raise_error
773 lambda {aspect.unadvise}.should_not raise_error
774 end
775 end
776
777 describe Aspect, "#unadvise for 'empty' aspects" do
778 before(:all) do
779 @advice = Proc.new {}
780 end
781
782 it "should do nothing for unadvised types." do
783 expected_methods = (Watchful.private_methods + Watchful.private_instance_methods).sort
784 aspect = Aspect.new :around, :type => Watchful, :method => /does_not_exist/, :advice => @advice, :severity => Logger::Severity::ERROR
785 ((Watchful.private_methods + Watchful.private_instance_methods).sort - expected_methods).should == []
786 aspect.unadvise
787 ((Watchful.private_methods + Watchful.private_instance_methods).sort - expected_methods).should == []
788 aspect.unadvise
789 ((Watchful.private_methods + Watchful.private_instance_methods).sort - expected_methods).should == []
790 end
791
792 it "should do nothing for unadvised objects." do
793 @watchful = Watchful.new
794 expected_methods = @watchful.private_methods.sort
795 aspect = Aspect.new :around, :object => @watchful, :method => /does_not_exist/, :advice => @advice, :severity => Logger::Severity::ERROR
796 (@watchful.private_methods.sort - expected_methods).should == []
797 aspect.unadvise
798 (@watchful.private_methods.sort - expected_methods).should == []
799 aspect.unadvise
800 (@watchful.private_methods.sort - expected_methods).should == []
801 end
802 end
803
804 describe Aspect, "#unadvise clean up" do
805 before(:all) do
806 @advice = Proc.new {}
807 @watchful = Watchful.new
808 end
809
810 def get_type_methods
811 public_methods = (Watchful.public_methods + Watchful.public_instance_methods).sort
812 protected_methods = (Watchful.protected_methods + Watchful.protected_instance_methods).sort
813 private_methods = (Watchful.private_methods + Watchful.private_instance_methods).sort
814 [public_methods, protected_methods, private_methods]
815 end
816
817 def get_object_methods
818 public_methods = @watchful.public_methods.sort
819 protected_methods = @watchful.protected_methods.sort
820 private_methods = @watchful.private_methods.sort
821 [public_methods, protected_methods, private_methods]
822 end
823
824 def diff_methods actual, expected, private_should_not_be_equal = false
825 3.times do |i|
826 if i==2 && private_should_not_be_equal
827 actual[i].should_not == expected[i]
828 else
829 actual[i].should == expected[i]
830 end
831 end
832 end
833
834 def do_unadvise_spec parameters, which_get_methods
835 parameters[:after] = ''
836 expected_methods = send(which_get_methods)
837 advice_called = false
838 aspect = Aspect.new(:after, parameters) {|jp, obj, *args| advice_called = true}
839 diff_methods send(which_get_methods), expected_methods, true
840 aspect.unadvise
841 diff_methods send(which_get_methods), expected_methods
842
843 %w[public protected private].each do |protection|
844 advice_called = false
845 block_called = false
846 @watchful.send("#{protection}_watchful_method".intern, :a1, :a2, :a3) {|*args| block_called = true}
847 advice_called.should be_false
848 block_called.should be_true
849 end
850 end
851
852 it "should remove all advice added by a pointcut-based aspect." do
853 do_unadvise_spec({:pointcut => {:type => Watchful, :method_options => :exclude_ancestor_methods}}, :get_type_methods)
854 end
855
856 it "should remove all advice added by a type-based aspect." do
857 do_unadvise_spec({:type => Watchful, :method_options => :exclude_ancestor_methods}, :get_type_methods)
858 end
859
860 it "should remove all advice added by an object-based aspect." do
861 do_unadvise_spec({:object => @watchful, :method_options => :exclude_ancestor_methods}, :get_object_methods)
862 end
863 end
864
865 module Aquarium
866 class FooForPrivateCheck
867 def bar; end
868 end
869 end
870
871 describe Aspect, "#unadvise clean up when all advices have been removed" do
872 before(:all) do
873 @advice = Proc.new {}
874 @aspect1 = @aspect2 = nil
875 end
876
877 def check_cleanup before_methods, before_class_variables
878 after = yield
879 (after[0] - before_methods).should_not == []
880 (after[1] - before_class_variables).should_not == []
881 @aspect1.unadvise
882 after = yield
883 (after[0] - before_methods).should_not == []
884 (after[1] - before_class_variables).should_not == []
885 @aspect2.unadvise
886 after = yield
887 (after[0] - before_methods).should == []
888 (after[1] - before_class_variables).should == []
889 end
890
891 it "should remove all advice overhead for pointcut-based aspects." do
892 before_methods = Aquarium::FooForPrivateCheck.private_instance_methods.sort
893 before_class_variables = Aquarium::FooForPrivateCheck.class_variables.sort
894 @aspect1 = Aspect.new(:before, :pointcut => {:type => Aquarium::FooForPrivateCheck, :method_options => :exclude_ancestor_methods}) {|jp, obj, *args| true}
895 @aspect2 = Aspect.new(:after, :pointcut => {:type => Aquarium::FooForPrivateCheck, :method_options => :exclude_ancestor_methods}) {|jp, obj, *args| true}
896 check_cleanup(before_methods, before_class_variables) do
897 [Aquarium::FooForPrivateCheck.private_instance_methods.sort, Aquarium::FooForPrivateCheck.class_variables.sort]
898 end
899 end
900
901 it "should remove all advice overhead for type-based aspects." do
902 before_methods = Aquarium::FooForPrivateCheck.private_instance_methods.sort
903 before_class_variables = Aquarium::FooForPrivateCheck.class_variables.sort
904 @aspect1 = Aspect.new(:before, :type => Aquarium::FooForPrivateCheck, :method_options => :exclude_ancestor_methods) {|jp, obj, *args| true}
905 @aspect2 = Aspect.new(:after, :type => Aquarium::FooForPrivateCheck, :method_options => :exclude_ancestor_methods) {|jp, obj, *args| true}
906 check_cleanup(before_methods, before_class_variables) do
907 [Aquarium::FooForPrivateCheck.private_instance_methods.sort, Aquarium::FooForPrivateCheck.class_variables.sort]
908 end
909 end
910
911 it "should remove all advice overhead for object-based aspects." do
912 object = Aquarium::FooForPrivateCheck.new
913 before_methods = object.private_methods.sort
914 before_class_variables = (class << object; self.class_variables.sort; end)
915 @aspect1 = Aspect.new(:before, :object => object, :method_options => :exclude_ancestor_methods) {|jp, obj, *args| true}
916 @aspect2 = Aspect.new(:after, :object => object, :method_options => :exclude_ancestor_methods) {|jp, obj, *args| true}
917 check_cleanup(before_methods, before_class_variables) do
918 [object.private_methods.sort, (class << object; self.class_variables.sort; end)]
919 end
920 end
921 end
922
923 %w[public protected private].each do |protection|
924 describe Aspect, " when advising and unadvising #{protection} methods" do
925 it "should keep the protection level of the advised methods unchanged." do
926 meta = "#{protection}_instance_methods"
927 method = "#{protection}_watchful_method"
928 Watchful.send(meta).should include(method)
929 aspect = Aspect.new(:after, :type => Watchful, :method => method.intern, :method_options => [protection.intern]) {|jp, obj, *args| true }
930 Watchful.send(meta).should include(method)
931 aspect.unadvise
932 Watchful.send(meta).should include(method)
933 end
934 end
935 end
936
937
938 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 it "should cause the object to respond to the type's original method." do
946 object = TypeDefinedMethodClass.new
947 aspect = Aspect.new(:before, :object => object, :method => :m) {true}
948 aspect.unadvise
949 object.m
950 object.called.should be_true
951 end
952 end
953
954 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 it "should cause the object to respond to the object's original method." do
961 object = TypeDefinedMethodClass.new
962 def object.m; @called = true; end
963 aspect = Aspect.new(:before, :object => object, :method => :m) {true}
964 aspect.unadvise
965 object.m
966 object.called.should be_true
967 end
968 end
969
970 describe Aspect, " when advising methods with non-alphanumeric characters" do
971 module Aquarium::Aspects
972 class ClassWithMethodNamesContainingOddChars
973 @@method_names = []
974 %w[= ! ?].each do |s|
975 @@method_names << "_a#{s}" << "a#{s}"
976 end
977 %w[+ - * / < << > >> =~ == === <=> % ^ ~ [] & | `].each do |s|
978 @@method_names << s
979 end
980 @@method_names.each do |s|
981 class_eval(<<-EOF, __FILE__, __LINE__)
982 def #{s}; "#{s}"; end
983 EOF
984 end
985 def self.method_names; @@method_names; end
986 end
987 end
988 it "should work with any valid ruby character" do
989 actual = ""
990 Aspect.new :before, :type => Aquarium::Aspects::ClassWithMethodNamesContainingOddChars,
991 :methods => Aquarium::Aspects::ClassWithMethodNamesContainingOddChars.method_names do |jp, obj, *args|
992 actual += ", #{jp.method_name}"
993 end
994 object = Aquarium::Aspects::ClassWithMethodNamesContainingOddChars.new
995 expected = ""
996 Aquarium::Aspects::ClassWithMethodNamesContainingOddChars.method_names.each do |s|
997 object.send s
998 expected += ", #{s}"
999 end
1000 actual.should == expected
1001 end
1002 end
1003
1004 describe Aspect, "#eql?" do
1005 before(:all) do
1006 @advice = Proc.new {}
1007 end
1008 after(:each) do
1009 @aspect1.unadvise
1010 @aspect2.unadvise
1011 end
1012
1013 it "should return true if both aspects have the same specification and pointcuts." do
1014 @aspect1 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method}, :advice => @advice
1015 @aspect2 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method}, :advice => @advice
1016 @aspect1.should eql(@aspect2)
1017 end
1018
1019 it "should return true if both aspects have the same specification and pointcuts, even if the advice procs are not equal." do
1020 @aspect1 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do true end
1021 @aspect2 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method} do false end
1022 @aspect1.should eql(@aspect2)
1023 end
1024
1025 it "should return false if each aspect advises pointcuts in different objects, even if the the objects are equivalent." do
1026 @aspect1 = Aspect.new :before, :pointcut => {:object => Watchful.new, :methods => :public_watchful_method} do true end
1027 @aspect2 = Aspect.new :before, :pointcut => {:object => Watchful.new, :methods => :public_watchful_method} do false end
1028 @aspect1.should_not eql(@aspect2)
1029 end
1030 end
1031
1032 describe Aspect, "#==" do
1033 before(:all) do
1034 @advice = Proc.new {}
1035 end
1036 after(:each) do
1037 @aspect1.unadvise
1038 @aspect2.unadvise
1039 end
1040
1041 it "should be equivalent to #eql?." do
1042 @aspect1 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method}, :advice => @advice
1043 @aspect2 = Aspect.new :before, :pointcut => {:type => Watchful, :methods => :public_watchful_method}, :advice => @advice
1044 @aspect1.specification.should == @aspect2.specification
1045 @aspect1.pointcuts.should == @aspect2.pointcuts
1046 @aspect1.should eql(@aspect2)
1047 @aspect1.should == @aspect2
1048 end
1049 end
1050
1051 describe Aspect, "#advice_chain_inspect" do
1052 it "should return the string '[nil]' if passed a nil advice chain" do
1053 Aspect.advice_chain_inspect(nil).should == "[nil]"
1054 chain = NoAdviceChainNode.new({:aspect => nil})
1055 Aspect.advice_chain_inspect(chain).should include("NoAdviceChainNode")
1056 end
1057 end
1058
1059 def all_public_methods_of_type type
1060 (type.public_methods + type.public_instance_methods).sort
1061 end
1062 def all_protected_methods_of_type type
1063 (type.protected_methods + type.protected_instance_methods).sort
1064 end
1065 def all_public_methods_of_object object
1066 object.public_methods.sort
1067 end
1068 def all_protected_methods_of_object object
1069 object.protected_methods.sort
1070 end
1071
1072 def do_watchful_public_protected_private raises = false, expected_advice_called_value = 1, args_passed_to_proceed = nil
1073 %w[public protected private].each do |protection|
1074 do_watchful_spec protection, raises, expected_advice_called_value, args_passed_to_proceed
1075 end
1076 end
1077
1078 def do_watchful_spec protection, raises, expected_advice_called_value, args_passed_to_proceed
1079 suffix = raises ? "_that_raises" : ""
1080 expected_advice_called = protection == "public" ? expected_advice_called_value : 0
1081 watchful = Watchful.new
1082 @advice_called = 0
1083 block_called = 0
1084 if raises
1085 lambda {watchful.send("#{protection}_watchful_method#{suffix}".intern, :a1, :a2, :a3) {|*args| block_called += 1}}.should raise_error(Watchful::WatchfulError)
1086 else
1087 watchful.send("#{protection}_watchful_method#{suffix}".intern, :a1, :a2, :a3) {|*args| block_called += 1}
1088 end
1089 @advice_called.should == expected_advice_called
1090 block_called.should == 1
1091 expected_args = (protection == "public" && !args_passed_to_proceed.nil?) ? args_passed_to_proceed : [:a1, :a2, :a3]
1092 watchful.instance_variable_get("@#{protection}_watchful_method#{suffix}_args".intern).should == expected_args
1093 end
Generated using the rcov code coverage analysis tool for Ruby version 0.8.1.2.