summaryrefslogtreecommitdiff
path: root/test/ruby/test_exception.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_exception.rb')
-rw-r--r--test/ruby/test_exception.rb1218
1 files changed, 1101 insertions, 117 deletions
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index ea733b3add..31e5aa9f6b 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -1,6 +1,6 @@
+# frozen_string_literal: false
require 'test/unit'
require 'tempfile'
-require_relative 'envutil'
class TestException < Test::Unit::TestCase
def test_exception_rescued
@@ -20,15 +20,15 @@ class TestException < Test::Unit::TestCase
if bad
bad = false
retry
- assert(false)
end
+ assert(!bad)
end
assert(true)
end
def test_exception_in_rescue
string = "this must be handled no.3"
- e = assert_raise(RuntimeError) do
+ assert_raise_with_message(RuntimeError, string) do
begin
raise "exception in rescue clause"
rescue
@@ -36,12 +36,11 @@ class TestException < Test::Unit::TestCase
end
assert(false)
end
- assert_equal(string, e.message)
end
def test_exception_in_ensure
string = "exception in ensure clause"
- e = assert_raise(RuntimeError) do
+ assert_raise_with_message(RuntimeError, string) do
begin
raise "this must be handled no.4"
ensure
@@ -51,7 +50,6 @@ class TestException < Test::Unit::TestCase
end
assert(false)
end
- assert_equal(string, e.message)
end
def test_exception_ensure
@@ -80,6 +78,77 @@ class TestException < Test::Unit::TestCase
assert(!bad)
end
+ def test_exception_in_ensure_with_next
+ string = "[ruby-core:82936] [Bug #13930]"
+ assert_raise_with_message(RuntimeError, string) do
+ lambda do
+ next
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end.call
+ assert(false)
+ end
+
+ assert_raise_with_message(RuntimeError, string) do
+ flag = true
+ while flag
+ flag = false
+ begin
+ next
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end
+ end
+ end
+
+ iseq = RubyVM::InstructionSequence.compile(<<-RUBY)
+ begin
+ while true
+ break
+ end
+ rescue
+ end
+ RUBY
+
+ assert_equal false, iseq.to_a[13].any?{|(e,_)| e == :throw}
+ end
+
+ def test_exception_in_ensure_with_redo
+ string = "[ruby-core:82936] [Bug #13930]"
+
+ assert_raise_with_message(RuntimeError, string) do
+ i = 0
+ lambda do
+ i += 1
+ redo if i < 2
+ rescue
+ assert(false)
+ ensure
+ raise string
+ end.call
+ assert(false)
+ end
+ end
+
+ def test_exception_in_ensure_with_return
+ @string = "[ruby-core:97104] [Bug #16618]"
+ def self.meow
+ return if true # This if modifier suppresses "warning: statement not reached"
+ assert(false)
+ rescue
+ assert(false)
+ ensure
+ raise @string
+ end
+ assert_raise_with_message(RuntimeError, @string) do
+ meow
+ end
+ end
+
def test_errinfo_in_debug
bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do
def to_s
@@ -131,28 +200,87 @@ class TestException < Test::Unit::TestCase
assert(!bad)
end
+ def test_catch_no_throw
+ assert_equal(:foo, catch {:foo})
+ end
+
def test_catch_throw
- assert(catch(:foo) {
- loop do
- loop do
- throw :foo, true
- break
- end
- break
- assert(false) # should no reach here
- end
- false
- })
+ 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]'
- t = Tempfile.open(["dep", ".rb"])
- t.puts("throw :extdep, 42")
- t.close
- assert_equal(42, catch(:extdep) {require t.path}, bug7185)
- ensure
- t.close! if t
+ 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_catch_throw_in_require_cant_be_rescued
+ bug18562 = '[ruby-core:107403]'
+ Tempfile.create(["dep", ".rb"]) {|t|
+ t.puts("throw :extdep, 42")
+ t.close
+
+ rescue_all = Class.new(Exception)
+ def rescue_all.===(_)
+ raise "should not reach here"
+ end
+
+ v = assert_throw(:extdep, bug18562) do
+ require t.path
+ rescue rescue_all
+ assert(false, "should not reach here")
+ end
+
+ assert_equal(42, v, bug18562)
+ }
+ 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
@@ -266,20 +394,21 @@ class TestException < Test::Unit::TestCase
def test_type_error_message_encoding
c = eval("Module.new do break class C\u{4032}; self; end; end")
o = c.new
- e = assert_raise(TypeError) do
+ assert_raise_with_message(TypeError, /C\u{4032}/) do
""[o]
end
- assert_match(/C\u{4032}/, e.message)
c.class_eval {def to_int; self; end}
- e = assert_raise(TypeError) do
+ assert_raise_with_message(TypeError, /C\u{4032}/) do
""[o]
end
- assert_match(/C\u{4032}/, e.message)
c.class_eval {def to_a; self; end}
- assert_raise(TypeError) do
+ assert_raise_with_message(TypeError, /C\u{4032}/) do
[*o]
end
- assert_match(/C\u{4032}/, e.message)
+ 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
@@ -287,7 +416,7 @@ class TestException < Test::Unit::TestCase
assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/)
- assert_in_out_err([], <<-INPUT, [], /backtrace must be Array of String \(TypeError\)$/)
+ assert_in_out_err([], <<-INPUT, [], /backtrace must be an Array of String or an Array of Thread::Backtrace::Location \(TypeError\)$/)
begin
raise
rescue
@@ -314,28 +443,11 @@ class TestException < Test::Unit::TestCase
INPUT
end
- def test_safe4
- cmd = proc{raise SystemExit}
- safe0_p = proc{|*args| args}
-
- test_proc = proc {
- $SAFE = 4
- begin
- cmd.call
- rescue SystemExit => e
- safe0_p["SystemExit: #{e.inspect}"]
- raise e
- rescue Exception => e
- safe0_p["Exception (NOT SystemExit): #{e.inspect}"]
- raise e
- end
- }
- assert_raise(SystemExit, '[ruby-dev:38760]') {test_proc.call}
- end
-
def test_thread_signal_location
- _, stderr, _ = EnvUtil.invoke_ruby("-d", <<-RUBY, false, true)
+ # pend('TODO: a known bug [Bug #14474]')
+ _, 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
@@ -366,6 +478,23 @@ end.join
def to_s; ""; end
end
assert_equal(e.inspect, e.new.inspect)
+
+ # https://bugs.ruby-lang.org/issues/18170#note-13
+ assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect)
+ assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect)
+ assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect)
+ assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').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
@@ -379,6 +508,16 @@ end.join
assert_raise(TypeError) { e.set_backtrace(1) }
assert_raise(TypeError) { e.set_backtrace([1]) }
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(caller_locations(1, 1) + ["foo"])
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(["foo"] + caller_locations(1, 1))
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
end
def test_exit_success_p
@@ -407,18 +546,18 @@ end.join
assert_not_send([e, :success?], "abort means failure")
end
- def test_nomethoderror
- bug3237 = '[ruby-core:29948]'
- str = "\u2600"
- id = :"\u2604"
- e = assert_raise(NoMethodError) {str.__send__(id)}
- assert_equal("undefined method `#{id}' for #{str.inspect}:String", e.message, bug3237)
- end
-
def test_errno
assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding)
end
+ def test_errno_constants
+ assert_equal [:NOERROR], Errno.constants.grep_v(/\AE/)
+ all_assertions_foreach("should be a subclass of SystemCallError", *Errno.constants) do |c|
+ e = Errno.const_get(c)
+ assert_operator e, :<, SystemCallError, proc {e.ancestors.inspect}
+ end
+ end
+
def test_too_many_args_in_eval
bug5720 = '[ruby-core:41520]'
arg_string = (0...140000).to_a.join(", ")
@@ -455,23 +594,44 @@ end.join
end
def test_exception_in_name_error_to_str
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5575 = '[ruby-core:41612]'
- t = nil
- Tempfile.open(["test_exception_in_name_error_to_str", ".rb"]) do |f|
- t = f
- t.puts <<-EOC
+ begin;
begin
BasicObject.new.inspect
rescue
- $!.inspect
+ assert_nothing_raised(NameError, bug5575) {$!.inspect}
end
- EOC
- end
- assert_nothing_raised(NameError, bug5575) do
- load(t.path)
+ end;
+ end
+
+ def test_ensure_after_nomemoryerror
+ omit "Forcing NoMemoryError causes problems in some environments"
+ assert_separately([], "$_ = 'a' * 1_000_000_000_000_000_000")
+ rescue NoMemoryError
+ assert_raise(NoMemoryError) do
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ bug15779 = bug15779 = '[ruby-core:92342]'
+ begin;
+ require 'open-uri'
+
+ begin
+ 'a' * 1_000_000_000_000_000_000
+ ensure
+ URI.open('http://www.ruby-lang.org/')
+ end
+ end;
end
- ensure
- t.close(true) if t
+ rescue Test::Unit::AssertionFailedError
+ # Possibly compiled with -DRUBY_DEBUG, in which
+ # case rb_bug is used instead of NoMemoryError,
+ # and we cannot test ensure after NoMemoryError.
+ rescue RangeError
+ # MingW can raise RangeError instead of NoMemoryError,
+ # so we cannot test this case.
+ rescue Timeout::Error
+ # Solaris 11 CI times out instead of raising NoMemoryError,
+ # so we cannot test this case.
end
def test_equal
@@ -481,22 +641,28 @@ end.join
end
def test_exception_in_exception_equal
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug5865 = '[ruby-core:41979]'
- t = nil
- Tempfile.open(["test_exception_in_exception_equal", ".rb"]) do |f|
- t = f
- t.puts <<-EOC
+ begin;
o = Object.new
def o.exception(arg)
end
- _ = RuntimeError.new("a") == o
- EOC
- end
- assert_nothing_raised(ArgumentError, bug5865) do
- load(t.path)
+ 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
- ensure
- t.close(true) if t
+ 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]'
@@ -519,53 +685,871 @@ end.join
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")
+ 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([], <<-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([], <<-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)
+ assert_raise(TypeError) {raise msg, {cause: 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
- o = Object.new
- def o.to_str
- "foo"
+ begin
+ raise bug12741
+ rescue
+ e = assert_raise_with_message(RuntimeError, "stop") do
+ a = true
+ sleep 1
+ end
end
- o.taint
- e = NameError.new(o)
- s = e.to_s
- assert_equal(false, s.tainted?)
+ assert_nil(e.cause)
+ ensure
+ y.join
end
- def test_exception_to_s_should_not_propagate_untrustedness
- favorite_lang = "Ruby"
+ def test_cause_thread_with_cause
+ bug12741 = '[ruby-core:77222] [Bug #12741]'
+
+ x = Thread.current
+ q = Thread::Queue.new
+ y = Thread.start do
+ q.pop
+ begin
+ raise "caller's cause"
+ rescue
+ x.raise "stop"
+ end
+ end
- for exc in [Exception, NameError]
- assert_raise(SecurityError) do
- lambda {
- $SAFE = 4
- exc.new(favorite_lang).to_s
- favorite_lang.replace("Python")
- }.call
+ 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
- assert_raise(SecurityError) do
- lambda {
- $SAFE = 4
- o = Object.new
- o.singleton_class.send(:define_method, :to_str) {
- favorite_lang
- }
- NameError.new(o).to_s
- favorite_lang.replace("Python")
- }.call
+ 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
- assert_equal("Ruby", favorite_lang)
+ 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_predicate err, :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")
+ # Disabled by [Feature #18367]
+ #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
+ categories = Warning.categories.to_h {|cat| [cat, Warning[cat]]}
+ 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, category: nil|
+ warning << str
+ end
+ end
+ end
+
+ $VERBOSE = true
+ Warning.categories.each {|cat| Warning[cat] = true}
+ yield
+
+ return warning
+ ensure
+ $VERBOSE = verbose
+ categories.each {|cat, flag| Warning[cat] = flag}
+
+ ::Warning.class_eval do
+ remove_method :warn
+ alias_method :warn, :warn2
+ remove_method :warn2
+ end
+ end
+
+ def test_warning_warn
+ warning = capture_warning_warn {$asdfasdsda_test_warning_warn}
+ assert_match(/global variable '\$asdfasdsda_test_warning_warn' 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
+ (message, category), = capture_warning_warn(category: true) do
+ $; = "www"
+ $; = nil
+ end
+
+ assert_include message, 'deprecated'
+ assert_equal :deprecated, category
+ 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#{<<~'};'}", [], /global variable '\$asdfiasdofa_test_warning_warn_super' not initialized/)
+ {#
+ module Warning
+ def warn(message)
+ super
+ end
+ end
+
+ $VERBOSE = true
+ $asdfiasdofa_test_warning_warn_super
+ };
+ end
+
+ def test_warning_category
+ assert_raise(TypeError) {Warning[nil]}
+ assert_raise(ArgumentError) {Warning[:XXXX]}
+
+ all_assertions_foreach("categories", *Warning.categories) do |cat|
+ value = Warning[cat]
+ assert_include([true, false], value)
+
+ enabled = EnvUtil.verbose_warning do
+ Warning[cat] = true
+ Warning.warn "#{cat} feature", category: cat
+ end
+ disabled = EnvUtil.verbose_warning do
+ Warning[cat] = false
+ Warning.warn "#{cat} feature", category: cat
+ end
+ ensure
+ Warning[cat] = value
+ assert_equal "#{cat} feature", enabled
+ assert_empty disabled
+ end
+ end
+
+ def test_undef_Warning_warn
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ Warning.undef_method(:warn)
+ assert_raise(NoMethodError) { warn "" }
+ end;
+ 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
+ File.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 at .*\):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_marshal_circular_cause
+ dump = "\x04\bo:\x11RuntimeError\b:\tmesgI\"\berr\x06:\x06ET:\abt[\x00:\ncause@\x05"
+ assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do
+ e = Marshal.load(dump)
+ assert_same(e, e.cause)
+ 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
+
+ def test_detailed_message
+ e = RuntimeError.new("message")
+ assert_equal("message (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mmessage (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("")
+ assert_equal("unhandled exception", e.detailed_message)
+ assert_equal("\e[1;4munhandled exception\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new
+ assert_equal("RuntimeError (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+ end
+
+ def test_detailed_message_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+ end
+ end
+
+ def test_full_message_with_custom_detailed_message
+ e = RuntimeError.new("message")
+ opt_ = nil
+ e.define_singleton_method(:detailed_message) do |**opt|
+ opt_ = opt
+ "BOO!"
+ end
+ assert_match("BOO!", e.full_message.lines.first)
+ assert_equal({ highlight: Exception.to_tty? }, opt_)
+ end
+
+ def test_full_message_with_encoding
+ message = "\u{dc}bersicht"
+ begin
+ begin
+ raise message
+ rescue => e
+ raise "\n#{e.message}"
+ end
+ rescue => e
+ end
+ assert_include(e.full_message, message)
+ end
+
+ def test_syntax_error_detailed_message
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ Thread.new {}.join
+ "<#{super}>\n""<#{File.basename(__FILE__)}>"
+ rescue ThreadError => e
+ e.message
+ end
+ end
+ end;
+ pattern = /^<detail\.rb>/
+ assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern)
+
+ File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 1 +
+ end;
+ assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,|
+ assert_empty(stdout)
+ assert_not_empty(stderr.grep(pattern))
+ error, = stderr.grep(/unexpected end-of-input/)
+ assert_not_nil(error)
+ assert_match(/<.*unexpected end-of-input.*>|\^ unexpected end-of-input,/, error)
+ end
+ end
+ end
+
+ def test_syntax_error_path
+ e = assert_raise(SyntaxError) {
+ eval("1+", nil, "test_syntax_error_path.rb")
+ }
+ assert_equal("test_syntax_error_path.rb", e.path)
+
+ Dir.mktmpdir do |dir|
+ lib = File.join(dir, "syntax_error-path.rb")
+ File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ STDERR.puts "\n""path=#{path}\n"
+ super
+ end
+ end
+ end;
+ main = File.join(dir, "syntax_error.rb")
+ File.write(main, "1+\n")
+ assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*])
+ end
+ end
+
+ class Ex; end
+
+ def test_exception_message_for_unexpected_implicit_conversion_type
+ a = Ex.new
+ def self.x(a) = nil
+
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Hash") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Proc") do
+ x(&a)
+ end
+
+ def a.to_a = 1
+ def a.to_hash = 1
+ def a.to_proc = 1
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Array (TestException::Ex#to_a gives Integer)") do
+ x(*a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Hash (TestException::Ex#to_hash gives Integer)") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex to Proc (TestException::Ex#to_proc gives Integer)") do
+ x(&a)
+ end
end
end