module RBS::UnitTest::TypeAssertions
def self.included(base)
def self.included(base) base.extend ClassMethods end
def allow_non_simple_method_type()
def allow_non_simple_method_type() begin @allows_non_simple_method_type = true yield rescue @allows_non_simple_method_type = false end end
def allows_error(*errors)
def allows_error(*errors) yield rescue *errors => exn notify "Error allowed: #{exn.inspect}" end
def assert_const_type(type, constant_name)
def assert_const_type(type, constant_name) constant = Object.const_get(constant_name) typecheck = RBS::Test::TypeCheck.new( self_class: constant.class, instance_class: instance_class, class_class: class_class, builder: builder, sample_size: 100, unchecked_classes: [] ) value_type = case type when String RBS::Parser.parse_type(type, variables: []) || raise else type end assert typecheck.value(constant, value_type), "`#{constant_name}` (#{constant.inspect}) must be compatible with given type `#{value_type}`" type_name = TypeName.parse(constant_name).absolute! definition = env.constant_entry(type_name) assert definition, "Cannot find RBS type definition of `#{constant_name}`" case definition when RBS::Environment::ClassEntry, RBS::Environment::ModuleEntry definition_type = RBS::Types::ClassSingleton.new(name: type_name, location: nil) when RBS::Environment::ClassAliasEntry, RBS::Environment::ModuleAliasEntry type_name = env.normalize_type_name!(type_name) definition_type = RBS::Types::ClassSingleton.new(name: type_name, location: nil) when RBS::Environment::ConstantEntry definition_type = definition.decl.type end assert definition_type, "Cannot find RBS entry for `#{constant_name}`" definition_type or raise assert typecheck.value(constant, definition_type), "`#{constant_name}` (#{constant.inspect}) must be compatible with RBS type definition `#{definition_type}`" end
def assert_send_type(method_type, receiver, method, *args, &block)
def assert_send_type(method_type, receiver, method, *args, &block) thod_type, receiver, method, args, block) do |method_type, trace, result, exception| RBS::Test::TypeCheck.new( s: receiver.class, builder, ze: 100, _classes: [], class: instance_class, ss: class_class pecheck.method_call(method, method_type, trace, errors: []) y errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}" = method_defs(method) = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) } errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" tion if exception
def assert_type(type, value)
def assert_type(type, value) typecheck = RBS::Test::TypeCheck.new( self_class: value.class, instance_class: _ = "No `instance` class allowed", class_class: _ = "No `class` class allowed", builder: builder, sample_size: 100, unchecked_classes: [] ) type = case type when String RBS::Parser.parse_type(type, variables: []) or raise else type end assert typecheck.value(value, type), "`#{value.inspect}` must be compatible with given type `#{type}`" end
def break_from_block(value = nil)
def break_from_block(value = nil) raise "Cannot break without `@break_tag`" unless @break_tag throw @break_tag, value end
def builder
def builder (_ = self.class).builder end
def class_class
def class_class type, _ = target case type when RBS::Types::ClassSingleton, RBS::Types::ClassInstance Object.const_get(type.name.to_s).singleton_class end end
def env
def env (_ = self.class).env end
def instance_class
def instance_class type, _ = target case type when RBS::Types::ClassSingleton, RBS::Types::ClassInstance Object.const_get(type.name.to_s) end end
def method_defs(method)
def method_defs(method) type, definition = target case type when Types::ClassInstance subst = RBS::Substitution.build(definition.type_params, type.args) definition.methods[method].defs.map do |type_def| type_def.update( type: type_def.type.sub(subst) ) end when Types::ClassSingleton definition.methods[method].defs else raise end end
def method_types(method)
def method_types(method) method_defs(method).map(&:type) end
def pass(message = nil)
def pass(message = nil) assert true, message end
def refute_send_type(method_type, receiver, method, *args, &block)
def refute_send_type(method_type, receiver, method, *args, &block) thod_type, receiver, method, args, block) do |method_type, trace, result, exception| = method_type.update( od_type.block Types::Block.new( e: method_type.block.type.with_return_type(RBS::Types::Bases::Any.new(location: nil)), uired: method_type.block.required, f_type: nil hod_type.type.with_return_type(RBS::Types::Bases::Any.new(location: nil)) RBS::Test::TypeCheck.new( s: receiver.class, class: instance_class, ss: class_class, builder, ze: 100, _classes: [] pecheck.method_call(method, method_type, trace, errors: []) ator exception, :is_a?, ::Exception y errors.map {|x| RBS::Test::Errors.to_string(x) } = method_defs(method) = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) } errors.all? {|es| es.size > 0 }, "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"
def send_setup(method_type, receiver, method, args, proc)
def send_setup(method_type, receiver, method, args, proc) mt = case method_type when String RBS::Parser.parse_method_type(method_type, variables: []) || raise when RBS::MethodType method_type end validate_simple_method_type(mt) trace = [] #: Array[Test::CallTrace] spy = Spy.wrap(receiver, method) spy.callback = -> (result) { trace << result } result = nil #: untyped exception = nil #: Exception? non_jump_exit = true begin result = catch do |tag| @break_tag = tag spy.wrapped_object.__send__(method, *args, &proc) ensure @break_tag = nil end non_jump_exit = false rescue Exception => exn exception = exn ensure if non_jump_exit && !exception raise "`break` nor `return` from blocks given to `assert_send_type` are prohibited. Use `#break_from_block` instead." end end last_trace = trace.last or raise "empty trace" yield(mt, last_trace, result, exception) end
def target
def target targets.last || (_ = self.class).target end
def targets
def targets @targets ||= [] end
def testing(type_or_string)
def testing(type_or_string) type = case type_or_string when String RBS::Parser.parse_type(type_or_string, variables: []) else type_or_string end definition = case type when RBS::Types::ClassInstance builder.build_instance(type.name) when RBS::Types::ClassSingleton builder.build_singleton(type.name) else raise "Test target should be class instance or class singleton: #{type}" end targets.push( [ type, #: target_type definition ] ) if block_given? begin yield ensure targets.pop end else [type, definition] end end
def validate_simple_method_type(type)
def validate_simple_method_type(type) return if @allows_non_simple_method_type refute_predicate type, :has_self_type?, "`self` types is prohibited in method type: `#{type}`" refute_predicate type, :has_classish_type?, "`instance` and `class` types is prohibited in method type: `#{type}`" refute_predicate type, :with_nonreturn_void?, "`void` is only allowed at return type or generics parameters: `#{type}`" end