diff options
Diffstat (limited to 'test/ruby/test_thread.rb')
| -rw-r--r-- | test/ruby/test_thread.rb | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb new file mode 100644 index 0000000000..179af08341 --- /dev/null +++ b/test/ruby/test_thread.rb @@ -0,0 +1,655 @@ +require 'test/unit' +require 'thread' +require_relative 'envutil' + +class TestThread < Test::Unit::TestCase + class Thread < ::Thread + Threads = [] + def self.new(*) + th = super + th.abort_on_exception = true + Threads << th + th + end + end + + def setup + Thread::Threads.clear + end + + def teardown + Thread::Threads.each do |t| + t.kill if t.alive? + begin + t.join + rescue Exception + end + end + end + + def test_mutex_synchronize + m = Mutex.new + r = 0 + max = 10 + (1..max).map{ + Thread.new{ + i=0 + while i<max*max + i+=1 + m.synchronize{ + r += 1 + } + end + } + }.each{|e| + e.join + } + assert_equal(max * max * max, r) + end + + def test_condvar + mutex = Mutex.new + condvar = ConditionVariable.new + result = [] + mutex.synchronize do + t = Thread.new do + mutex.synchronize do + result << 1 + condvar.signal + end + end + + result << 0 + condvar.wait(mutex) + result << 2 + t.join + end + assert_equal([0, 1, 2], result) + end + + def test_condvar_wait_not_owner + mutex = Mutex.new + condvar = ConditionVariable.new + + assert_raise(ThreadError) { condvar.wait(mutex) } + end + + def test_condvar_wait_exception_handling + # Calling wait in the only thread running should raise a ThreadError of + # 'stopping only thread' + mutex = Mutex.new + condvar = ConditionVariable.new + + locked = false + thread = Thread.new do + Thread.current.abort_on_exception = false + mutex.synchronize do + begin + condvar.wait(mutex) + rescue Exception + locked = mutex.locked? + raise + end + end + end + + until thread.stop? + sleep(0.1) + end + + thread.raise Interrupt, "interrupt a dead condition variable" + assert_raise(Interrupt) { thread.value } + assert(locked) + end + + def test_condvar_wait_and_broadcast + nr_threads = 3 + threads = Array.new + mutex = Mutex.new + condvar = ConditionVariable.new + result = [] + + nr_threads.times do |i| + threads[i] = Thread.new do + mutex.synchronize do + result << "C1" + condvar.wait mutex + result << "C2" + end + end + end + sleep 0.1 + mutex.synchronize do + result << "P1" + condvar.broadcast + result << "P2" + end + nr_threads.times do |i| + threads[i].join + end + + assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result + end + +# Hmm.. don't we have a way of catch fatal exception? +# +# def test_cv_wait_deadlock +# mutex = Mutex.new +# cv = ConditionVariable.new +# +# assert_raises(fatal) { +# mutex.lock +# cv.wait mutex +# mutex.unlock +# } +# end + + def test_condvar_wait_deadlock_2 + nr_threads = 3 + threads = Array.new + mutex = Mutex.new + condvar = ConditionVariable.new + + nr_threads.times do |i| + if (i != 0) + mutex.unlock + end + threads[i] = Thread.new do + mutex.synchronize do + condvar.wait mutex + end + end + mutex.lock + end + + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) { condvar.wait mutex } + end + mutex.unlock rescue + threads[i].each.join + end + + def test_condvar_timed_wait + mutex = Mutex.new + condvar = ConditionVariable.new + timeout = 0.3 + locked = false + + t0 = Time.now + mutex.synchronize do + begin + condvar.wait(mutex, timeout) + ensure + locked = mutex.locked? + end + end + t1 = Time.now + t = t1-t0 + + assert_block { timeout*0.9 < t && t < timeout*1.1 } + assert(locked) + end + + def test_condvar_nolock + mutex = Mutex.new + condvar = ConditionVariable.new + + assert_raise(ThreadError) { condvar.wait(mutex) } + end + + def test_condvar_nolock_2 + mutex = Mutex.new + condvar = ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex)} + end.join + end + + def test_condvar_nolock_3 + mutex = Mutex.new + condvar = ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex, 0.1)} + end.join + end + + def test_local_barrier + dir = File.dirname(__FILE__) + lbtest = File.join(dir, "lbtest.rb") + $:.unshift File.join(File.dirname(dir), 'ruby') + require 'envutil' + $:.shift + 3.times { + result = `#{EnvUtil.rubybin} #{lbtest}` + assert(!$?.coredump?, '[ruby-dev:30653]') + assert_equal("exit.", result[/.*\Z/], '[ruby-dev:30653]') + } + end + + def test_priority + c1 = c2 = 0 + t1 = Thread.new { loop { c1 += 1 } } + t1.priority = -1 + t2 = Thread.new { loop { c2 += 1 } } + t2.priority = -3 + assert_equal(-1, t1.priority) + assert_equal(-3, t2.priority) + sleep 0.5 + 5.times do + break if c1 > c2 + sleep 0.1 + end + t1.kill + t2.kill + # assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + end + + def test_new + assert_raise(ThreadError) do + Thread.new + end + + t1 = Thread.new { sleep } + assert_raise(ThreadError) do + t1.instance_eval { initialize { } } + end + + t2 = Thread.new(&method(:sleep).to_proc) + assert_raise(ThreadError) do + t2.instance_eval { initialize { } } + end + + ensure + t1.kill if t1 + t2.kill if t2 + end + + def test_join + t = Thread.new { sleep } + assert_nil(t.join(0.5)) + + ensure + t.kill if t + end + + def test_join2 + t1 = Thread.new { sleep(1.5) } + t2 = Thread.new do + t1.join(1) + end + t3 = Thread.new do + sleep 0.5 + t1.join + end + assert_nil(t2.value) + assert_equal(t1, t3.value) + + ensure + t1.kill if t1 + t2.kill if t2 + t3.kill if t3 + end + + def test_kill_main_thread + assert_in_out_err([], <<-INPUT, %w(1), []) + p 1 + Thread.kill Thread.current + p 2 + INPUT + end + + def test_kill_wrong_argument + bug4367 = '[ruby-core:35086]' + assert_raise(TypeError, bug4367) { + Thread.kill(nil) + } + end + + def test_exit + s = 0 + Thread.new do + s += 1 + Thread.exit + s += 2 + end.join + assert_equal(1, s) + end + + def test_wakeup + s = 0 + t = Thread.new do + s += 1 + Thread.stop + s += 1 + end + sleep 0.5 + assert_equal(1, s) + t.wakeup + sleep 0.5 + assert_equal(2, s) + assert_raise(ThreadError) { t.wakeup } + + ensure + t.kill if t + end + + def test_stop + assert_in_out_err([], <<-INPUT, %w(2), []) + begin + Thread.stop + p 1 + rescue ThreadError + p 2 + end + INPUT + end + + def test_list + assert_in_out_err([], <<-INPUT) do |r, e| + t1 = Thread.new { sleep } + Thread.pass + t2 = Thread.new { loop { } } + t3 = Thread.new { }.join + p [Thread.current, t1, t2].map{|t| t.object_id }.sort + p Thread.list.map{|t| t.object_id }.sort + INPUT + assert_equal(r.first, r.last) + assert_equal([], e) + end + end + + def test_main + assert_in_out_err([], <<-INPUT, %w(true false), []) + p Thread.main == Thread.current + Thread.new { p Thread.main == Thread.current }.join + INPUT + end + + def test_abort_on_exception + assert_in_out_err([], <<-INPUT, %w(false 1), []) + p Thread.abort_on_exception + begin + Thread.new { raise } + sleep 0.5 + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(true 2), []) + Thread.abort_on_exception = true + p Thread.abort_on_exception + begin + Thread.new { raise } + sleep 0.5 + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), /.+/) + p Thread.abort_on_exception + begin + Thread.new { raise } + sleep 0.5 + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(false true 2), []) + p Thread.abort_on_exception + begin + t = Thread.new { sleep 0.5; raise } + t.abort_on_exception = true + p t.abort_on_exception + sleep 1 + p 1 + rescue + p 2 + end + INPUT + end + + def test_status_and_stop_p + a = ::Thread.new { raise("die now") } + b = Thread.new { Thread.stop } + c = Thread.new { Thread.exit } + d = Thread.new { sleep } + e = Thread.current + sleep 0.5 + + assert_equal(nil, a.status) + assert(a.stop?) + + assert_equal("sleep", b.status) + assert(b.stop?) + + assert_equal(false, c.status) + assert_match(/^#<TestThread::Thread:.* dead>$/, c.inspect) + assert(c.stop?) + + d.kill + assert_equal(["aborting", false], [d.status, d.stop?]) + + assert_equal(["run", false], [e.status, e.stop?]) + + ensure + a.kill if a + b.kill if b + c.kill if c + d.kill if d + end + + def test_safe_level + t = Thread.new { $SAFE = 3; sleep } + sleep 0.5 + assert_equal(0, Thread.current.safe_level) + assert_equal(3, t.safe_level) + + ensure + t.kill if t + end + + def test_thread_local + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + + assert_equal(true, t.key?(:foo)) + assert_equal(true, t.key?("foo")) + assert_equal(false, t.key?(:qux)) + assert_equal(false, t.key?("qux")) + + assert_equal([:foo, :bar, :baz], t.keys) + + ensure + t.kill if t + end + + def test_thread_local_security + t = Thread.new { sleep } + + assert_raise(SecurityError) do + Thread.new { $SAFE = 4; t[:foo] }.join + end + + assert_raise(SecurityError) do + Thread.new { $SAFE = 4; t[:foo] = :baz }.join + end + + assert_raise(RuntimeError) do + Thread.new do + Thread.current[:foo] = :bar + Thread.current.freeze + Thread.current[:foo] = :baz + end.join + end + end + + def test_select_wait + assert_nil(IO.select(nil, nil, nil, 1)) + t = Thread.new do + IO.select(nil, nil, nil, nil) + end + sleep 0.5 + t.kill + end + + def test_mutex_deadlock + m = Mutex.new + m.synchronize do + assert_raise(ThreadError) do + m.synchronize do + assert(false) + end + end + end + end + + def test_mutex_interrupt + m = Mutex.new + m.lock + t = Thread.new do + m.lock + :foo + end + sleep 0.5 + t.kill + assert_nil(t.value) + end + + def test_mutex_illegal_unlock + m = Mutex.new + m.lock + assert_raise(ThreadError) do + Thread.new do + m.unlock + end.join + end + end + + def test_mutex_fifo_like_lock + m1 = Mutex.new + m2 = Mutex.new + m1.lock + m2.lock + m1.unlock + m2.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + + m3 = Mutex.new + m1.lock + m2.lock + m3.lock + m1.unlock + m2.unlock + m3.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + assert_equal(false, m3.locked?) + end + + def test_mutex_trylock + m = Mutex.new + assert_equal(true, m.try_lock) + assert_equal(false, m.try_lock, '[ruby-core:20943]') + + Thread.new{ + assert_equal(false, m.try_lock) + }.join + + m.unlock + end + + def test_recursive_outer + arr = [] + obj = Struct.new(:foo, :visited).new(arr, false) + arr << obj + def obj.hash + self[:visited] = true + super + raise "recursive_outer should short circuit intermediate calls" + end + assert_nothing_raised {arr.hash} + assert(obj[:visited]) + end +end + +class TestThreadGroup < Test::Unit::TestCase + def test_thread_init + thgrp = ThreadGroup.new + Thread.new{ + thgrp.add(Thread.current) + assert_equal(thgrp, Thread.new{sleep 1}.group) + }.join + end + + def test_frozen_thgroup + thgrp = ThreadGroup.new + + t = Thread.new{1} + Thread.new{ + thgrp.add(Thread.current) + thgrp.freeze + assert_raise(ThreadError) do + Thread.new{1}.join + end + assert_raise(ThreadError) do + thgrp.add(t) + end + assert_raise(ThreadError) do + ThreadGroup.new.add Thread.current + end + }.join + t.join + end + + def test_enclosed_thgroup + thgrp = ThreadGroup.new + assert_equal(false, thgrp.enclosed?) + + t = Thread.new{1} + Thread.new{ + thgrp.add(Thread.current) + thgrp.enclose + assert_equal(true, thgrp.enclosed?) + assert_nothing_raised do + Thread.new{1}.join + end + assert_raise(ThreadError) do + thgrp.add t + end + assert_raise(ThreadError) do + ThreadGroup.new.add Thread.current + end + }.join + t.join + end + + def test_uninitialized + c = Class.new(Thread) + c.class_eval { def initialize; end } + assert_raise(ThreadError) { c.new.start } + end + + def test_backtrace + Thread.new{ + assert_equal(Array, Thread.main.backtrace.class) + }.join + + t = Thread.new{} + t.join + assert_equal(nil, t.backtrace) + end +end |
