1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_exception.rb
Jeremy Evans 80b5a0ff2a
Make rb_scan_args handle keywords more similar to Ruby methods (#2460)
Cfuncs that use rb_scan_args with the : entry suffer similar keyword
argument separation issues that Ruby methods suffer if the cfuncs
accept optional or variable arguments.

This makes the following changes to : handling.

* Treats as **kw, prompting keyword argument separation warnings
  if called with a positional hash.

* Do not look for an option hash if empty keywords are provided.
  For backwards compatibility, treat an empty keyword splat as a empty
  mandatory positional hash argument, but emit a a warning, as this
  behavior will be removed in Ruby 3.  The argument number check
  needs to be moved lower so it can correctly handle an empty
  positional argument being added.

* If the last argument is nil and it is necessary to treat it as an option
  hash in order to make sure all arguments are processed, continue to
  treat the last argument as the option hash. Emit a warning in this case,
  as this behavior will be removed in Ruby 3.

* If splitting the keyword hash into two hashes, issue a warning, as we
  will not be splitting hashes in Ruby 3.

* If the keyword argument is required to fill a mandatory positional
  argument, continue to do so, but emit a warning as this behavior will
  be going away in Ruby 3.

* If keyword arguments are provided and the last argument is not a hash,
  that indicates something wrong. This can happen if a cfunc is calling
  rb_scan_args multiple times, and providing arguments that were not
  passed to it from Ruby.  Callers need to switch to the new
  rb_scan_args_kw function, which allows passing of whether keywords
  were provided.

This commit fixes all warnings caused by the changes above.

It switches some function calls to *_kw versions with appropriate
kw_splat flags. If delegating arguments, RB_PASS_CALLED_KEYWORDS
is used.  If creating new arguments, RB_PASS_KEYWORDS is used if
the last argument is a hash to be treated as keywords.

In open_key_args in io.c, use rb_scan_args_kw.
In this case, the arguments provided come from another C
function, not Ruby.  The last argument may or may not be a hash,
so we can't set keyword argument mode.  However, if it is a
hash, we don't want to warn when treating it as keywords.

In Ruby files, make sure to appropriately use keyword splats
or literal keywords when calling Cfuncs that now issue keyword
argument separation warnings through rb_scan_args.  Also, make
sure not to pass nil in place of an option hash.

Work around Kernel#warn warnings due to problems in the Rubygems
override of the method.  There is an open pull request to fix
these issues in Rubygems, but part of the Rubygems tests for
their override fail on ruby-head due to rb_scan_args not
recognizing empty keyword splats, which this commit fixes.

Implementation wise, adding rb_scan_args_kw is kind of a pain,
because rb_scan_args takes a variable number of arguments.
In order to not duplicate all the code, the function internals need
to be split into two functions taking a va_list, and to avoid passing
in a ton of arguments, a single struct argument is used to handle
the variables previously local to the function.
2019-09-25 11:18:49 -07:00

1482 lines
35 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
require 'tempfile'
class TestException < Test::Unit::TestCase
def test_exception_rescued
begin
raise "this must be handled"
assert(false)
rescue
assert(true)
end
end
def test_exception_retry
bad = true
begin
raise "this must be handled no.2"
rescue
if bad
bad = false
retry
end
assert(!bad)
end
assert(true)
end
def test_exception_in_rescue
string = "this must be handled no.3"
assert_raise_with_message(RuntimeError, string) do
begin
raise "exception in rescue clause"
rescue
raise string
end
assert(false)
end
end
def test_exception_in_ensure
string = "exception in ensure clause"
assert_raise_with_message(RuntimeError, string) do
begin
raise "this must be handled no.4"
ensure
assert_instance_of(RuntimeError, $!)
assert_equal("this must be handled no.4", $!.message)
raise "exception in ensure clause"
end
assert(false)
end
end
def test_exception_ensure
bad = true
begin
begin
raise "this must be handled no.5"
ensure
bad = false
end
rescue
end
assert(!bad)
end
def test_exception_ensure_2 # just duplication?
bad = true
begin
begin
raise "this must be handled no.6"
ensure
bad = false
end
rescue
end
assert(!bad)
end
def test_errinfo_in_debug
bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do
def to_s
require '\0'
rescue LoadError
self.class.to_s
end
end
err = EnvUtil.verbose_warning do
assert_raise(bug9568) do
$DEBUG, debug = true, $DEBUG
begin
raise bug9568
ensure
$DEBUG = debug
end
end
end
assert_include(err, bug9568.to_s)
end
def test_errinfo_encoding_in_debug
exc = Module.new {break class_eval("class C\u{30a8 30e9 30fc} < RuntimeError; self; end".encode(Encoding::EUC_JP))}
exc.inspect
err = EnvUtil.verbose_warning do
assert_raise(exc) do
$DEBUG, debug = true, $DEBUG
begin
raise exc
ensure
$DEBUG = debug
end
end
end
assert_include(err, exc.to_s)
end
def test_break_ensure
bad = true
while true
begin
break
ensure
bad = false
end
end
assert(!bad)
end
def test_catch_no_throw
assert_equal(:foo, catch {:foo})
end
def test_catch_throw
result = catch(:foo) {
loop do
loop do
throw :foo, true
break
end
assert(false, "should not reach here")
end
false
}
assert(result)
end
def test_catch_throw_noarg
assert_nothing_raised(UncaughtThrowError) {
result = catch {|obj|
throw obj, :ok
assert(false, "should not reach here")
}
assert_equal(:ok, result)
}
end
def test_uncaught_throw
tag = nil
e = assert_raise_with_message(UncaughtThrowError, /uncaught throw/) {
catch("foo") {|obj|
tag = obj.dup
throw tag, :ok
assert(false, "should not reach here")
}
assert(false, "should not reach here")
}
assert_not_nil(tag)
assert_same(tag, e.tag)
assert_equal(:ok, e.value)
end
def test_catch_throw_in_require
bug7185 = '[ruby-dev:46234]'
Tempfile.create(["dep", ".rb"]) {|t|
t.puts("throw :extdep, 42")
t.close
assert_equal(42, assert_throw(:extdep, bug7185) {require t.path}, bug7185)
}
end
def test_throw_false
bug12743 = '[ruby-core:77229] [Bug #12743]'
Thread.start {
e = assert_raise_with_message(UncaughtThrowError, /false/, bug12743) {
throw false
}
assert_same(false, e.tag, bug12743)
}.join
end
def test_else_no_exception
begin
assert(true)
rescue
assert(false)
else
assert(true)
end
end
def test_else_raised
begin
assert(true)
raise
assert(false)
rescue
assert(true)
else
assert(false)
end
end
def test_else_nested_no_exception
begin
assert(true)
begin
assert(true)
rescue
assert(false)
else
assert(true)
end
assert(true)
rescue
assert(false)
else
assert(true)
end
end
def test_else_nested_rescued
begin
assert(true)
begin
assert(true)
raise
assert(false)
rescue
assert(true)
else
assert(false)
end
assert(true)
rescue
assert(false)
else
assert(true)
end
end
def test_else_nested_unrescued
begin
assert(true)
begin
assert(true)
rescue
assert(false)
else
assert(true)
end
assert(true)
raise
assert(false)
rescue
assert(true)
else
assert(false)
end
end
def test_else_nested_rescued_reraise
begin
assert(true)
begin
assert(true)
raise
assert(false)
rescue
assert(true)
else
assert(false)
end
assert(true)
raise
assert(false)
rescue
assert(true)
else
assert(false)
end
end
def test_raise_with_wrong_number_of_arguments
assert_raise(TypeError) { raise nil }
assert_raise(TypeError) { raise 1, 1 }
assert_raise(ArgumentError) { raise 1, 1, 1, 1 }
end
def test_type_error_message_encoding
c = eval("Module.new do break class C\u{4032}; self; end; end")
o = c.new
assert_raise_with_message(TypeError, /C\u{4032}/) do
""[o]
end
c.class_eval {def to_int; self; end}
assert_raise_with_message(TypeError, /C\u{4032}/) do
""[o]
end
c.class_eval {def to_a; self; end}
assert_raise_with_message(TypeError, /C\u{4032}/) do
[*o]
end
obj = eval("class C\u{1f5ff}; self; end").new
assert_raise_with_message(TypeError, /C\u{1f5ff}/) do
Class.new {include obj}
end
end
def test_errat
assert_in_out_err([], "p $@", %w(nil), [])
assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/)
assert_in_out_err([], <<-INPUT, [], /backtrace must be Array of String \(TypeError\)$/)
begin
raise
rescue
$@ = 1
end
INPUT
assert_in_out_err([], <<-INPUT, [], /^foo: unhandled exception$/)
begin
raise
rescue
$@ = 'foo'
raise
end
INPUT
assert_in_out_err([], <<-INPUT, [], /^foo: unhandled exception\s+from bar\s+from baz$/)
begin
raise
rescue
$@ = %w(foo bar baz)
raise
end
INPUT
end
def test_thread_signal_location
skip
_, stderr, _ = EnvUtil.invoke_ruby(%w"--disable-gems -d", <<-RUBY, false, true)
Thread.start do
Thread.current.report_on_exception = false
begin
Process.kill(:INT, $$)
ensure
raise "in ensure"
end
end.join
RUBY
assert_not_match(/:0/, stderr, "[ruby-dev:39116]")
end
def test_errinfo
begin
raise "foo"
assert(false)
rescue => e
assert_equal(e, $!)
1.times { assert_equal(e, $!) }
end
assert_equal(nil, $!)
end
def test_inspect
assert_equal("#<Exception: Exception>", Exception.new.inspect)
e = Class.new(Exception)
e.class_eval do
def to_s; ""; end
end
assert_equal(e.inspect, e.new.inspect)
end
def test_to_s
e = StandardError.new("foo")
assert_equal("foo", e.to_s)
def (s = Object.new).to_s
"bar"
end
e = StandardError.new(s)
assert_equal("bar", e.to_s)
end
def test_set_backtrace
e = Exception.new
e.set_backtrace("foo")
assert_equal(["foo"], e.backtrace)
e.set_backtrace(%w(foo bar baz))
assert_equal(%w(foo bar baz), e.backtrace)
assert_raise(TypeError) { e.set_backtrace(1) }
assert_raise(TypeError) { e.set_backtrace([1]) }
end
def test_exit_success_p
begin
exit
rescue SystemExit => e
end
assert_send([e, :success?], "success by default")
begin
exit(true)
rescue SystemExit => e
end
assert_send([e, :success?], "true means success")
begin
exit(false)
rescue SystemExit => e
end
assert_not_send([e, :success?], "false means failure")
begin
abort
rescue SystemExit => e
end
assert_not_send([e, :success?], "abort means failure")
end
def test_nomethoderror
bug3237 = '[ruby-core:29948]'
str = "\u2600"
id = :"\u2604"
msg = "undefined method `#{id}' for \"#{str}\":String"
assert_raise_with_message(NoMethodError, msg, bug3237) do
str.__send__(id)
end
end
def test_errno
assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding)
end
def test_too_many_args_in_eval
bug5720 = '[ruby-core:41520]'
arg_string = (0...140000).to_a.join(", ")
assert_raise(SystemStackError, bug5720) {eval "raise(#{arg_string})"}
end
def test_systemexit_new
e0 = SystemExit.new
assert_equal(0, e0.status)
assert_equal("SystemExit", e0.message)
ei = SystemExit.new(3)
assert_equal(3, ei.status)
assert_equal("SystemExit", ei.message)
es = SystemExit.new("msg")
assert_equal(0, es.status)
assert_equal("msg", es.message)
eis = SystemExit.new(7, "msg")
assert_equal(7, eis.status)
assert_equal("msg", eis.message)
bug5728 = '[ruby-dev:44951]'
et = SystemExit.new(true)
assert_equal(true, et.success?, bug5728)
assert_equal("SystemExit", et.message, bug5728)
ef = SystemExit.new(false)
assert_equal(false, ef.success?, bug5728)
assert_equal("SystemExit", ef.message, bug5728)
ets = SystemExit.new(true, "msg")
assert_equal(true, ets.success?, bug5728)
assert_equal("msg", ets.message, bug5728)
efs = SystemExit.new(false, "msg")
assert_equal(false, efs.success?, bug5728)
assert_equal("msg", efs.message, bug5728)
end
def test_exception_in_name_error_to_str
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5575 = '[ruby-core:41612]'
begin;
begin
BasicObject.new.inspect
rescue
assert_nothing_raised(NameError, bug5575) {$!.inspect}
end
end;
end
def test_equal
bug5865 = '[ruby-core:41979]'
assert_equal(RuntimeError.new("a"), RuntimeError.new("a"), bug5865)
assert_not_equal(RuntimeError.new("a"), StandardError.new("a"), bug5865)
end
def test_exception_in_exception_equal
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5865 = '[ruby-core:41979]'
begin;
o = Object.new
def o.exception(arg)
end
assert_nothing_raised(ArgumentError, bug5865) do
RuntimeError.new("a") == o
end
end;
end
def test_backtrace_by_exception
begin
line = __LINE__; raise "foo"
rescue => e
end
e2 = e.exception("bar")
assert_not_equal(e.message, e2.message)
assert_equal(e.backtrace, e2.backtrace)
loc = e2.backtrace_locations[0]
assert_equal([__FILE__, line], [loc.path, loc.lineno])
end
Bug4438 = '[ruby-core:35364]'
def test_rescue_single_argument
assert_raise(TypeError, Bug4438) do
begin
raise
rescue 1
end
end
end
def test_rescue_splat_argument
assert_raise(TypeError, Bug4438) do
begin
raise
rescue *Array(1)
end
end
end
def test_to_s_taintness_propagation
for exc in [Exception, NameError]
m = "abcdefg"
e = exc.new(m)
e.taint
s = e.to_s
assert_equal(false, m.tainted?,
"#{exc}#to_s should not propagate taintness")
assert_equal(false, s.tainted?,
"#{exc}#to_s should not propagate taintness")
end
o = Object.new
def o.to_str
"foo"
end
o.taint
e = NameError.new(o)
s = e.to_s
assert_equal(false, s.tainted?)
end
def m
m(&->{return 0})
42
end
def test_stackoverflow
feature6216 = '[ruby-core:43794] [Feature #6216]'
e = assert_raise(SystemStackError, feature6216) {m}
level = e.backtrace.size
assert_operator(level, :>, 10, feature6216)
feature6216 = '[ruby-core:63377] [Feature #6216]'
e = assert_raise(SystemStackError, feature6216) {raise e}
assert_equal(level, e.backtrace.size, feature6216)
end
def test_machine_stackoverflow
bug9109 = '[ruby-dev:47804] [Bug #9109]'
assert_separately(%w[--disable-gem], <<-SRC)
assert_raise(SystemStackError, #{bug9109.dump}) {
h = {a: ->{h[:a].call}}
h[:a].call
}
SRC
rescue SystemStackError
end
def test_machine_stackoverflow_by_define_method
bug9454 = '[ruby-core:60113] [Bug #9454]'
assert_separately(%w[--disable-gem], <<-SRC)
assert_raise(SystemStackError, #{bug9454.dump}) {
define_method(:foo) {self.foo}
self.foo
}
SRC
rescue SystemStackError
end
def test_machine_stackoverflow_by_trace
assert_normal_exit("#{<<-"begin;"}\n#{<<~"end;"}", timeout: 60)
begin;
require 'timeout'
require 'tracer'
class HogeError < StandardError
def to_s
message.upcase # disable tailcall optimization
end
end
Tracer.stdout = open(IO::NULL, "w")
begin
Timeout.timeout(5) do
Tracer.on
HogeError.new.to_s
end
rescue Timeout::Error
# ok. there are no SEGV or critical error
rescue SystemStackError => e
# ok.
end
end;
end
def test_cause
msg = "[Feature #8257]"
cause = nil
e = assert_raise(StandardError) {
begin
raise msg
rescue => e
cause = e.cause
raise StandardError
end
}
assert_nil(cause, msg)
cause = e.cause
assert_instance_of(RuntimeError, cause, msg)
assert_equal(msg, cause.message, msg)
end
def test_cause_reraised
msg = "[Feature #8257]"
e = assert_raise(RuntimeError) {
begin
raise msg
rescue => e
raise e
end
}
assert_not_same(e, e.cause, "#{msg}: should not be recursive")
end
def test_cause_raised_in_rescue
a = nil
e = assert_raise_with_message(RuntimeError, 'b') {
begin
raise 'a'
rescue => a
begin
raise 'b'
rescue => b
assert_same(a, b.cause)
begin
raise 'c'
rescue
raise b
end
end
end
}
assert_same(a, e.cause, 'cause should not be overwritten by reraise')
end
def test_cause_at_raised
a = nil
e = assert_raise_with_message(RuntimeError, 'b') {
begin
raise 'a'
rescue => a
b = RuntimeError.new('b')
assert_nil(b.cause)
begin
raise 'c'
rescue
raise b
end
end
}
assert_equal('c', e.cause.message, 'cause should be the exception at raised')
assert_same(a, e.cause.cause)
end
def test_cause_at_end
errs = [
/-: unexpected return\n/,
/.*undefined local variable or method `n'.*\n/,
]
assert_in_out_err([], <<-'end;', [], errs)
END{n}; END{return}
end;
end
def test_raise_with_cause
msg = "[Feature #8257]"
cause = ArgumentError.new("foobar")
e = assert_raise(RuntimeError) {raise msg, cause: cause}
assert_same(cause, e.cause)
end
def test_cause_with_no_arguments
cause = ArgumentError.new("foobar")
assert_raise_with_message(ArgumentError, /with no arguments/) do
raise cause: cause
end
end
def test_raise_with_cause_in_rescue
e = assert_raise_with_message(RuntimeError, 'b') {
begin
raise 'a'
rescue => a
begin
raise 'b'
rescue => b
assert_same(a, b.cause)
begin
raise 'c'
rescue
raise b, cause: ArgumentError.new('d')
end
end
end
}
assert_equal('d', e.cause.message, 'cause option should be honored always')
assert_nil(e.cause.cause)
end
def test_cause_thread_no_cause
bug12741 = '[ruby-core:77222] [Bug #12741]'
x = Thread.current
a = false
y = Thread.start do
Thread.pass until a
x.raise "stop"
end
begin
raise bug12741
rescue
e = assert_raise_with_message(RuntimeError, "stop") do
a = true
sleep 1
end
end
assert_nil(e.cause)
ensure
y.join
end
def test_cause_thread_with_cause
bug12741 = '[ruby-core:77222] [Bug #12741]'
x = Thread.current
q = Queue.new
y = Thread.start do
q.pop
begin
raise "caller's cause"
rescue
x.raise "stop"
end
end
begin
raise bug12741
rescue
e = assert_raise_with_message(RuntimeError, "stop") do
q.push(true)
sleep 1
end
ensure
y.join
end
assert_equal("caller's cause", e.cause.message)
end
def test_unknown_option
bug = '[ruby-core:63203] [Feature #8257] should pass unknown options'
exc = Class.new(RuntimeError) do
attr_reader :arg
def initialize(msg = nil)
@arg = msg
super(msg)
end
end
e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar"}
assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug)
e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar", cause: RuntimeError.new("zzz")}
assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug)
e = assert_raise(exc, bug) {raise exc, {}}
assert_equal({}, e.arg, bug)
end
def test_circular_cause
bug13043 = '[ruby-core:78688] [Bug #13043]'
begin
begin
raise "error 1"
ensure
orig_error = $!
begin
raise "error 2"
rescue => err
raise orig_error
end
end
rescue => x
end
assert_equal(orig_error, x)
assert_equal(orig_error, err.cause)
assert_nil(orig_error.cause, bug13043)
end
def test_cause_with_frozen_exception
exc = ArgumentError.new("foo").freeze
assert_raise_with_message(ArgumentError, exc.message) {
raise exc, cause: RuntimeError.new("bar")
}
end
def test_anonymous_message
assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/)
end
PrettyObject =
Class.new(BasicObject) do
alias object_id __id__
def pretty_inspect; "`obj'"; end
alias inspect pretty_inspect
end
def test_frozen_error_receiver
obj = Object.new.freeze
(obj.foo = 1) rescue (e = $!)
assert_same(obj, e.receiver)
obj.singleton_class.const_set(:A, 2) rescue (e = $!)
assert_same(obj.singleton_class, e.receiver)
end
def test_frozen_error_initialize
obj = Object.new
exc = FrozenError.new("bar", obj)
assert_equal("bar", exc.message)
assert_same(obj, exc.receiver)
exc = FrozenError.new("bar")
assert_equal("bar", exc.message)
assert_raise_with_message(ArgumentError, "no receiver is available") {
exc.receiver
}
exc = FrozenError.new
assert_equal("FrozenError", exc.message)
assert_raise_with_message(ArgumentError, "no receiver is available") {
exc.receiver
}
end
def test_frozen_error_message
obj = Object.new.freeze
e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) {
obj.instance_variable_set(:@test, true)
}
assert_include(e.message, obj.inspect)
klass = Class.new do
def init
@x = true
end
def inspect
init
super
end
end
obj = klass.new.freeze
e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) {
obj.init
}
assert_include(e.message, klass.inspect)
end
def test_name_error_new_default
error = NameError.new
assert_equal("NameError", error.message)
end
def test_name_error_new_message
error = NameError.new("Message")
assert_equal("Message", error.message)
end
def test_name_error_new_name
error = NameError.new("Message")
assert_nil(error.name)
error = NameError.new("Message", :foo)
assert_equal(:foo, error.name)
end
def test_name_error_new_receiver
receiver = Object.new
error = NameError.new
assert_raise(ArgumentError) {error.receiver}
assert_equal("NameError", error.message)
error = NameError.new(receiver: receiver)
assert_equal(["NameError", receiver],
[error.message, error.receiver])
error = NameError.new("Message", :foo, receiver: receiver)
assert_equal(["Message", receiver, :foo],
[error.message, error.receiver, error.name])
end
def test_nomethod_error_new_default
error = NoMethodError.new
assert_equal("NoMethodError", error.message)
end
def test_nomethod_error_new_message
error = NoMethodError.new("Message")
assert_equal("Message", error.message)
end
def test_nomethod_error_new_name
error = NoMethodError.new("Message")
assert_nil(error.name)
error = NoMethodError.new("Message", :foo)
assert_equal(:foo, error.name)
end
def test_nomethod_error_new_name_args
error = NoMethodError.new("Message", :foo)
assert_nil(error.args)
error = NoMethodError.new("Message", :foo, [1, 2])
assert_equal([:foo, [1, 2]], [error.name, error.args])
end
def test_nomethod_error_new_name_args_priv
error = NoMethodError.new("Message", :foo, [1, 2])
assert_not_predicate(error, :private_call?)
error = NoMethodError.new("Message", :foo, [1, 2], true)
assert_equal([:foo, [1, 2], true],
[error.name, error.args, error.private_call?])
end
def test_nomethod_error_new_receiver
receiver = Object.new
error = NoMethodError.new
assert_raise(ArgumentError) {error.receiver}
error = NoMethodError.new(receiver: receiver)
assert_equal(receiver, error.receiver)
error = NoMethodError.new("Message")
assert_raise(ArgumentError) {error.receiver}
error = NoMethodError.new("Message", receiver: receiver)
assert_equal(["Message", receiver],
[error.message, error.receiver])
error = NoMethodError.new("Message", :foo)
assert_raise(ArgumentError) {error.receiver}
error = NoMethodError.new("Message", :foo, receiver: receiver)
assert_equal(["Message", :foo, receiver],
[error.message, error.name, error.receiver])
error = NoMethodError.new("Message", :foo, [1, 2])
assert_raise(ArgumentError) {error.receiver}
error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver)
assert_equal(["Message", :foo, [1, 2], receiver],
[error.message, error.name, error.args, error.receiver])
error = NoMethodError.new("Message", :foo, [1, 2], true)
assert_raise(ArgumentError) {error.receiver}
error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver)
assert_equal([:foo, [1, 2], true, receiver],
[error.name, error.args, error.private_call?, error.receiver])
end
def test_name_error_info_const
obj = PrettyObject.new
e = assert_raise(NameError) {
obj.instance_eval("Object")
}
assert_equal(:Object, e.name)
e = assert_raise(NameError) {
BasicObject::X
}
assert_same(BasicObject, e.receiver)
assert_equal(:X, e.name)
end
def test_name_error_info_method
obj = PrettyObject.new
e = assert_raise(NameError) {
obj.instance_eval {foo}
}
assert_equal(:foo, e.name)
assert_same(obj, e.receiver)
e = assert_raise(NoMethodError) {
obj.foo(1, 2)
}
assert_equal(:foo, e.name)
assert_equal([1, 2], e.args)
assert_same(obj, e.receiver)
assert_not_predicate(e, :private_call?)
e = assert_raise(NoMethodError) {
obj.instance_eval {foo(1, 2)}
}
assert_equal(:foo, e.name)
assert_equal([1, 2], e.args)
assert_same(obj, e.receiver)
assert_predicate(e, :private_call?)
end
def test_name_error_info_local_variables
obj = PrettyObject.new
def obj.test(a, b=nil, *c, &d)
e = a
1.times {|f| g = foo; g}
e
end
e = assert_raise(NameError) {
obj.test(3)
}
assert_equal(:foo, e.name)
assert_same(obj, e.receiver)
assert_equal(%i[a b c d e f g], e.local_variables.sort)
end
def test_name_error_info_method_missing
obj = PrettyObject.new
def obj.method_missing(*)
super
end
e = assert_raise(NoMethodError) {
obj.foo(1, 2)
}
assert_equal(:foo, e.name)
assert_equal([1, 2], e.args)
assert_same(obj, e.receiver)
assert_not_predicate(e, :private_call?)
e = assert_raise(NoMethodError) {
obj.instance_eval {foo(1, 2)}
}
assert_equal(:foo, e.name)
assert_equal([1, 2], e.args)
assert_same(obj, e.receiver)
assert_predicate(e, :private_call?)
end
def test_name_error_info_parent_iseq_mark
assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;')
-> {require ARGV[0]}.call
end;
end
def test_output_string_encoding
# "\x82\xa0" in cp932 is "\u3042" (Japanese hiragana 'a')
# change $stderr to force calling rb_io_write() instead of fwrite()
assert_in_out_err(["-Eutf-8:cp932"], '# coding: cp932
$stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_equal 1, outs.size
assert_equal 0, errs.size
err = outs.first.force_encoding('utf-8')
assert err.valid_encoding?, 'must be valid encoding'
assert_match %r/\u3042/, err
end
end
def test_multibyte_and_newline
bug10727 = '[ruby-core:67473] [Bug #10727]'
assert_in_out_err([], <<-'end;', [], /\u{306b 307b 3093 3054} \(E\)\n\u{6539 884c}/, bug10727, encoding: "UTF-8")
class E < StandardError
def initialize
super("\u{306b 307b 3093 3054}\n\u{6539 884c}")
end
end
raise E
end;
end
def assert_null_char(src, *args, **opts)
begin
eval(src)
rescue => e
end
assert_not_nil(e)
assert_include(e.message, "\0")
assert_in_out_err([], src, [], [], *args, **opts) do |_, err,|
err.each do |e|
assert_not_include(e, "\0")
end
end
e
end
def test_control_in_message
bug7574 = '[ruby-dev:46749]'
assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574)
begin;
Object.const_defined?("String\0")
end;
assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574)
begin;
Object.const_get("String\0")
end;
end
def test_encoding_in_message
name = "\u{e9}t\u{e9}"
e = EnvUtil.with_default_external("US-ASCII") do
assert_raise(NameError) do
Object.const_get(name)
end
end
assert_include(e.message, name)
end
def test_method_missing_reason_clear
bug10969 = '[ruby-core:68515] [Bug #10969]'
a = Class.new {def method_missing(*) super end}.new
assert_raise(NameError) {a.instance_eval("foo")}
assert_raise(NoMethodError, bug10969) {a.public_send("bar", true)}
end
def test_message_of_name_error
assert_raise_with_message(NameError, /\Aundefined method `foo' for module `#<Module:.*>'$/) do
Module.new do
module_function :foo
end
end
end
def capture_warning_warn
verbose = $VERBOSE
warning = []
::Warning.class_eval do
alias_method :warn2, :warn
remove_method :warn
define_method(:warn) do |str|
warning << str
end
end
$VERBOSE = true
yield
return warning
ensure
$VERBOSE = verbose
::Warning.class_eval do
remove_method :warn
alias_method :warn, :warn2
remove_method :warn2
end
end
def test_warning_warn
warning = capture_warning_warn {@a}
assert_match(/instance variable @a not initialized/, warning[0])
assert_equal(["a\nz\n"], capture_warning_warn {warn "a\n", "z"})
assert_equal([], capture_warning_warn {warn})
assert_equal(["\n"], capture_warning_warn {warn ""})
end
def test_kernel_warn_uplevel
warning = capture_warning_warn {warn("test warning", uplevel: 0)}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
def (obj = Object.new).w(n) warn("test warning", uplevel: n) end
warning = capture_warning_warn {obj.w(0)}
assert_equal("#{__FILE__}:#{__LINE__-2}: warning: test warning\n", warning[0])
warning = capture_warning_warn {obj.w(1)}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
assert_raise(ArgumentError) {warn("test warning", uplevel: -1)}
assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/)
warning = capture_warning_warn {warn("test warning", {uplevel: 0})}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: The last argument is used as the keyword parameter\n", warning[0])
assert_match(/warning: for method defined here|warning: test warning/, warning[1])
warning = capture_warning_warn {warn("test warning", **{uplevel: 0})}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])
warning = capture_warning_warn {warn("test warning", {uplevel: 0}, **{})}
assert_equal("test warning\n{:uplevel=>0}\n", warning[0])
assert_raise(ArgumentError) {warn("test warning", foo: 1)}
end
def test_warning_warn_invalid_argument
assert_raise(TypeError) do
::Warning.warn nil
end
assert_raise(TypeError) do
::Warning.warn 1
end
assert_raise(Encoding::CompatibilityError) do
::Warning.warn "\x00a\x00b\x00c".force_encoding("utf-16be")
end
end
def test_warning_warn_circular_require_backtrace
warning = nil
path = nil
Tempfile.create(%w[circular .rb]) do |t|
path = File.realpath(t.path)
basename = File.basename(path)
t.puts "require '#{basename}'"
t.close
$LOAD_PATH.push(File.dirname(t))
warning = capture_warning_warn {require basename}
ensure
$LOAD_PATH.pop
$LOADED_FEATURES.delete(t)
end
assert_equal(1, warning.size)
assert_match(/circular require/, warning.first)
assert_match(/^\tfrom #{Regexp.escape(path)}:1:/, warning.first)
end
def test_warning_warn_super
assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /instance variable @a not initialized/)
{#
module Warning
def warn(message)
super
end
end
$VERBOSE = true
@a
};
end
def test_undefined_backtrace
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
class Exception
undef backtrace
end
assert_raise(RuntimeError) {
raise RuntimeError, "hello"
}
end;
end
def test_redefined_backtrace
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
$exc = nil
class Exception
undef backtrace
def backtrace
$exc = self
end
end
e = assert_raise(RuntimeError) {
raise RuntimeError, "hello"
}
assert_same(e, $exc)
end;
end
def test_blocking_backtrace
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class Bug < RuntimeError
def backtrace
IO.readlines(IO::NULL)
end
end
bug = Bug.new '[ruby-core:85939] [Bug #14577]'
n = 10000
i = 0
n.times do
begin
raise bug
rescue Bug
i += 1
end
end
assert_equal(n, i)
end;
end
def test_wrong_backtrace
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
class Exception
undef backtrace
def backtrace(a)
end
end
assert_raise(RuntimeError) {
raise RuntimeError, "hello"
}
end;
error_class = Class.new(StandardError) do
def backtrace; :backtrace; end
end
begin
raise error_class
rescue error_class => e
assert_raise(TypeError) {$@}
assert_raise(TypeError) {e.full_message}
end
end
def test_backtrace_in_eval
bug = '[ruby-core:84434] [Bug #14229]'
assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval\):1:/, bug)
end
def test_full_message
message = RuntimeError.new("testerror").full_message
assert_operator(message, :end_with?, "\n")
test_method = "def foo; raise 'testerror'; end"
out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true)
assert_predicate(status1, :success?)
assert_empty(err1, "expected nothing wrote to $stdout by #full_message")
_, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true)
assert_equal(err2, out1)
e = RuntimeError.new("a\n")
message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do
e.full_message
end
assert_operator(message, :end_with?, "\n")
message = message.gsub(/\e\[[\d;]*m/, '')
assert_not_operator(message, :end_with?, "\n\n")
e = RuntimeError.new("a\n\nb\n\nc")
message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do
e.full_message
end
assert_all?(message.lines) do |m|
/\e\[\d[;\d]*m[^\e]*\n/ !~ m
end
e = RuntimeError.new("testerror")
message = e.full_message(highlight: false)
assert_not_match(/\e/, message)
bt = ["test:100", "test:99", "test:98", "test:1"]
e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt}
bottom = "test:100: testerror (RuntimeError)\n"
top = "test:1\n"
remark = "Traceback (most recent call last):"
message = e.full_message(highlight: false, order: :top)
assert_not_match(/\e/, message)
assert_operator(message.count("\n"), :>, 2)
assert_operator(message, :start_with?, bottom)
assert_operator(message, :end_with?, top)
message = e.full_message(highlight: false, order: :bottom)
assert_not_match(/\e/, message)
assert_operator(message.count("\n"), :>, 2)
assert_operator(message, :start_with?, remark)
assert_operator(message, :end_with?, bottom)
assert_raise_with_message(ArgumentError, /:top or :bottom/) {
e.full_message(highlight: false, order: :middle)
}
message = e.full_message(highlight: true)
assert_match(/\e/, message)
assert_not_match(/(\e\[1)m\1/, message)
e2 = assert_raise(RuntimeError) {raise RuntimeError, "", bt}
assert_not_match(/(\e\[1)m\1/, e2.full_message(highlight: true))
message = e.full_message
if Exception.to_tty?
assert_match(/\e/, message)
message = message.gsub(/\e\[[\d;]*m/, '')
assert_operator(message, :start_with?, remark)
assert_operator(message, :end_with?, bottom)
else
assert_not_match(/\e/, message)
assert_operator(message, :start_with?, bottom)
assert_operator(message, :end_with?, top)
end
end
def test_exception_in_message
code = "#{<<~"begin;"}\n#{<<~'end;'}"
begin;
class Bug14566 < StandardError
def message; raise self.class; end
end
raise Bug14566
end;
assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 2)
end
def test_non_exception_cause
assert_raise_with_message(TypeError, /exception/) do
raise "foo", cause: 1
end;
end
def test_circular_cause_handle
assert_raise_with_message(ArgumentError, /circular cause/) do
begin
raise "error 1"
rescue => e1
raise "error 2" rescue raise e1, cause: $!
end
end;
end
def test_super_in_method_missing
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
class Object
def method_missing(name, *args, &block)
super
end
end
bug14670 = '[ruby-dev:50522] [Bug #14670]'
assert_raise_with_message(NoMethodError, /`foo'/, bug14670) do
Object.new.foo
end
end;
end
end