Module: RBS::UnitTest::TypeAssertions

Defined in:
sig/unit_test/type_assertions.rbs,
lib/rbs/unit_test/type_assertions.rb

Overview

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_type to confirm if a method call has the expected method type
  • refute_send_type to confirm if a method call doesn't have the method type
  • assert_const_type to confirm if a constant has an expected type
  • assert_type to confirm a Ruby value has a RBS type

See .testing and .library methods to set up RBS type testing.

Defined Under Namespace

Modules: ClassMethods, _BaseAssertions

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ void

This method returns an undefined value.

Parameters:



57
58
59
# File 'lib/rbs/unit_test/type_assertions.rb', line 57

def self.included(base)
  base.extend ClassMethods
end

Instance Method Details

#allow_non_simple_method_type { ... } ⇒ void

This method returns an undefined value.

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

Yields:

Yield Returns:

  • (void)


194
195
196
197
198
199
200
201
# File 'sig/unit_test/type_assertions.rbs', line 194

def allow_non_simple_method_type()
  begin
    @allows_non_simple_method_type = true
    yield
  rescue
    @allows_non_simple_method_type = false
  end
end

#allows_error(*errors) { ... } ⇒ void

This method returns an undefined value.

Parameters:

  • (Exception)

Yields:

Yield Returns:

  • (void)


279
280
281
282
283
# File 'lib/rbs/unit_test/type_assertions.rb', line 279

def allows_error(*errors)
  yield
rescue *errors => exn
  notify "Error allowed: #{exn.inspect}"
end

#assert_const_type(type, constant_name) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • constant_type (String, Types::t)
  • constant_name (String)


176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'sig/unit_test/type_assertions.rbs', line 176

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

#assert_send_typevoid

This method returns an undefined value.

Calls a method method_name and validates if it's compatible with RBS type definition

  1. It calls method_name with receiver passing args and given block,
  2. Validates if it's compatible with given method_type, and
  3. Validates if it's also compatible with one of the overloads defined in RBS type 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.

Parameters:

  • method_type (String, MethodType)
  • receiver (Object)
  • method_name (Symbol)
  • args (Object)


144
# File 'sig/unit_test/type_assertions.rbs', line 144

def assert_send_type: (String | MethodType method_type, untyped receiver, Symbol method_name, *untyped args) ?{ () -> untyped } -> void

#assert_send_type_errorvoid

This method returns an undefined value.

Calls a method method_name, validates if it's compatible with RBS type definition, and ensures it raises the exception class.

  1. It calls method_name with receiver passing args and given block,
  2. Validates if it's compatible with given method_type, and
  3. Validates if it's compatible with one of the overloads defined in RBS type definition
  4. Ensures that exception is thrown

See assert_send_type for the details.

Parameters:

  • method_type (String, MethodType)
  • exception (Class)
  • receiver (Object)
  • method_name (Symbol)
  • args (Object)


165
# File 'sig/unit_test/type_assertions.rbs', line 165

def assert_send_type_error: (String | MethodType method_type, Class exception, untyped receiver, Symbol method_name, *untyped args) ?{ () -> untyped } -> void

#assert_type(type, value) ⇒ void

This method returns an undefined value.

Asserts if given value has a type of value_type

Parameters:

  • value_type (String, Types::t)
  • value (Object)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'sig/unit_test/type_assertions.rbs', line 180

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

#assert_visibility(visibility, method) ⇒ void

This method returns an undefined value.

Asserts if RBS definition declares given method_name with visibility

Parameters:

  • visibility (:private, :public)
  • method_name (Symbol)


184
185
186
187
188
189
190
191
# File 'sig/unit_test/type_assertions.rbs', line 184

def assert_visibility(visibility, method)
  _, definition = target
  method_entry = definition.methods[method]

  assert method_entry, "Method `#{method}` not found in RBS definition"
  assert visibility == method_entry.accessibility,
    "Expected `#{method}` to be #{visibility}, but was #{method_entry.accessibility}"
end

#break_from_block(value = nil) ⇒ void

This method returns an undefined value.

Break from assert_send_type or refute_send_type

Parameters:

  • value (Object) (defaults to: nil)


202
203
204
205
# File 'sig/unit_test/type_assertions.rbs', line 202

def break_from_block(value = nil)
  raise "Cannot break without `@break_tag`" unless @break_tag
  throw @break_tag, value
end

#builderDefinitionBuilder

Returns:



65
66
67
# File 'lib/rbs/unit_test/type_assertions.rb', line 65

def builder
  (_ = self.class).builder
end

#class_classClass

The singleton class object that is associated to the class type of the testing type

Returns:

  • (Class)


105
106
107
108
109
110
111
112
# File 'sig/unit_test/type_assertions.rbs', line 105

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

#envEnvironment

Returns:



61
62
63
# File 'lib/rbs/unit_test/type_assertions.rb', line 61

def env
  (_ = self.class).env
end

#instance_classClass

The class object that is associated to the instance type of the testing type

Returns:

  • (Class)


101
102
103
104
105
106
107
108
# File 'sig/unit_test/type_assertions.rbs', line 101

def instance_class
  type, _ = target

  case type
  when RBS::Types::ClassSingleton, RBS::Types::ClassInstance
    Object.const_get(type.name.to_s)
  end
end

#method_defs(method) ⇒ Array[Definition::Method::TypeDef]

Parameters:

  • (Symbol)

Returns:



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File '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

#method_types(method) ⇒ Array[MethodType]

Parameters:

  • (Symbol)

Returns:



275
276
277
# File 'lib/rbs/unit_test/type_assertions.rb', line 275

def method_types(method)
  method_defs(method).map(&:type)
end

#pass(message = nil) ⇒ void

This method returns an undefined value.

Parameters:

  • message (String, nil) (defaults to: nil)


378
379
380
# File 'lib/rbs/unit_test/type_assertions.rb', line 378

def pass(message = nil)
  assert true, message
end

#refute_send_typevoid

This method returns an undefined value.

Calls a method method_name and validates if it's not compatible with RBS type definition

  1. It calls method_name with receiver passing args and given block,
  2. Validates if it's compatible with given method_type, and
  3. Validates if it's not compatible with none of the overloads defined in RBS type definition

See assert_send_type for the details.

Parameters:

  • method_type (String, MethodType)
  • receiver (Object)
  • method_name (Symbol)
  • args (Object)


154
# File 'sig/unit_test/type_assertions.rbs', line 154

def refute_send_type: (String | MethodType method_type, untyped receiver, Symbol method_name, *untyped args) ?{ () -> untyped } -> void

#send_setup(method_type, receiver, method, args, proc) {|mt, last_trace, result, exception| ... } ⇒ void

This method returns an undefined value.

Yields:

  • (mt, last_trace, result, exception)


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File '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[target_type, Definition]

Returns:



73
74
75
# File 'lib/rbs/unit_test/type_assertions.rb', line 73

def target
  targets.last || (_ = self.class).target
end

#targetsArray[[target_type, Definition]]

Returns:



69
70
71
# File 'lib/rbs/unit_test/type_assertions.rb', line 69

def targets
  @targets ||= []
end

#testing(type_or_string) ⇒ void

This method returns an undefined value.

Parameters:

  • target_type (String)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File '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

#validate_simple_method_type(type) ⇒ void

This method returns an undefined value.

Parameters:



365
366
367
368
369
370
371
# File 'lib/rbs/unit_test/type_assertions.rb', line 365

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