123456789_123456789_123456789_123456789_123456789_

Module: RBS::Test::Hook

Relationships & Source Files
Defined in: lib/rbs/test/hook.rb

Constant Summary

  • OPERATORS =
    # File 'lib/rbs/test/hook.rb', line 9
    {
      :[] => "indexlookup",
      :[]= => "indexset",
      :== => "eqeq",
      :=== => "eqeqeq",
      :!= => "noteq",
      :+ => "plus",
      :- => "minus",
      :* => "star",
      :/ => "slash",
      :> => "gt",
      :>= => "gteq",
      :< => "lt",
      :<= => "lteq",
      :<=> => "ufo",
      :& => "amp",
      :| => "vbar",
      :^ => "hat",
      :! => "not",
      :<< => "lshift",
      :>> => "rshift",
      :~ => "tilda",
      :=~ => "eqtilda",
      :% => "percent",
      :+@ => "unary_plus",
      :-@ => "unary_minus"
    }

Class Method Summary

Class Method Details

.alias_names(target, random)

[ GitHub ]

  
# File 'lib/rbs/test/hook.rb', line 36

def self.alias_names(target, random)
  suffix = "#{RBS::Test.suffix}_#{random}"

  case target
  when *OPERATORS.keys
    name = OPERATORS[target]
    [
      "#{name}____with__#{suffix}",
      "#{name}____without__#{suffix}"
    ]
  else
    aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1

    [
      "#{aliased_target}__with__#{suffix}#{punctuation}",
      "#{aliased_target}__without__#{suffix}#{punctuation}"
    ]
  end
end

.hook_instance_method(klass, method, key:)

[ GitHub ]

  
# File 'lib/rbs/test/hook.rb', line 188

def self.hook_instance_method(klass, method, key:)
  random = SecureRandom.hex(4)
  params = klass.instance_method(method).parameters
  line, source = hook_method_source("#{klass}#", method, key, random: random, params: params)

  klass.module_eval(source, __FILE__, line)
  setup_alias_method_chain klass, method, random: random
end

.hook_method_source(prefix, method_name, key, random:, params:)

[ GitHub ]

  
# File 'lib/rbs/test/hook.rb', line 76

def self.hook_method_source(prefix, method_name, key, random:, params:)
  with_name, without_name = alias_names(method_name, random)
  full_method_name = "#{prefix}#{method_name}"

  param_source = params.take_while {|param| param[0] == :req }
                       .map.with_index {|pair, index| pair[1] || "__req__#{random}__#{index}" }
  param_source.push("*rest_args__#{random}")
  block_param = "block__#{random}"

  RBS.logger.debug {
    "Generating method definition: def #{with_name}(#{param_source.join(", ")}, &#{block_param}) ..."
  }

  [__LINE__ + 1, <<RUBY]
def #{with_name}(#{param_source.join(", ")}, &#{block_param})
  args = [#{param_source.join(", ")}]
  ::RBS.logger.debug { "#{full_method_name} with arguments: [" + args.map(&:inspect).join(", ") + "]" }

  begin
    return_from_call = false
    block_calls = []

    if block_given?
receiver = self
block_receives_block = #{block_param}.parameters.last&.yield_self {|type, _| type == :block }

wrapped_block = proc do |*block_args, &block2|
  return_from_block = false

  begin
    block_result = if receiver.equal?(self)
                     if block_receives_block
                       #{block_param}.call(*block_args, &block2)
                     else
                       yield(*block_args)
                     end
                   else
                     instance_exec(*block_args, &#{block_param})
                   end

    return_from_block = true
  ensure
    exn = $!

    case
    when return_from_block
      # Returned from yield
      block_calls << ::RBS::Test::ArgumentsReturn.return(
        arguments: block_args,
        value: block_result
      )
    when exn
      # Exception
      block_calls << ::RBS::Test::ArgumentsReturn.exception(
        arguments: block_args,
        exception: exn
      )
    else
      # break?
      block_calls << ::RBS::Test::ArgumentsReturn.break(
        arguments: block_args
      )
    end
  end

  block_result
end.ruby2_keywords

result = __send__(:"#{without_name}", *args, &wrapped_block)
    else
result = __send__(:"#{without_name}", *args)
    end
    return_from_call = true
    result
  ensure
    exn = $!

    case
    when return_from_call
::RBS.logger.debug { "#{full_method_name} return with value: " + result.inspect }
method_call = ::RBS::Test::ArgumentsReturn.return(
  arguments: args,
  value: result
)
    when exn
::RBS.logger.debug { "#{full_method_name} exit with exception: " + exn.inspect }
method_call = ::RBS::Test::ArgumentsReturn.exception(
  arguments: args,
  exception: exn
)
    else
::RBS.logger.debug { "#{full_method_name} exit with jump" }
method_call = ::RBS::Test::ArgumentsReturn.break(arguments: args)
    end

    trace = ::RBS::Test::CallTrace.new(
method_name: #{method_name.inspect},
method_call: method_call,
block_calls: block_calls,
block_given: block_given?,
    )

    ::RBS::Test::Observer.notify(#{key.inspect}, self, trace)
  end

  result
end

ruby2_keywords :#{with_name}
RUBY
end

.hook_singleton_method(klass, method, key:)

[ GitHub ]

  
# File 'lib/rbs/test/hook.rb', line 197

def self.hook_singleton_method(klass, method, key:)
  random = SecureRandom.hex(4)
  params = klass.method(method).parameters
  line, source = hook_method_source("#{klass}.",method, key, random: random, params: params)

  klass.singleton_class.module_eval(source, __FILE__, line)
  setup_alias_method_chain klass.singleton_class, method, random: random
end

.setup_alias_method_chain(klass, target, random:)

[ GitHub ]

  
# File 'lib/rbs/test/hook.rb', line 56

def self.setup_alias_method_chain(klass, target, random:)
  with_method, without_method = alias_names(target, random)

  RBS.logger.debug "alias name: #{target}, #{with_method}, #{without_method}"

  klass.instance_eval do
    alias_method without_method, target
    alias_method target, with_method

    case
    when public_method_defined?(without_method)
      public target
    when protected_method_defined?(without_method)
      protected target
    when private_method_defined?(without_method)
      private target
    end
  end
end