Feature #5856 ยป trunk-raise-any.diff
| eval.c | ||
|---|---|---|
|
break;
|
||
|
}
|
||
|
if (argc > 0) {
|
||
|
if (!rb_obj_is_kind_of(mesg, rb_eException))
|
||
|
rb_raise(rb_eTypeError, "exception object expected");
|
||
|
if (argc > 2)
|
||
|
set_backtrace(mesg, argv[2]);
|
||
|
}
|
||
| test/ruby/test_raise_any.rb | ||
|---|---|---|
|
=begin
|
||
|
Feature: Raise any object
|
||
|
= Proposal
|
||
|
The ability to raise any object that conforms to the protocol of Exception.
|
||
|
= Problem
|
||
|
* The Exception subclass hierarchy is well-established.
|
||
|
* CRuby does not allow any object that behaves as an Exception to be raised, it must be a subclass of Exception.
|
||
|
* 3rd-party code often rescues Exception; e.g. for error recovery, retry and/or logging.
|
||
|
* Users need the ability to raise objects that would not normally be rescued by *any* code;
|
||
|
e.g.: hard timeouts or custom signal handlers in an application.
|
||
|
= Solution
|
||
|
* ruby/eval.c: Remove make_exception() assertion rb_obj_is_kind_of(mesg, rb_mRaiseable).
|
||
|
Sample Usage:
|
||
|
=end
|
||
|
require 'test/unit'
|
||
|
class TestRaiseable < Test::Unit::TestCase
|
||
|
# Example: mixin implementation of Exception protocol methods:
|
||
|
module Raiseable
|
||
|
def exception *args # error.c: exc_exception()
|
||
|
case args.size
|
||
|
when 0
|
||
|
return self
|
||
|
when 1
|
||
|
return self if self.object_id == args[0].object_id
|
||
|
end
|
||
|
clone.initialize *args
|
||
|
end
|
||
|
def initialize mesg = nil # error.c: exc_initialize()
|
||
|
@mesg = mesg
|
||
|
@bt = nil
|
||
|
self
|
||
|
end
|
||
|
def == other # error.c: exc_equal()
|
||
|
return true if self.object_id == other.object_id
|
||
|
if self.class == other.class
|
||
|
mesg = other.message
|
||
|
backtrace = other.backtrace
|
||
|
else
|
||
|
mesg = other.instance_variable_get(:'@mesg')
|
||
|
backtrace = other.backtrace
|
||
|
end
|
||
|
@mesg == mesg && self.backtrace == backtrace
|
||
|
end
|
||
|
def to_s # error.c: exc_to_s()
|
||
|
@mesg || self.class.name
|
||
|
end
|
||
|
def message; to_s; end
|
||
|
def inspect # error.c: exc_inspect()
|
||
|
"#<#{self.class}: #{self}>"
|
||
|
end
|
||
|
def backtrace; @bt; end
|
||
|
def set_backtrace v # error.c: exc_set_backtrace()
|
||
|
v = [ v ] if String === v
|
||
|
raise TypeError, "backtrace must be Array of String" unless Array === v || v.all?{|e| String === e}
|
||
|
@bt = v
|
||
|
end
|
||
|
def self.included target
|
||
|
super
|
||
|
target.extend ModuleMethods
|
||
|
end
|
||
|
module ModuleMethods
|
||
|
def exception *args; new *args; end
|
||
|
end
|
||
|
end
|
||
|
def test_raiseable
|
||
|
raiseable = Class.new do
|
||
|
include Raiseable
|
||
|
end
|
||
|
begin
|
||
|
raise raiseable, "this must be handled"
|
||
|
assert(false)
|
||
|
rescue Exception
|
||
|
$stderr.puts "#{__FILE__}:#{__LINE__}: Unexpected Exception: #{$!.inspect}"
|
||
|
assert(false)
|
||
|
rescue Raiseable
|
||
|
assert(true)
|
||
|
end
|
||
|
end
|
||
|
class SomeLibraryFromSomebodyElse
|
||
|
def do_it
|
||
|
something_that_takes_too_long
|
||
|
rescue ::Exception => exc
|
||
|
$stderr.puts "\n#{__FILE__}:#{__LINE__}: Something failed: #{exc.inspect}"
|
||
|
42
|
||
|
end
|
||
|
def something_that_takes_too_long
|
||
|
sleep 10
|
||
|
end
|
||
|
end
|
||
|
class MyTimeoutError
|
||
|
include Raiseable
|
||
|
end
|
||
|
def test_MyTimeoutError
|
||
|
raise MyTimeoutError
|
||
|
assert(false)
|
||
|
rescue ::Exception
|
||
|
$stderr.puts "#{__FILE__}:#{__LINE__}: Unexpected Exception: #{$!.inspect}"
|
||
|
assert(false)
|
||
|
rescue MyTimeoutError
|
||
|
assert(true)
|
||
|
end
|
||
|
def test_raiseable_timeout
|
||
|
require 'timeout'
|
||
|
begin
|
||
|
Timeout.timeout(1, MyTimeoutError) do
|
||
|
SomeLibraryFromSomebodyElse.new.do_it
|
||
|
end
|
||
|
assert(false)
|
||
|
rescue MyTimeoutError
|
||
|
assert(true)
|
||
|
rescue Exception
|
||
|
$stderr.puts "#{__FILE__}:#{__LINE__}: Unexpected Exception: #{$!.inspect}"
|
||
|
assert(false)
|
||
|
end
|
||
|
end
|
||
|
end
|
||