mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
92c3ad9c27
In general accepting arbitrary keywords is a bad idea unless you are delegating keywords or acting on arbitrary keywords. In this case, the category keyword is ignored, and it's less error prone to not ignore all keywords.
1259 lines
30 KiB
Ruby
1259 lines
30 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_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 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_cause_exception_in_cause_message
|
|
assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}") do |outs, errs, status|
|
|
begin;
|
|
exc = Class.new(StandardError) do
|
|
def initialize(obj, cnt)
|
|
super(obj)
|
|
@errcnt = cnt
|
|
end
|
|
def to_s
|
|
return super if @errcnt <= 0
|
|
@errcnt -= 1
|
|
raise "xxx"
|
|
end
|
|
end.new("ok", 10)
|
|
raise "[Bug #17033]", cause: exc
|
|
end;
|
|
assert_equal(1, errs.count {|m| m.include?("[Bug #17033]")}, proc {errs.pretty_inspect})
|
|
end
|
|
end
|
|
|
|
def test_anonymous_message
|
|
assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/)
|
|
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(category: false)
|
|
verbose = $VERBOSE
|
|
deprecated = Warning[:deprecated]
|
|
warning = []
|
|
|
|
::Warning.class_eval do
|
|
alias_method :warn2, :warn
|
|
remove_method :warn
|
|
|
|
if category
|
|
define_method(:warn) do |str, category: nil|
|
|
warning << [str, category]
|
|
end
|
|
else
|
|
define_method(:warn) do |str|
|
|
warning << str
|
|
end
|
|
end
|
|
end
|
|
|
|
$VERBOSE = true
|
|
Warning[:deprecated] = true
|
|
yield
|
|
|
|
return warning
|
|
ensure
|
|
$VERBOSE = verbose
|
|
Warning[:deprecated] = deprecated
|
|
|
|
::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_warn_deprecated_backwards_compatibility_category
|
|
warning = capture_warning_warn { Dir.exists?("non-existent") }
|
|
|
|
assert_match(/deprecated/, warning[0])
|
|
end
|
|
|
|
def test_warn_deprecated_category
|
|
warning = capture_warning_warn(category: true) { Dir.exists?("non-existent") }
|
|
|
|
assert_equal :deprecated, warning[0][1]
|
|
end
|
|
|
|
def test_warn_deprecated_to_remove_backwards_compatibility_category
|
|
warning = capture_warning_warn { Object.new.tainted? }
|
|
|
|
assert_match(/deprecated/, warning[0])
|
|
end
|
|
|
|
def test_warn_deprecated_to_remove_category
|
|
warning = capture_warning_warn(category: true) { Object.new.tainted? }
|
|
|
|
assert_equal :deprecated, warning[0][1]
|
|
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_match(/test warning.*{:uplevel=>0}/m, warning[0])
|
|
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 {
|
|
assert require(basename)
|
|
}
|
|
ensure
|
|
$LOAD_PATH.pop
|
|
$LOADED_FEATURES.delete(t.path)
|
|
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_warning_category
|
|
assert_raise(TypeError) {Warning[nil]}
|
|
assert_raise(ArgumentError) {Warning[:XXXX]}
|
|
assert_include([true, false], Warning[:deprecated])
|
|
assert_include([true, false], Warning[:experimental])
|
|
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/, '')
|
|
else
|
|
assert_not_match(/\e/, message)
|
|
end
|
|
assert_operator(message, :start_with?, bottom)
|
|
assert_operator(message, :end_with?, top)
|
|
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
|