# 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 assert(false) end 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_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 _, stderr, _ = EnvUtil.invoke_ruby("--disable-gems -d", <<-RUBY, false, true) Thread.start do 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.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" EnvUtil.with_default_external(Encoding::UTF_8) do msg = "undefined method `#{id}' for #{str.inspect}:String" assert_raise_with_message(NoMethodError, msg, bug3237) do str.__send__(id) end 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_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_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_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: "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_anonymous_message assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/) end def test_name_error_info obj = BasicObject.new class << obj alias object_id __id__ def pretty_inspect; "`obj'"; end end e = assert_raise(NameError) { obj.instance_eval("Object") } assert_equal(:Object, e.name) e = assert_raise(NameError) { BasicObject::X } assert_same(BasicObject, e.receiver) 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) def obj.test(a, b=nil, *c, &d) e = a 1.times {|f| g = foo} 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_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 /\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 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 `#'$/) do Module.new do module_function :foo end end end end