class RBS::Test::Tester

Attributes

env[R]
instance_testers[R]
singleton_testers[R]
targets[R]

Public Class Methods

new(env:) click to toggle source
# File lib/rbs/test/tester.rb, line 9
def initialize(env:)
  @env = env
  @targets = []
  @instance_testers = {}
  @singleton_testers = {}
end

Public Instance Methods

builder() click to toggle source
# File lib/rbs/test/tester.rb, line 20
def builder
  @builder ||= DefinitionBuilder.new(env: env)
end
factory() click to toggle source
# File lib/rbs/test/tester.rb, line 16
def factory
  @factory ||= Factory.new
end
install!(klass, sample_size:, unchecked_classes:) click to toggle source
# File lib/rbs/test/tester.rb, line 40
def install!(klass, sample_size:, unchecked_classes:)
  RBS.logger.info { "Installing runtime type checker in #{klass}..." }

  type_name = factory.type_name(klass.name).absolute!

  builder.build_instance(type_name).tap do |definition|
    instance_key = new_key(type_name, "InstanceChecker")
    tester, set = instance_testers[klass] ||= [
      MethodCallTester.new(klass, builder, definition, kind: :instance, sample_size: sample_size, unchecked_classes: unchecked_classes),
      Set[]
    ]
    Observer.register(instance_key, tester)

    definition.methods.each do |name, method|
      if reason = skip_method?(type_name, method)
        unless reason == :implemented_in
          RBS.logger.info { "Skipping ##{name} because of `#{reason}`..." }
        end
      else
        if !set.include?(name) && (
            name == :initialize ||
            klass.instance_methods(false).include?(name) ||
            klass.private_instance_methods(false).include?(name))
          RBS.logger.info { "Setting up method hook in ##{name}..." }
          Hook.hook_instance_method klass, name, key: instance_key
          set << name
        end
      end
    end
  end

  builder.build_singleton(type_name).tap do |definition|
    singleton_key = new_key(type_name, "SingletonChecker")
    tester, set = singleton_testers[klass] ||= [
      MethodCallTester.new(klass.singleton_class, builder, definition, kind: :singleton, sample_size: sample_size, unchecked_classes: unchecked_classes),
      Set[]
    ]
    Observer.register(singleton_key, tester)

    definition.methods.each do |name, method|
      if reason = skip_method?(type_name, method)
        unless reason == :implemented_in
          RBS.logger.info { "Skipping .#{name} because of `#{reason}`..." }
        end
      else
        if klass.methods(false).include?(name) && !set.include?(name)
          RBS.logger.info { "Setting up method hook in .#{name}..." }
          Hook.hook_singleton_method klass, name, key: singleton_key
          set << name
        end
      end
    end
  end

  targets << klass
end
new_key(type_name, prefix) click to toggle source
# File lib/rbs/test/tester.rb, line 97
def new_key(type_name, prefix)
  "#{prefix}__#{type_name}__#{SecureRandom.hex(10)}"
end
skip_method?(type_name, method) click to toggle source
# File lib/rbs/test/tester.rb, line 24
def skip_method?(type_name, method)
  if method.implemented_in == type_name
    if method.annotations.any? {|a| a.string == "rbs:test:skip" }
      :skip
    else
      false
    end
  else
    if method.annotations.any? {|a| a.string == "rbs:test:target" }
      false
    else
      :implemented_in
    end
  end
end