Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/kernel.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ module Kernel : BasicObject
#
def self?.fail: () -> bot
| (string message, ?cause: Exception?) -> bot
| (_Exception exception, ?_ToS? message, ?String | Array[String] | Array[Thread::Backtrace::Location] | nil backtrace, ?cause: Exception?) -> bot
| (_Exception exception, ?string | _ToS message, ?String | Array[String] | Array[Thread::Backtrace::Location] | nil backtrace, ?cause: Exception?) -> bot
| (_Exception exception, ?cause: Exception?, **untyped) -> bot

# <!--
Expand Down
25 changes: 25 additions & 0 deletions lib/rbs/unit_test/type_assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,31 @@ def send_setup(method_type, receiver, method, args, proc)
end
end

ruby2_keywords 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

ruby2_keywords 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(
Expand Down
11 changes: 11 additions & 0 deletions sig/unit_test/type_assertions.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ module RBS
#
def refute_send_type: (String | MethodType method_type, untyped receiver, Symbol method_name, *untyped args) ?{ () -> untyped } -> void

# 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.
#
def assert_send_type_error: (String | MethodType method_type, Class exception, untyped receiver, Symbol method_name, *untyped args) ?{ () -> untyped } -> void

# Asserts if the constant `constant_name` has `constant_type`, and the RBS definition has compatible type
#
# ```ruby
Expand Down
144 changes: 144 additions & 0 deletions test/stdlib/Kernel_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,150 @@ def test_String
Kernel, :String, ToS.new
end

def test_abort
old_stderr = $stderr
$stderr = File.open(File::NULL, 'w')

assert_send_type_error '() -> bot', SystemExit,
Kernel, :abort

with_string 'oops' do |message|
assert_send_type_error '(string) -> bot', SystemExit,
Kernel, :abort, message
end
ensure
$stderr.close rescue nil
$stderr = old_stderr
end

def test_exit
assert_send_type_error '() -> bot', SystemExit,
Kernel, :exit

with_int.and with_bool do |status|
assert_send_type_error '(int | bool) -> bot', SystemExit,
Kernel, :exit, status
end
end

def test_exit!
# Sadly can't use `assert_send_type_error`, so we use exit status to check.
_, status = Process.wait2(Process.spawn(RUBY_EXECUTABLE, '--disable=all', '-e', 'exit!; exit(80)'))
assert_equal 1, status.exitstatus

_, status = Process.wait2(Process.spawn(RUBY_EXECUTABLE, '--disable=all', '-e', 'exit!(true); exit(80)'))
assert_equal 0, status.exitstatus

_, status = Process.wait2(Process.spawn(RUBY_EXECUTABLE, '--disable=all', '-e', 'exit!(false); exit(80)'))
assert_equal 1, status.exitstatus

_, status = Process.wait2(Process.spawn(RUBY_EXECUTABLE, '--disable=all', '-e', 'exit!(12); exit(80)'))
assert_equal 12, status.exitstatus

_, status = Process.wait2(Process.spawn(RUBY_EXECUTABLE, '--disable=all', '-e', <<~'RUBY'))
# hardcode a "blank slate" object in
class ToInt < BasicObject
instance_methods.each do |im|
next if im == :__id__
next if im == :__send__
undef_method im
end

def to_int = 12
end

exit!(ToInt.new)
exit(80)
RUBY
assert_equal 12, status.exitstatus
end

def test_at_exit
assert_send_type "() { () -> void } -> Proc",
Kernel, :at_exit do end
end

def test_catch
assert_send_type "() { (Object) -> untyped } -> untyped",
Kernel, :catch do end
assert_send_type "[T] (T) { (T) -> untyped } -> untyped",
Kernel, :catch, Object.new do end
end

def test_throw
# Make sure it requires an arg
refute_send_type "() -> bot",
Kernel, :throw

with_untyped do |tag|
assert_send_type_error '(untyped) -> bot', UncaughtThrowError,
Kernel, :throw, tag
with_untyped do |obj|
assert_send_type_error '(untyped, untyped) -> bot', UncaughtThrowError,
Kernel, :throw, tag, obj
end
end
end

TestException = Class.new(Exception)

def test_raise(method: :raise)
assert_send_type_error '() -> bot', RuntimeError,
Kernel, method

cause = TestException.new

with_string do |message|
assert_send_type_error '(string) -> bot', RuntimeError,
Kernel, method, message
assert_send_type_error '(string, cause: nil) -> bot', RuntimeError,
Kernel, method, message, cause: nil
assert_send_type_error '(string, cause: Exception) -> bot', RuntimeError,
Kernel, method, message, cause: cause
end

exception = BlankSlate.new
def exception.exception(mesasage = nil) = TestException.new

assert_send_type_error '(_Exception) -> bot', TestException,
Kernel, method, exception
assert_send_type_error '(_Exception, cause: nil) -> bot', TestException,
Kernel, method, exception, cause: nil
assert_send_type_error '(_Exception, cause: Exception) -> bot', TestException,
Kernel, method, exception, cause: cause

with_string.and ToS.new, nil do |message|
assert_send_type_error '(_Exception, string | _ToS) -> bot', TestException,
Kernel, method, exception, message
assert_send_type_error '(_Exception, string | _ToS, cause: nil) -> bot', TestException,
Kernel, method, exception, message, cause: nil
assert_send_type_error '(_Exception, string | _ToS, cause: Exception) -> bot', TestException,
Kernel, method, exception, message, cause: cause

with "bt", caller, caller_locations, nil do |backtrace|
assert_send_type_error '(_Exception, string | _ToS, String | Array[String] | Array[Thread::Backtrace::Location] | nil) -> bot', TestException,
Kernel, method, exception, message, backtrace
assert_send_type_error '(_Exception, string | _ToS, String | Array[String] | Array[Thread::Backtrace::Location] | nil, cause: nil) -> bot', TestException,
Kernel, method, exception, message, backtrace, cause: nil
assert_send_type_error '(_Exception, string | _ToS, String | Array[String] | Array[Thread::Backtrace::Location] | nil, cause: Exception) -> bot', TestException,
Kernel, method, exception, message, backtrace, cause: cause
end
end

with_untyped do |value|
assert_send_type_error '(_Exception, **untyped) -> bot', TestException,
Kernel, method, exception, key: value
assert_send_type_error '(_Exception, cause: nil, **untyped) -> bot', TestException,
Kernel, method, exception, cause: nil, key: value
assert_send_type_error '(_Exception, cause: Exception, **untyped) -> bot', TestException,
Kernel, method, exception, cause: cause, key: value
end
end

def test_fail
test_raise method: :fail
end

def test_autoload?
with_interned :TestModuleForAutoload do |interned|
assert_send_type "(::interned) -> String?",
Expand Down