123456789_123456789_123456789_123456789_123456789_

Class: RBS::Test::Tester

Relationships & Source Files
Namespace Children
Classes:
Exceptions:
Inherits: Object
Defined in: lib/rbs/test/tester.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(env:) ⇒ Tester

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 11

def initialize(env:)
  @env = env
  @targets = []
  @instance_testers = {}
  @singleton_testers = {}
end

Instance Attribute Details

#env (readonly)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 6

attr_reader :env

#instance_testers (readonly)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 8

attr_reader :instance_testers

#singleton_testers (readonly)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 9

attr_reader :singleton_testers

#targets (readonly)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 7

attr_reader :targets

Instance Method Details

#builder

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 22

def builder
  @builder ||= DefinitionBuilder.new(env: env)
end

#factory

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 18

def factory
  @factory ||= Factory.new
end

#install!(klass, sample_size:, unchecked_classes:)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 42

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)

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 99

def new_key(type_name, prefix)
  "#{prefix}__#{type_name}__#{SecureRandom.hex(10)}"
end

#skip_method?(type_name, method) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rbs/test/tester.rb', line 26

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