cheap_advice 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +70 -0
- data/Rakefile +79 -0
- data/VERSION +1 -0
- data/cheap_advice.slides.textile +257 -0
- data/example/ex01.rb +51 -0
- data/example/ex02-trace-0.yml +17 -0
- data/example/ex02-trace-1.yml +17 -0
- data/example/ex02-trace-2.yml +23 -0
- data/example/ex02-trace-4.yml +20 -0
- data/example/ex02-trace-5.yml +22 -0
- data/example/ex02-trace-6.yml +25 -0
- data/example/ex02.rb +99 -0
- data/lib/cheap_advice.rb +511 -0
- data/lib/cheap_advice/configuration.rb +217 -0
- data/lib/cheap_advice/trace.rb +228 -0
- data/spec/cheap_advice_spec.rb +342 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
| @@ -0,0 +1,217 @@ | |
| 1 | 
            +
            require 'cheap_advice'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class CheapAdvice
         | 
| 4 | 
            +
              # 
         | 
| 5 | 
            +
              class Configuration
         | 
| 6 | 
            +
                class Error < ::CheapAdvice::Error; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # Configuration input hash.
         | 
| 9 | 
            +
                attr_accessor :config
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                # Hash mapping advice names to CheapAdvice objects.
         | 
| 12 | 
            +
                attr_accessor :advice
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Array of CheapAdvice::Advised methods.
         | 
| 15 | 
            +
                attr_accessor :advised
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                # Hash of file names that were explicity required before applying advice.
         | 
| 18 | 
            +
                attr_accessor :required
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # Flag
         | 
| 21 | 
            +
                attr_accessor :config_changed
         | 
| 22 | 
            +
                alias :config_changed? :config_changed
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                attr_accessor :verbose
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def initialize opts = nil
         | 
| 27 | 
            +
                  opts ||= EMPTY_Hash
         | 
| 28 | 
            +
                  @verbose = false
         | 
| 29 | 
            +
                  opts.each do | k, v |
         | 
| 30 | 
            +
                    send(:"#{k}=", v)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  @advice ||= { }
         | 
| 33 | 
            +
                  @targets = [ ]
         | 
| 34 | 
            +
                  @advised = [ ]
         | 
| 35 | 
            +
                  @required = { }
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def config_changed!
         | 
| 39 | 
            +
                  @config_changed = true
         | 
| 40 | 
            +
                  self
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def configure_if_changed!
         | 
| 44 | 
            +
                  if config_changed?
         | 
| 45 | 
            +
                    configure!
         | 
| 46 | 
            +
                    @config_changed = false
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  self
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def configure!
         | 
| 52 | 
            +
                  disable!
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  # First pass: parse target and defaults.
         | 
| 55 | 
            +
                  c = [ ]
         | 
| 56 | 
            +
                  d = { }
         | 
| 57 | 
            +
                  get_config.each do | target_name, target_config |
         | 
| 58 | 
            +
                    annotate_error "target=#{target_name}" do
         | 
| 59 | 
            +
                      t = parse_target(target_name)
         | 
| 60 | 
            +
                      # _log { "#{target_name.inspect} => #{t.inspect}" }
         | 
| 61 | 
            +
                      case target_config
         | 
| 62 | 
            +
                      when true, false
         | 
| 63 | 
            +
                        target_config = { :enabled => target_config }
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                      t.update(target_config) if target_config
         | 
| 66 | 
            +
                      [ :advice, :require ].each do | k |
         | 
| 67 | 
            +
                        t[k] = as_array(t[k]) if t.key?(k)
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                      case
         | 
| 70 | 
            +
                      when t[:meth].nil? && t[:mod].nil? # global default.
         | 
| 71 | 
            +
                        d[nil] = t
         | 
| 72 | 
            +
                      when t[:meth].nil? # module default.
         | 
| 73 | 
            +
                        d[t[:mod]] = t
         | 
| 74 | 
            +
                      else
         | 
| 75 | 
            +
                        c << t # real target
         | 
| 76 | 
            +
                      end
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                  d[nil] ||= { }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # Second pass: merge defaults with target.
         | 
| 82 | 
            +
                  @targets = [ ]
         | 
| 83 | 
            +
                  c.each do | t |
         | 
| 84 | 
            +
                    x = merge!(d[nil].dup, d[t[:mod]] || EMPTY_Hash)
         | 
| 85 | 
            +
                    t = merge!(x, t)
         | 
| 86 | 
            +
                    # _log { "target = #{t.inspect}" }
         | 
| 87 | 
            +
                    next if t[:enabled] == false
         | 
| 88 | 
            +
                    @targets << t
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  enable!
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  self
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def disable!
         | 
| 97 | 
            +
                  @targets.each do | t |
         | 
| 98 | 
            +
                    (t[:advice] || EMPTY_Array).each do | advice_name |
         | 
| 99 | 
            +
                      advice_name = advice_name.to_sym
         | 
| 100 | 
            +
                      if advised = (t[:advised] || EMPTY_Hash)[advice_name]
         | 
| 101 | 
            +
                        advised.disable!
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                  @advised.clear
         | 
| 106 | 
            +
                  self
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def enable!
         | 
| 110 | 
            +
                  @advised.clear
         | 
| 111 | 
            +
                  @targets.each do | t |
         | 
| 112 | 
            +
                    t_str = target_as_string(t)
         | 
| 113 | 
            +
                    annotate_error "target=#{t_str.inspect}" do
         | 
| 114 | 
            +
                      (t[:require] || EMPTY_Array).each do | r |
         | 
| 115 | 
            +
                        _log { "#{t_str}: require #{r}" }
         | 
| 116 | 
            +
                        unless @required[r]
         | 
| 117 | 
            +
                          require r
         | 
| 118 | 
            +
                          @required[r] = true
         | 
| 119 | 
            +
                        end
         | 
| 120 | 
            +
                      end
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                        
         | 
| 123 | 
            +
                    (t[:advice] || EMPTY_Array).each do | advice_name |
         | 
| 124 | 
            +
                      advice_name = advice_name.to_sym
         | 
| 125 | 
            +
                      annotate_error "target=#{target_as_string(t)} advice=#{advice_name.inspect}" do
         | 
| 126 | 
            +
                        unless advice = @advice[advice_name]
         | 
| 127 | 
            +
                          raise Error, "no advice by that name"
         | 
| 128 | 
            +
                        end
         | 
| 129 | 
            +
                        options = t[:options][nil]
         | 
| 130 | 
            +
                        options = merge!(options, t[:options][advice_name])
         | 
| 131 | 
            +
                        # _log { "#{t.inspect} options => #{options.inspect}" }
         | 
| 132 | 
            +
                        
         | 
| 133 | 
            +
                        advised = advice.advise!(t[:mod], t[:meth], t[:kind], options)
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                        (t[:advised] ||= { })[advice_name] = advised
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                        @advised << advised
         | 
| 138 | 
            +
                      end
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                  self
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                def _log msg = nil
         | 
| 145 | 
            +
                  return self unless @verbose
         | 
| 146 | 
            +
                  msg ||= yield if block_given?
         | 
| 147 | 
            +
                  $stderr.puts "#{self.class}: #{msg}"
         | 
| 148 | 
            +
                  self
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                def annotate_error x
         | 
| 152 | 
            +
                  yield
         | 
| 153 | 
            +
                rescue Exception => err
         | 
| 154 | 
            +
                  msg = "in #{x.inspect}: #{err.inspect}"
         | 
| 155 | 
            +
                  _log { "ERROR: #{msg}\n  #{err.backtrace * "\n  "}" }
         | 
| 156 | 
            +
                  raise Error, msg, err.backtrace
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def get_config
         | 
| 160 | 
            +
                  case @config
         | 
| 161 | 
            +
                  when Hash
         | 
| 162 | 
            +
                    @config
         | 
| 163 | 
            +
                  when Proc
         | 
| 164 | 
            +
                    @config.call(self)
         | 
| 165 | 
            +
                  when nil
         | 
| 166 | 
            +
                    raise Error, "no config"
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                def as_array x
         | 
| 171 | 
            +
                  x = EMPTY_Array if x == nil
         | 
| 172 | 
            +
                  x = x.split(/\s+|\s*,\s*/) if String === x
         | 
| 173 | 
            +
                  raise "Unexpected Hash" if Hash === x
         | 
| 174 | 
            +
                  x
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                def parse_target x
         | 
| 178 | 
            +
                  case x
         | 
| 179 | 
            +
                  when nil
         | 
| 180 | 
            +
                    { }
         | 
| 181 | 
            +
                  when Hash
         | 
| 182 | 
            +
                    x
         | 
| 183 | 
            +
                  when String, Symbol
         | 
| 184 | 
            +
                    if x.to_s =~ /\A([a-z0-9_:]+)(?:([#\.])([a-z0-9_]+[=\!\?]?))?\Z/i
         | 
| 185 | 
            +
                      { :mod => $1,
         | 
| 186 | 
            +
                        :kind => $2 && ($2 == '.' ? :module : :instance),
         | 
| 187 | 
            +
                        :meth => $3,
         | 
| 188 | 
            +
                      }
         | 
| 189 | 
            +
                    else
         | 
| 190 | 
            +
                      raise Error, "cannot parse #{x.inspect}"
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
                def target_as_string t
         | 
| 195 | 
            +
                  "#{t[:mod]}#{t[:kind] == :instance ? '#' : '.'}#{t[:meth]}"
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                def merge! dst, src
         | 
| 199 | 
            +
                  case dst
         | 
| 200 | 
            +
                  when nil, Hash
         | 
| 201 | 
            +
                    case src
         | 
| 202 | 
            +
                    when Hash
         | 
| 203 | 
            +
                      dst = dst ? dst.dup : { }
         | 
| 204 | 
            +
                      src.each do | k, v |
         | 
| 205 | 
            +
                        dst[k] = merge!(dst[k], v)
         | 
| 206 | 
            +
                      end
         | 
| 207 | 
            +
                    else
         | 
| 208 | 
            +
                      dst = src
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
                  else
         | 
| 211 | 
            +
                    dst = src
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
                  dst
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
              end
         | 
| 217 | 
            +
            end
         | 
| @@ -0,0 +1,228 @@ | |
| 1 | 
            +
            require 'cheap_advice'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'time' # Time#iso8601
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class CheapAdvice
         | 
| 6 | 
            +
              # Sample Tracing Advice factory.
         | 
| 7 | 
            +
              module Trace
         | 
| 8 | 
            +
                def self.new opts = nil
         | 
| 9 | 
            +
                  opts ||= { }
         | 
| 10 | 
            +
                  trace = CheapAdvice.new(:around, opts) do | ar, body |
         | 
| 11 | 
            +
                    a = ar.advice
         | 
| 12 | 
            +
                    ad = ar.advised
         | 
| 13 | 
            +
                    logger = ad.logger[:name] || ad.logger_default[:name]
         | 
| 14 | 
            +
                    logger = a.logger[logger] || a.logger_default[logger]
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    formatter = nil
         | 
| 17 | 
            +
                    if ad[:log_before] != false
         | 
| 18 | 
            +
                      a.log(logger) do
         | 
| 19 | 
            +
                        formatter = a.new_formatter(logger)
         | 
| 20 | 
            +
                        ar[:time_before] = Time.now
         | 
| 21 | 
            +
                        formatter.record(ar, :before)
         | 
| 22 | 
            +
                      end
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    body.call
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    if ad[:log_after] != false
         | 
| 28 | 
            +
                      a.log(logger) do
         | 
| 29 | 
            +
                        formatter ||= a.new_formatter(logger)
         | 
| 30 | 
            +
                        ar[:time_after] = Time.now
         | 
| 31 | 
            +
                        if ar.error
         | 
| 32 | 
            +
                          ar[:error] = ar.error   if ad[:log_error] != false
         | 
| 33 | 
            +
                        else
         | 
| 34 | 
            +
                          ar[:result] = ar.result if ad[:log_result] != false
         | 
| 35 | 
            +
                        end
         | 
| 36 | 
            +
                        formatter.record(ar, :after)
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  trace.extend(Behavior)
         | 
| 41 | 
            +
                  trace.advised_extend = Behavior
         | 
| 42 | 
            +
                  trace
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                module Behavior
         | 
| 46 | 
            +
                  def logger
         | 
| 47 | 
            +
                    # $stderr.puts " #{self.class} @options = #{@options.inspect}"
         | 
| 48 | 
            +
                    @options[:logger] ||= { }
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                  def logger_default
         | 
| 51 | 
            +
                    logger[nil] ||= { }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def new_formatter logger
         | 
| 55 | 
            +
                    formatter(logger).new(logger, *formatter_options(logger))
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def formatter logger
         | 
| 59 | 
            +
                    logger[:formatter] ||= 
         | 
| 60 | 
            +
                      logger_default[:formatter] ||= 
         | 
| 61 | 
            +
                      DefaultFormatter
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def formatter_options logger
         | 
| 65 | 
            +
                    logger[:formatter_options] ||=
         | 
| 66 | 
            +
                      logger_default[:formatter_options] ||=
         | 
| 67 | 
            +
                      [ ]
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  def log_prefix logger, ar
         | 
| 71 | 
            +
                    pre = 
         | 
| 72 | 
            +
                      logger[:log_prefix] ||= 
         | 
| 73 | 
            +
                      logger_default[:log_prefix] ||= 
         | 
| 74 | 
            +
                      EMPTY_String
         | 
| 75 | 
            +
                    case pre
         | 
| 76 | 
            +
                    when Proc
         | 
| 77 | 
            +
                      pre.call(ar)
         | 
| 78 | 
            +
                    else
         | 
| 79 | 
            +
                      pre
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def log logger, msg = nil
         | 
| 84 | 
            +
                    return msg unless logger
         | 
| 85 | 
            +
                    msg ||= yield if block_given?
         | 
| 86 | 
            +
                    return msg if msg.nil?
         | 
| 87 | 
            +
                    dst = logger[:target]
         | 
| 88 | 
            +
                    case dst
         | 
| 89 | 
            +
                    when nil
         | 
| 90 | 
            +
                      nil
         | 
| 91 | 
            +
                    when IO
         | 
| 92 | 
            +
                      dst.seek(0, IO::SEEK_END)
         | 
| 93 | 
            +
                      dst.puts msg.to_s
         | 
| 94 | 
            +
                      dst.flush
         | 
| 95 | 
            +
                    when Proc
         | 
| 96 | 
            +
                      dst.call(msg)
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      dst.send(logger[:method] || :debug, msg)
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    msg
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def log_all msg = nil
         | 
| 104 | 
            +
                    logger.values.each do | dst |
         | 
| 105 | 
            +
                      log(dst) { msg ||= yield if block_given?; msg }
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                    msg
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                class BaseFormatter
         | 
| 113 | 
            +
                  attr_reader :logger
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def initialize logger, *args
         | 
| 116 | 
            +
                    @logger = logger
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  def format obj, mode
         | 
| 120 | 
            +
                    case mode
         | 
| 121 | 
            +
                    when :rcvr
         | 
| 122 | 
            +
                      obj && obj.to_s
         | 
| 123 | 
            +
                    when :module
         | 
| 124 | 
            +
                      obj && obj.name
         | 
| 125 | 
            +
                    when :time
         | 
| 126 | 
            +
                      obj && obj.iso8601(6)
         | 
| 127 | 
            +
                    when :error
         | 
| 128 | 
            +
                      obj.inspect
         | 
| 129 | 
            +
                    when :result
         | 
| 130 | 
            +
                      obj.inspect
         | 
| 131 | 
            +
                    when :method
         | 
| 132 | 
            +
                      ad = ar.meth_to_s
         | 
| 133 | 
            +
                    else
         | 
| 134 | 
            +
                      nil
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
                  
         | 
| 139 | 
            +
                class DefaultFormatter < BaseFormatter
         | 
| 140 | 
            +
                  def format obj, mode
         | 
| 141 | 
            +
                    case mode
         | 
| 142 | 
            +
                    when :error
         | 
| 143 | 
            +
                      return "ERROR #{obj.inspect}"
         | 
| 144 | 
            +
                    when :result
         | 
| 145 | 
            +
                      return "=> #{obj.inspect}"
         | 
| 146 | 
            +
                    when :time
         | 
| 147 | 
            +
                      return super
         | 
| 148 | 
            +
                    else
         | 
| 149 | 
            +
                      obj = super || obj
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    obj = obj.inspect
         | 
| 153 | 
            +
                    if mode == :args
         | 
| 154 | 
            +
                      obj = obj.to_s.gsub(/\A\[|\]\Z/, '')
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                    obj
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  # Formats the ActivationRecord for the logger.
         | 
| 160 | 
            +
                  def record ar, mode
         | 
| 161 | 
            +
                    ad = ar.advised
         | 
| 162 | 
            +
                    msg = nil
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    case mode
         | 
| 165 | 
            +
                    when :before, :after
         | 
| 166 | 
            +
                      msg = ad.log_prefix(logger, ar).to_s
         | 
| 167 | 
            +
                      msg = msg.dup if msg.frozen?
         | 
| 168 | 
            +
                      ar[:args] ||= format(ar.args, :args) if ad[:log_args] != false
         | 
| 169 | 
            +
                      ar[:meth] ||= "#{ad.meth_to_s} #{ar.rcvr.class}"
         | 
| 170 | 
            +
                      msg << "#{format(ar[:"time_#{mode}"], :time)} #{ar[:meth]}"
         | 
| 171 | 
            +
                      msg << " #{format(ar.rcvr, :rcvr)}" if ad[:log_rcvr]
         | 
| 172 | 
            +
                      msg << " ( #{ar[:args]} )" if ar[:args]
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    case mode
         | 
| 176 | 
            +
                    when :before
         | 
| 177 | 
            +
                      msg << " {"
         | 
| 178 | 
            +
                    when :after
         | 
| 179 | 
            +
                      msg << " }"
         | 
| 180 | 
            +
                      if ar.error
         | 
| 181 | 
            +
                        msg << " #{format(ar[:error],  :error )}" if ad[:log_error]  != false
         | 
| 182 | 
            +
                      else
         | 
| 183 | 
            +
                        msg << " #{format(ar[:result], :result)}" if ad[:log_result] != false
         | 
| 184 | 
            +
                      end
         | 
| 185 | 
            +
                    end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                    msg
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                end # class
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                class YamlFormatter < BaseFormatter
         | 
| 192 | 
            +
                  def to_hash ar, mode
         | 
| 193 | 
            +
                    ad = ar.advised
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                    data = (ar.advice[:log_data] || EMPTY_Hash).dup
         | 
| 196 | 
            +
                    data.update(ar.advised[:log_data] || EMPTY_Hash)
         | 
| 197 | 
            +
                    # pp [ :'ar.data=', ar.data ]
         | 
| 198 | 
            +
                    data.update(ar.data)
         | 
| 199 | 
            +
                    # pp [ :'data=', data ]
         | 
| 200 | 
            +
                    if x = ad.log_prefix(logger, ar)
         | 
| 201 | 
            +
                      data[:log_prefix] = x
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
                    data[:meth] = ar.meth
         | 
| 204 | 
            +
                    data[:mod] = Module === (x = ar.mod) ? x.name : x
         | 
| 205 | 
            +
                    data[:kind] = ar.kind
         | 
| 206 | 
            +
                    data[:signature] = ar.meth_to_s
         | 
| 207 | 
            +
                    data[:rcvr] = format(ar.rcvr, :rcvr) if ad[:log_rcvr]
         | 
| 208 | 
            +
                    data[:rcvr_class] = ar.rcvr.class.name
         | 
| 209 | 
            +
                    if x = data[:time_after] && 
         | 
| 210 | 
            +
                        data[:time_before] && 
         | 
| 211 | 
            +
                        (data[:time_after].to_f - data[:time_before].to_f)
         | 
| 212 | 
            +
                      data[:time_elapsed] = x
         | 
| 213 | 
            +
                    end
         | 
| 214 | 
            +
                    data
         | 
| 215 | 
            +
                  end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                  def record ar, mode
         | 
| 218 | 
            +
                    case mode
         | 
| 219 | 
            +
                    when :after
         | 
| 220 | 
            +
                      data = to_hash(ar, mode)
         | 
| 221 | 
            +
                      YAML.dump(data)
         | 
| 222 | 
            +
                    else
         | 
| 223 | 
            +
                      nil
         | 
| 224 | 
            +
                    end
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
              end
         | 
| 228 | 
            +
            end
         | 
| @@ -0,0 +1,342 @@ | |
| 1 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class CheapAdvice
         | 
| 4 | 
            +
              module Test
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                module M
         | 
| 7 | 
            +
                  attr_accessor :_m
         | 
| 8 | 
            +
                  def m(arg)
         | 
| 9 | 
            +
                    @_m = 1 + arg
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                  def self.mm(arg)
         | 
| 12 | 
            +
                    3 + arg
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                class Foo
         | 
| 17 | 
            +
                  include M
         | 
| 18 | 
            +
                  attr_accessor :foo, :bar
         | 
| 19 | 
            +
                  attr_reader :_baz, :_bar
         | 
| 20 | 
            +
                  (class << self; self; end).instance_eval do 
         | 
| 21 | 
            +
                    attr_accessor :_baz
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  
         | 
| 24 | 
            +
                  def self.baz(arg)
         | 
| 25 | 
            +
                    self._baz = 3 + arg
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def baz(arg)
         | 
| 29 | 
            +
                    @_baz = 5 + arg
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def do_it(arg)
         | 
| 33 | 
            +
                    yield(arg + 7) + 2
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
                
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                class Bar
         | 
| 39 | 
            +
                  include M
         | 
| 40 | 
            +
                  attr_accessor :bar
         | 
| 41 | 
            +
                  attr_reader :_baz
         | 
| 42 | 
            +
                  
         | 
| 43 | 
            +
                  def baz(arg)
         | 
| 44 | 
            +
                    @_baz = 7 + arg
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def calls_private_method(arg)
         | 
| 48 | 
            +
                    private_method(arg)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                                           
         | 
| 51 | 
            +
                  private
         | 
| 52 | 
            +
                  def private_method(arg)
         | 
| 53 | 
            +
                    arg
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  protected
         | 
| 57 | 
            +
                  def protected_method(arg)
         | 
| 58 | 
            +
                    arg
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                class Baz < Bar
         | 
| 63 | 
            +
                  def calls_protected_method(arg)
         | 
| 64 | 
            +
                    protected_method(arg)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
             | 
| 72 | 
            +
            describe "CheapAdvice" do
         | 
| 73 | 
            +
              attr_reader :tracing_advice
         | 
| 74 | 
            +
              attr_reader :f, :b
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              before(:each) do
         | 
| 77 | 
            +
                @tracing_advice = CheapAdvice.new(:around) do | ar, body |
         | 
| 78 | 
            +
                  ar.advice.log "  TRACE: before #{ar.rcvr.class}\##{ar.meth}(#{ar.args.join(", ")})"
         | 
| 79 | 
            +
                  ar.advice.log "         foo = #{@foo.inspect}"
         | 
| 80 | 
            +
                  ar.advice.log "         bar = #{@bar.inspect}"
         | 
| 81 | 
            +
                  result = body.call
         | 
| 82 | 
            +
                  ar.advice.log "  TRACE: after  #{ar.rcvr.class}\##{ar.meth}(#{ar.args.join(", ")}) => #{result.inspect}"
         | 
| 83 | 
            +
                  ar.result = "yo!"
         | 
| 84 | 
            +
                  ar.advice.log "  TRACE: return #{ar.result.inspect}"
         | 
| 85 | 
            +
                  "oy!" # Not relevant.
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
                @tracing_advice.instance_eval do
         | 
| 88 | 
            +
                  def log msg = nil
         | 
| 89 | 
            +
                    return @log unless msg
         | 
| 90 | 
            +
                    (@log ||= [ ]) << msg.dup
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              it "handles simple tracing_advice example." do
         | 
| 96 | 
            +
                @f = CheapAdvice::Test::Foo.new
         | 
| 97 | 
            +
                @b = CheapAdvice::Test::Bar.new
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                assert_without_advice
         | 
| 100 | 
            +
                tracing_advice.log.should == nil
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                tracing_advice.advise!( [CheapAdvice::Test::Foo, CheapAdvice::Test::Bar], [ :bar, :bar=, :baz ])
         | 
| 103 | 
            +
                f.foo = 10
         | 
| 104 | 
            +
                f.foo.should == 10
         | 
| 105 | 
            +
                f.baz(10).should == "yo!"
         | 
| 106 | 
            +
                b.bar = 101
         | 
| 107 | 
            +
                b.bar.should == "yo!"
         | 
| 108 | 
            +
                b.baz(10).should == "yo!"
         | 
| 109 | 
            +
                tracing_advice.log.should_not == nil
         | 
| 110 | 
            +
                tracing_advice.log.size.should == 20
         | 
| 111 | 
            +
                
         | 
| 112 | 
            +
                tracing_advice.unadvise!
         | 
| 113 | 
            +
                assert_without_advice
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              def assert_without_advice
         | 
| 117 | 
            +
                f.foo = 10
         | 
| 118 | 
            +
                f.foo.should == 10
         | 
| 119 | 
            +
                f.baz(10).should == 15
         | 
| 120 | 
            +
                f._baz.should == 15
         | 
| 121 | 
            +
                
         | 
| 122 | 
            +
                b.bar = 101
         | 
| 123 | 
            +
                b.bar.should == 101
         | 
| 124 | 
            +
                b.baz(10).should == 17
         | 
| 125 | 
            +
                b._baz.should == 17
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              it 'handles methods with blocks.' do
         | 
| 129 | 
            +
                ars = [ ]
         | 
| 130 | 
            +
                basic_advice = CheapAdvice.new(:before) do | ar |
         | 
| 131 | 
            +
                  ars << ar
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
                basic_advice.advised.size.should == 0
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                @f = CheapAdvice::Test::Foo.new
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                assert_do_it f
         | 
| 138 | 
            +
                ars.size.should == 0
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                basic_advice.advise! CheapAdvice::Test::Foo, 'do_it'
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                basic_advice.advised.size.should == 1
         | 
| 143 | 
            +
                advised = basic_advice.advised.first
         | 
| 144 | 
            +
                advised.mod.should == CheapAdvice::Test::Foo
         | 
| 145 | 
            +
                advised.meth.should == :do_it
         | 
| 146 | 
            +
                advised.enabled.should == true
         | 
| 147 | 
            +
                
         | 
| 148 | 
            +
                assert_do_it f
         | 
| 149 | 
            +
                ars.size.should == 1
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                advised.unadvise!
         | 
| 152 | 
            +
                advised.enabled.should == false
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                assert_do_it f
         | 
| 155 | 
            +
                ars.size.should == 1
         | 
| 156 | 
            +
              end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
              def assert_do_it f
         | 
| 159 | 
            +
                arg = nil
         | 
| 160 | 
            +
                result = f.do_it(10) do | _arg |
         | 
| 161 | 
            +
                  _arg.should == 17
         | 
| 162 | 
            +
                  arg = _arg
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
                arg.should == 17
         | 
| 165 | 
            +
                result.should == 19
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              it 'handles applying the same advice only once.' do
         | 
| 169 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
                null_advice.advised.size.should == 0
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                advised = null_advice.advise! CheapAdvice::Test::Foo, :do_it
         | 
| 174 | 
            +
                null_advice.advised.size.should == 1
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                advised_again = null_advice.advise! CheapAdvice::Test::Foo, :do_it
         | 
| 177 | 
            +
                advised_again.object_id.should == advised.object_id
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                null_advice.advised.size.should == 1
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                advised = null_advice.advise! CheapAdvice::Test::Foo, :baz, :class
         | 
| 182 | 
            +
                null_advice.advised.size.should == 2
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                advised_again = null_advice.advise! CheapAdvice::Test::Foo, :baz, :class
         | 
| 185 | 
            +
                advised_again.object_id.should == advised.object_id
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                null_advice.advised.size.should == 2
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                advised.unadvise!
         | 
| 190 | 
            +
              end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
              it 'handles String for class and method names.' do
         | 
| 193 | 
            +
                advice_called = 0
         | 
| 194 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 195 | 
            +
                  advice_called += 1
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
                null_advice.advised.size.should == 0
         | 
| 198 | 
            +
             | 
| 199 | 
            +
             | 
| 200 | 
            +
                @f = CheapAdvice::Test::Foo.new
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                advice_called.should == 0
         | 
| 203 | 
            +
                
         | 
| 204 | 
            +
                advised = null_advice.advise!('CheapAdvice::Test::Foo', 'baz')
         | 
| 205 | 
            +
                null_advice.advised.size.should == 1
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                @f.baz(5).should == 10
         | 
| 208 | 
            +
                @f._baz.should == 10
         | 
| 209 | 
            +
                advice_called.should == 1
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                advised.unadvise!
         | 
| 212 | 
            +
              end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
              it 'handles Module instance method advice.' do
         | 
| 215 | 
            +
                advice_called = 0
         | 
| 216 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 217 | 
            +
                  advice_called += 1
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
                null_advice.advised.size.should == 0
         | 
| 220 | 
            +
             | 
| 221 | 
            +
             | 
| 222 | 
            +
                @f = CheapAdvice::Test::Foo.new
         | 
| 223 | 
            +
                @b = CheapAdvice::Test::Bar.new
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                advice_called.should == 0
         | 
| 226 | 
            +
                
         | 
| 227 | 
            +
                advised = null_advice.advise!('CheapAdvice::Test::M', 'm')
         | 
| 228 | 
            +
                null_advice.advised.size.should == 1
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                @f.m(5).should == 6
         | 
| 231 | 
            +
                @f._m.should == 6
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                @b.m(5).should == 6
         | 
| 234 | 
            +
                @b._m.should == 6
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                advice_called.should == 2
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                advised.unadvise!
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
              it 'handles Module singleton method advice.' do
         | 
| 242 | 
            +
                advice_called = 0
         | 
| 243 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 244 | 
            +
                  advice_called += 1
         | 
| 245 | 
            +
                end
         | 
| 246 | 
            +
                null_advice.advised.size.should == 0
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                advice_called.should == 0
         | 
| 249 | 
            +
                
         | 
| 250 | 
            +
                advised = null_advice.advise!(CheapAdvice::Test::M, :mm, :module)
         | 
| 251 | 
            +
                null_advice.advised.size.should == 1
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                CheapAdvice::Test::M.mm(5).should == 8
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                advice_called.should == 1
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                advised.unadvise!
         | 
| 258 | 
            +
              end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
              it 'handles Class method advice.' do
         | 
| 261 | 
            +
                advice_called = 0
         | 
| 262 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 263 | 
            +
                  advice_called += 1
         | 
| 264 | 
            +
                end
         | 
| 265 | 
            +
                null_advice.advised.size.should == 0
         | 
| 266 | 
            +
             | 
| 267 | 
            +
             | 
| 268 | 
            +
                @f = CheapAdvice::Test::Foo.new
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                advice_called.should == 0
         | 
| 271 | 
            +
                CheapAdvice::Test::Foo._baz.should == nil
         | 
| 272 | 
            +
                
         | 
| 273 | 
            +
                advised = null_advice.advise!(CheapAdvice::Test::Foo, :baz, :class)
         | 
| 274 | 
            +
                null_advice.advised.size.should == 1
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                CheapAdvice::Test::Foo.baz(5).should == 8
         | 
| 277 | 
            +
                CheapAdvice::Test::Foo._baz.should == 8
         | 
| 278 | 
            +
                @f.baz(5).should == 10
         | 
| 279 | 
            +
                @f._baz.should == 10
         | 
| 280 | 
            +
                advice_called.should == 1
         | 
| 281 | 
            +
             | 
| 282 | 
            +
                advised.unadvise!
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                CheapAdvice::Test::Foo.baz(7).should == 10
         | 
| 285 | 
            +
                CheapAdvice::Test::Foo._baz.should == 10
         | 
| 286 | 
            +
                @f.baz(5).should == 10
         | 
| 287 | 
            +
                @f._baz.should == 10
         | 
| 288 | 
            +
                advice_called.should == 1
         | 
| 289 | 
            +
              end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
              it 'handles private method advice.' do
         | 
| 292 | 
            +
                advice_called = 0
         | 
| 293 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 294 | 
            +
                  advice_called += 1
         | 
| 295 | 
            +
                end
         | 
| 296 | 
            +
                null_advice.advised.size.should == 0
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                advice_called.should == 0
         | 
| 299 | 
            +
                
         | 
| 300 | 
            +
                advised = null_advice.advise!(CheapAdvice::Test::Bar, :private_method)
         | 
| 301 | 
            +
                advised.scope.should == :private
         | 
| 302 | 
            +
                null_advice.advised.size.should == 1
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                @b = CheapAdvice::Test::Bar.new
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                @b.calls_private_method(5).should == 5
         | 
| 307 | 
            +
                advice_called.should == 1
         | 
| 308 | 
            +
             | 
| 309 | 
            +
                advised.unadvise!
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                @b.calls_private_method(5).should == 5
         | 
| 312 | 
            +
                advice_called.should == 1
         | 
| 313 | 
            +
              end
         | 
| 314 | 
            +
             | 
| 315 | 
            +
             | 
| 316 | 
            +
              it 'handles protected method advice.' do
         | 
| 317 | 
            +
                advice_called = 0
         | 
| 318 | 
            +
                null_advice = CheapAdvice.new(:before) do | ar |
         | 
| 319 | 
            +
                  advice_called += 1
         | 
| 320 | 
            +
                end
         | 
| 321 | 
            +
                null_advice.advised.size.should == 0
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                advice_called.should == 0
         | 
| 324 | 
            +
                
         | 
| 325 | 
            +
                advised = null_advice.advise!(CheapAdvice::Test::Bar, :protected_method)
         | 
| 326 | 
            +
                advised.scope.should == :protected
         | 
| 327 | 
            +
                null_advice.advised.size.should == 1
         | 
| 328 | 
            +
             | 
| 329 | 
            +
                @b = CheapAdvice::Test::Baz.new
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                @b.calls_protected_method(5).should == 5
         | 
| 332 | 
            +
                advice_called.should == 1
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                advised.unadvise!
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                @b.calls_protected_method(5).should == 5
         | 
| 337 | 
            +
                advice_called.should == 1
         | 
| 338 | 
            +
              end
         | 
| 339 | 
            +
             | 
| 340 | 
            +
            end
         | 
| 341 | 
            +
             | 
| 342 | 
            +
             |