module RBS::UnitTest::TypeAssertions
TypeAssertions provides assertions to test RBS type definitions in unit test
class FooInstanceTest < Test::Unit::TestCase include RBS::UnitTest::TypeAssertions testing "::Foo" def test_foo assert_send_type( "(String) -> Integer", Foo.new, :foo, "hello" ) end end
The module provides four assertions:
-
assert_send_typeto confirm if a method call has the expected method type -
refute_send_typeto confirm if a method call doesn’t have the method type -
assert_const_typeto confirm if a constant has an expected type -
assert_typeto confirm aRubyvalue has aRBStype
See .testing and .library methods to set up RBS type testing.
Public Class Methods
(Module) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 57 def self.included(base) base.extend ClassMethods end
Public Instance Methods
() { () → void } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 347 def allow_non_simple_method_type() begin @allows_non_simple_method_type = true yield rescue @allows_non_simple_method_type = false end end
Allow non simple-type method types given to assert_send_type and refute_send_type
allow_non_simple_method_type do assert_send_type("() -> self", ...) end
(*Exception) { () → void } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 279 def allows_error(*errors) yield rescue *errors => exn notify "Error allowed: #{exn.inspect}" end
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 285 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
Asserts if the constant constant_name has constant_type, and the RBS definition has compatible type
assert_const_type("Array[String]", "::Foo::Bar")
The assertion above succeeds if ::Foo::Bar is ["foo"] and RBS contains ::Foo::Bar: [untyped]. It fails if ::Foo::Bar is [2], or the RBS definition is ::Foo::Bar: String.
(String | MethodType method_type, untyped receiver, Symbol method_name, *untyped args) ?{ () → untyped } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 171 def assert_send_type(method_type, receiver, method, *args, &block) send_setup(method_type, receiver, method, args, block) do |method_type, trace, result, exception| typecheck = RBS::Test::TypeCheck.new( self_class: receiver.class, builder: builder, sample_size: 100, unchecked_classes: [], instance_class: instance_class, class_class: class_class ) errors = typecheck.method_call(method, method_type, trace, errors: []) assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}" method_defs = method_defs(method) all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) } assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" raise exception if exception result end end
Calls a method method_name and validates if it’s compatible with RBS type definition
-
It calls
method_namewithreceiverpassingargsand given block, -
Validates if it’s compatible with given
method_type, and -
Validates if it’s also compatible with one of the overloads defined in
RBStype definition
assert_send_type( "(::Integer) -> ::Integer", [], :sum, 8 )
To test methods that takes a block, pass a block to assert_send_type. We recommend using &proc { ... } syntax for textual representation and prevent from using break from the block.
assert_send_type( "() { () -> void } -> void", self, :loop, &proc { break_from_block } )
Exiting from the block using break and return skips assertions. Use break_from_block instead.
method_type must be simple. It raises an exception if it’s not simple. When you really need non-simple method_type, wrap the calls inside allow_non_simple_method_type. See docs/stdlib.md for the details.
(String | MethodType method_type, Class exception, untyped receiver, Symbol method_name, *untyped args) ?{ () → untyped } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 195 def assert_send_type_error(method_type, error_type, receiver, method, *args, &block) send_setup(method_type, receiver, method, args, block) do |method_type, trace, result, exception| typecheck = RBS::Test::TypeCheck.new( self_class: receiver.class, builder: builder, sample_size: 100, unchecked_classes: [], instance_class: instance_class, class_class: class_class ) errors = typecheck.method_call(method, method_type, trace, errors: []) assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}" method_defs = method_defs(method) all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) } assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" # Use `instnace_of?` instead of `is_a?` as we want to check for _the exact exception class_. assert exception.instance_of? error_type result end end
Calls a method method_name, validates if it’s compatible with RBS type definition, and ensures it raises the exception class.
-
It calls
method_namewithreceiverpassingargsand given block, -
Validates if it’s compatible with given
method_type, and -
Validates if it’s compatible with one of the overloads defined in
RBStype definition -
Ensures that
exceptionis thrown
See assert_send_type for the details.
(String | Types::t value_type, untyped value) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 326 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
Asserts if given value has a type of value_type
(?untyped value) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 364 def break_from_block(value = nil) raise "Cannot break without `@break_tag`" unless @break_tag throw @break_tag, value end
Break from assert_send_type or refute_send_type
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 65 def builder (_ = self.class).builder end
() → Class
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 121 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
The singleton class object that is associated to the class type of the testing type
() → Environment
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 61 def env (_ = self.class).env end
() → Class
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 112 def instance_class type, _ = target case type when RBS::Types::ClassSingleton, RBS::Types::ClassInstance Object.const_get(type.name.to_s) end end
The class object that is associated to the instance type of the testing type
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 255 def method_defs(method) type, definition = target case type when Types::ClassInstance, Types::ClassSingleton if type.is_a?(Types::ClassSingleton) && type.args.empty? definition.methods[method].defs else 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 end else raise end end
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 275 def method_types(method) method_defs(method).map(&:type) end
(?String? message) → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 369 def pass(message = nil) assert true, message end
(String | MethodType method_type, untyped receiver, Symbol method_name, *untyped args) ?{ () → untyped } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 220 def refute_send_type(method_type, receiver, method, *args, &block) send_setup(method_type, receiver, method, args, block) do |method_type, trace, result, exception| method_type = method_type.update( block: if method_type.block RBS::Types::Block.new( type: method_type.block.type.with_return_type(RBS::Types::Bases::Any.new(location: nil)), required: method_type.block.required, self_type: nil ) end, type: method_type.type.with_return_type(RBS::Types::Bases::Any.new(location: nil)) ) typecheck = RBS::Test::TypeCheck.new( self_class: receiver.class, instance_class: instance_class, class_class: class_class, builder: builder, sample_size: 100, unchecked_classes: [] ) errors = typecheck.method_call(method, method_type, trace, errors: []) assert_operator exception, :is_a?, ::Exception assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) } method_defs = method_defs(method) all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) } assert all_errors.all? {|es| es.size > 0 }, "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" result end end
Calls a method method_name and validates if it’s not compatible with RBS type definition
-
It calls
method_namewithreceiverpassingargsand given block, -
Validates if it’s compatible with given
method_type, and -
Validates if it’s not compatible with none of the overloads defined in
RBStype definition
See assert_send_type for the details.
[T] (String | RBS::MethodType method_type, untyped receiver, Symbol method_name, Array[untyped] args, Proc?) { (RBS::MethodType, Test::CallTrace, untyped, Exception?) → T } → T
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 130 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
() → [ target_type, Definition ]
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 73 def target targets.last || (_ = self.class).target end
() → Array[[ target_type, Definition ]]
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 69 def targets @targets ||= [] end
(String | target_type) ?{ () → void } → void
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 77 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
Source
# File vendor/bundle/ruby/4.0.0/gems/rbs-4.0.3/lib/rbs/unit_test/type_assertions.rb, line 356 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