mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
50784a0a44
* eval_error.c (print_errinfo): defer escaping control char in error messages until writing to stderr, instead of quoting at building the message. [ruby-core:90853] [Bug #15497] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66753 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
1417 lines
33 KiB
Ruby
1417 lines
33 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
|
|
bug5575 = '[ruby-core:41612]'
|
|
Tempfile.create(["test_exception_in_name_error_to_str", ".rb"]) do |t|
|
|
t.puts <<-EOC
|
|
begin
|
|
BasicObject.new.inspect
|
|
rescue
|
|
$!.inspect
|
|
end
|
|
EOC
|
|
t.close
|
|
assert_nothing_raised(NameError, bug5575) do
|
|
load(t.path)
|
|
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
|
|
bug5865 = '[ruby-core:41979]'
|
|
Tempfile.create(["test_exception_in_exception_equal", ".rb"]) do |t|
|
|
t.puts <<-EOC
|
|
o = Object.new
|
|
def o.exception(arg)
|
|
end
|
|
_ = RuntimeError.new("a") == o
|
|
EOC
|
|
t.close
|
|
assert_nothing_raised(ArgumentError, bug5865) do
|
|
load(t.path)
|
|
end
|
|
end
|
|
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_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:/)
|
|
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)
|
|
|
|
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
|