# frozen_string_literal: false require 'test/unit' require 'timeout' class TestTimeout < Test::Unit::TestCase def test_work_is_done_in_same_thread_as_caller assert_equal Thread.current, Timeout.timeout(10){ Thread.current } end def test_work_is_done_in_same_fiber_as_caller require 'fiber' # needed for ruby 3.0 and lower assert_equal Fiber.current, Timeout.timeout(10){ Fiber.current } end def test_non_timing_out_code_is_successful assert_nothing_raised do assert_equal :ok, Timeout.timeout(1){ :ok } end end def test_allows_zero_seconds assert_nothing_raised do assert_equal :ok, Timeout.timeout(0){:ok} end end def test_allows_nil_seconds assert_nothing_raised do assert_equal :ok, Timeout.timeout(nil){:ok} end end def test_included c = Class.new do include Timeout def test timeout(1) { :ok } end end assert_nothing_raised do assert_equal :ok, c.new.test end end def test_yield_param assert_equal [5, :ok], Timeout.timeout(5){|s| [s, :ok] } end def test_queue q = Thread::Queue.new assert_raise(Timeout::Error, "[ruby-dev:32935]") { Timeout.timeout(0.01) { q.pop } } end def test_timeout assert_raise(Timeout::Error) do Timeout.timeout(0.1) { nil while true } end end def test_nested_timeout a = nil assert_raise(Timeout::Error) do Timeout.timeout(0.1) { Timeout.timeout(1) { nil while true } a = 1 } end assert_nil a end class MyNewErrorOuter < StandardError; end class MyNewErrorInner < StandardError; end # DOES NOT fail with # - raise new(message) if exc.equal?(e) # + raise new(message) if exc.class == e.class def test_nested_timeout_error_identity begin Timeout.timeout(0.1, MyNewErrorOuter) { Timeout.timeout(1, MyNewErrorInner) { nil while true } } rescue => e assert e.class == MyNewErrorOuter end end # DOES fail with # - raise new(message) if exc.equal?(e) # + raise new(message) if exc.class == e.class def test_nested_timeout_which_error_bubbles_up raised_exception = nil begin Timeout.timeout(0.1) { Timeout.timeout(1) { raise Timeout::ExitException.new("inner message") } } rescue Exception => e raised_exception = e end assert_equal 'inner message', raised_exception.message end def test_cannot_convert_into_time_interval bug3168 = '[ruby-dev:41010]' def (n = Object.new).zero?; false; end assert_raise(TypeError, bug3168) {Timeout.timeout(n) { sleep 0.1 }} end def test_skip_rescue_standarderror e = nil assert_raise_with_message(Timeout::Error, /execution expired/) do Timeout.timeout 0.01 do begin sleep 3 rescue => e flunk "should not see any exception but saw #{e.inspect}" end end end end def test_raises_exception_internally e = nil assert_raise_with_message(Timeout::Error, /execution expired/) do Timeout.timeout 0.01 do begin sleep 3 rescue Exception => exc e = exc raise end end end assert_equal Timeout::ExitException, e.class end def test_rescue_exit exc = Class.new(RuntimeError) e = nil assert_nothing_raised(exc) do Timeout.timeout 0.01, exc do begin sleep 3 rescue exc => e end end end assert_raise_with_message(exc, 'execution expired') {raise e if e} end def test_custom_exception bug9354 = '[ruby-core:59511] [Bug #9354]' err = Class.new(StandardError) do def initialize(msg) super end end assert_nothing_raised(ArgumentError, bug9354) do assert_equal(:ok, Timeout.timeout(100, err) {:ok}) end assert_raise_with_message(err, 'execution expired') do Timeout.timeout 0.01, err do sleep 3 end end assert_raise_with_message(err, /connection to ruby-lang.org expired/) do Timeout.timeout 0.01, err, "connection to ruby-lang.org expired" do sleep 3 end end end def test_exit_exception assert_raise_with_message(Timeout::Error, "boon") do Timeout.timeout(10, Timeout::Error) do raise Timeout::Error, "boon" end end end def test_raise_with_message bug17812 = '[ruby-core:103502] [Bug #17812]: Timeout::Error doesn\'t let two-argument raise() set a new message' exc = Timeout::Error.new('foo') assert_raise_with_message(Timeout::Error, 'bar', bug17812) do raise exc, 'bar' end end def test_enumerator_next bug9380 = '[ruby-dev:47872] [Bug #9380]: timeout in Enumerator#next' e = (o=Object.new).to_enum def o.each sleep end assert_raise_with_message(Timeout::Error, 'execution expired', bug9380) do Timeout.timeout(0.01) {e.next} end end def test_handle_interrupt bug11344 = '[ruby-dev:49179] [Bug #11344]' ok = false assert_raise(Timeout::Error) { Thread.handle_interrupt(Timeout::ExitException => :never) { Timeout.timeout(0.01) { sleep 0.2 ok = true Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { sleep 0.2 } } } } assert(ok, bug11344) end def test_fork omit 'fork not supported' unless Process.respond_to?(:fork) r, w = IO.pipe pid = fork do r.close begin r = Timeout.timeout(0.01) { sleep 5 } w.write r.inspect rescue Timeout::Error w.write 'timeout' ensure w.close end end w.close Process.wait pid assert_equal 'timeout', r.read r.close end def test_threadgroup assert_separately(%w[-rtimeout], <<-'end;') tg = ThreadGroup.new thr = Thread.new do tg.add(Thread.current) Timeout.timeout(10){} end thr.join assert_equal [].to_s, tg.list.to_s end; end # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup assert_separately(%w[-rtimeout], <<-'end;') Thread.new { t = Thread.current group = ThreadGroup.new group.add(t) group.enclose assert_equal 42, Timeout.timeout(1) { 42 } }.join end; end end