require 'runit/testcase'
require 'runit/testsuite'
require 'runit/cui/testrunner'
require 'runit/topublic'
include RUNIT::ToPublic
require 'aspectr'
include AspectR
class Logger < Aspect
def initialize(io = STDOUT)
@io = io
end
def _tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end
def enter(method, object, exitstatus, *args)
@io.puts "enter: #{_tick} #{object.class}##{method}: called with #{args.inspect}"
end
def exit(method, object, exitstatus, *args)
@io.print "exit: #{_tick} #{object.class}##{method}: exited "
if exitstatus.kind_of?(Array)
@io.puts "normally returning #{exitstatus[0].inspect}"
elsif exitstatus == true
@io.puts "with exception '#{$!}'"
else
@io.puts "normally"
end
end
def senter(method, object, exitstatus, *args)
@io.puts "senter: #{_tick} Entering #{object.id}##{method}"
end
def sexit(method, object, exitstatus, *args)
@io.puts "sexit: #{_tick} Exiting #{object.id}##{method}"
end
end
class Counter < Aspect
attr_reader :incrementer_cnt
def initialize
@counters = {}
end
def wrap(target, *methods)
super(target, :inc, nil, *methods)
end
def unwrap(target, *methods)
super(target, :inc, nil, *methods)
end
def [](object, method)
method = method.id2name if method.kind_of?(Symbol)
@counters[object][method]
end
private
def inc(method, object, *args)
begin
@counters[object][method] += 1
rescue NameError
@counters[object] = {} unless @counters[object]
@counters[object][method] = 1
end
end
end
class Test
attr_reader :tt
def say(hello)
return hello + " world"
end
def inspect; id.inspect; end
def sayz(*args)
raise NotImplementedError, "NYI!"
end
end
class HistoryStdOut
attr_reader :history
def initialize
@history = Array.new
end
def puts(str)
if @last_was_print
@history[-1] += str
else
@history.push str
end
@last_was_print = false
end
def print(str)
@history.push str
@last_was_print = true
end
def last; @history.last; end
end
class CodeWrapper < Aspect
alias old_wrap wrap
def wrap(target, *methods)
wrap_with_code(target, "p 'pre'", "p 'post'", *methods)
end
def code_wrap(target, *methods)
wrap_with_code(target,"CodeWrapper.enter","CodeWrapper.leave", *methods)
end
@@counter = 0
def CodeWrapper.enter; @@counter += 1; end
def CodeWrapper.leave; @@counter += 1; end
def CodeWrapper.counter; @@counter; end
end
class T2; def m; end; end
class TestAspectR < RUNIT::TestCase
# Just so that we can use previously written tests. Clean up when there is
# time!
@@logger = Logger.new(@@hso = HistoryStdOut.new)
@@counter = Counter.new
@@t, @@u = Test.new, Test.new
def setup
end
def test_01_wrap
@@counter.wrap(Test, :say) # Wrap counter on Test#say
assert_equals("hello world", @@t.say("hello"))
assert_equals("hello world", @@u.say("hello"))
assert_equals(1, @@counter[@@t, :say])
assert_equals(1, @@counter[@@u, :say])
@@t.say("my")
assert_equals(2, @@counter[@@t, :say])
assert_equals(1, @@counter[@@u, :say])
end
def test_02_class_wrapping
@@logger.wrap(Test, :enter, :exit, /sa/)
@@t.say("t: hello")
assert_match(@@hso.history[-2], /enter.*Test#say: called with \[.*\]/)
assert_match(@@hso.history[-1], /exit.*Test#say: exited/)
@@u.say("u: hello")
assert_match(@@hso.history[-2], /enter.*Test#say: called with \[.*\]/)
assert_match(@@hso.history[-1], /exit.*Test#say: exited/)
end
def test_03_singleton_wrapping
@@logger.wrap(@@t, :senter, :sexit, :say)
@@t.say("t: hello")
assert_match(@@hso.history[-4], /senter/)
assert_match(@@hso.history[-3], /enter/)
assert_match(@@hso.history[-2], /exit/)
assert_match(@@hso.history[-1], /sexit/)
@@u.say("u: hello")
assert_match(@@hso.history[-2], /enter/)
assert_match(@@hso.history[-1], /exit/)
end
def test_04_unwrapping
@@logger.unwrap(Test, :enter, :exit, /sa/)
@@t.say("t: hello") # senter and sexit should still be there...
assert_match(@@hso.history[-2], /senter/)
assert_match(@@hso.history[-1], /sexit/)
l = @@hso.history.length
@@u.say("u: hello")
assert_equals(l, @@hso.history.length) # Nothing printed!
end
def test_05_dynamically_changing_advice
Logger.class_eval <<-'EOC'
def senter(*args)
@io.puts "senter version 2.0"
end
EOC
@@t.say("t: hello")
assert_match(@@hso.history[-2], /senter version 2\.0/)
end
def test_06_partial_unwrap
@@logger.unwrap(@@t, :senter, nil, :say)
l = @@hso.history.length
@@t.say("t: hello") # Should use sexit but not senter
assert_equals(l+1, @@hso.history.length)
assert_match(@@hso.history[-1], /sexit/)
end
def test_07_exception_in_method
@@logger.wrap(@@t, nil, :exit, :sayz)
l = @@hso.history.length
begin
@@t.sayz 1
rescue Exception; end
assert_equals(l+1, @@hso.history.length)
assert_match(@@hso.history[-1], /exit/)
end
def test_08_counter
assert_equals(7, @@counter[@@t, :say])
@@counter.unwrap(Test, :say)
@@t.say("t: hello")
assert_equals(7, @@counter[@@t, :say])
end
def test_09_transparency
assert_equals(1, Test.instance_method(:say).arity)
assert_equals(-1, Test.instance_method(:sayz).arity)
@@counter.wrap(Test, :tt)
assert_equals(0, Test.instance_method(:tt).arity)
end
def test_10_special_methods
@@logger.wrap(Array, :enter, nil, :[], :[]=, :initialize)
a = Array.new(2)
assert_match(@@hso.history.last, /enter/); l = @@hso.history.length
a[1]
assert_equals(l+1, @@hso.history.length)
assert_match(@@hso.history.last, /enter/); l = @@hso.history.length
a[1] = 10
assert_equals(l+1, @@hso.history.length)
assert_match(@@hso.history.last, /enter/); l = @@hso.history.length
@@logger.unwrap(Array, :[], :[]=, :initialize)
end
def test_11_code_wrapping
assert_equals(0, CodeWrapper.counter)
CodeWrapper.new.code_wrap(T2, :m)
T2.new.m
assert_equals(2, CodeWrapper.counter)
end
end
# Run if we were directly called
if $0 == __FILE__
testrunner = RUNIT::CUI::TestRunner.new
if ARGV.size == 0
suite = TestAspectR.suite
else
suite = RUNIT::TestSuite.new
ARGV.each do |testmethod|
suite.add_test(TestAspectR.new(testmethod))
end
end
testrunner.run(suite)
end
class T3; def m; end; def m2; end; end
def time(n = 100_000, &block)
start = Process.times.utime
n.times{block.call}
Process.times.utime - start
end
def test_overhead
puts "\nOverhead"
t = T3.new
puts "Time for unwrapped: #{tuw = time(50_000) {t.m}}"
CodeWrapper.new.code_wrap(T3, :m)
puts "Time for code-wrapped: #{tw = time(50_000) {t.m}}"
puts "A slowdown of #{tw/tuw}"
end
syntax highlighted by Code2HTML, v. 0.9.1