diff options
Diffstat (limited to 'test/ruby/test_ractor.rb')
| -rw-r--r-- | test/ruby/test_ractor.rb | 215 |
1 files changed, 189 insertions, 26 deletions
diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index c1f33798ba..e7eb0cd4b3 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -3,38 +3,19 @@ require 'test/unit' class TestRactor < Test::Unit::TestCase def test_shareability_of_iseq_proc - y = nil.instance_eval do + assert_raise Ractor::IsolationError do foo = [] - proc { foo } + Ractor.shareable_proc{ foo } end - assert_unshareable(y, /unshareable object \[\] from variable 'foo'/) - - y = [].instance_eval { proc { self } } - assert_unshareable(y, /Proc's self is not shareable/) - - y = [].freeze.instance_eval { proc { self } } - assert_make_shareable(y) - end - - def test_shareability_of_curried_proc - x = nil.instance_eval do - foo = [] - proc { foo }.curry - end - assert_unshareable(x, /unshareable object \[\] from variable 'foo'/) - - x = nil.instance_eval do - foo = 123 - proc { foo }.curry - end - assert_make_shareable(x) end def test_shareability_of_method_proc + # TODO: fix with Ractor.shareable_proc/lambda +=begin str = +"" x = str.instance_exec { proc { to_s } } - assert_unshareable(x, /Proc's self is not shareable/) + assert_unshareable(x, /Proc\'s self is not shareable/) x = str.instance_exec { method(:to_s) } assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) @@ -58,6 +39,60 @@ class TestRactor < Test::Unit::TestCase x = str.instance_exec { method(:itself).to_proc } assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error) +=end + end + + def test_shareable_proc_define_method_super_method_missing + assert_ractor(<<~'RUBY', timeout: 30) + iterations = 1_000_000 + + class SuperFromShareableProcMethodMissingBase + def method_missing(mid, *) = mid + end + + class SuperFromShareableProcMethodMissingChild < SuperFromShareableProcMethodMissingBase + BODY = Ractor.shareable_proc { super() } + define_method(:foo, &BODY) + define_method(:bar, &BODY) + end + + [:foo, :bar].map do |mid| + Ractor.new(mid, iterations) do |mid, iterations| + obj = SuperFromShareableProcMethodMissingChild.new + iterations.times do + got = obj.__send__(mid) + raise "#{mid} returned #{got.inspect}" unless got == mid + end + end + end.each(&:value) + RUBY + end + + def test_shareable_proc_define_method_super_method_entry + assert_ractor(<<~'RUBY', timeout: 30) + iterations = 1_000_000 + + class SuperFromShareableProcBase + def foo = :foo + def bar = :bar + end + + class SuperFromShareableProcChild < SuperFromShareableProcBase + BODY = Ractor.shareable_proc { super() } + define_method(:foo, &BODY) + define_method(:bar, &BODY) + end + + [:foo, :bar].map do |mid| + Ractor.new(mid, iterations) do |mid, iterations| + obj = SuperFromShareableProcChild.new + iterations.times do + got = obj.__send__(mid) + raise "#{mid} returned #{got.inspect}" unless got == mid + end + end + end.each(&:value) + RUBY end def test_shareability_error_uses_inspect @@ -65,7 +100,58 @@ class TestRactor < Test::Unit::TestCase def x.to_s raise "this should not be called" end - assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()> because it refers unshareable objects", exception: Ractor::Error) + end + + def test_sending_exception_with_backtrace + assert_ractor(<<~'RUBY') + def build_error + raise "Test" + rescue => error + error + end + + error = build_error + refute_empty error.backtrace + refute_empty error.backtrace_locations + + backtrace, backtrace_locations = Ractor.new(error) do |error2| + [error2.backtrace, error2.backtrace_locations] + end.value + + assert_equal error.backtrace, backtrace + refute_empty backtrace_locations + RUBY + end + + def test_sending_exception_with_array_backtrace + assert_ractor(<<~'RUBY') + error = StandardError.new + error.set_backtrace(["foo", "bar"]) + refute_empty error.backtrace + assert_nil error.backtrace_locations + + backtrace, backtrace_locations = Ractor.new(error) do |error2| + [error2.backtrace, error2.backtrace_locations] + end.value + + assert_equal error.backtrace, backtrace + assert_nil backtrace_locations + RUBY + end + + def test_sending_object_with_broken_clone + assert_ractor(<<~'RUBY') + o = Object.new + def o.clone + self + end + ractor = Ractor.new { Ractor.receive } + error = assert_raise Ractor::Error do + ractor.send(o) + end + assert_match "#clone returned self", error.message + RUBY end def test_default_thread_group @@ -99,6 +185,22 @@ class TestRactor < Test::Unit::TestCase RUBY end + + def test_class_variables + # [Bug #22072] + assert_ractor(<<~'RUBY') + module Foo + def self.foo = @@foo + end + + Foo.class_variable_set(:@@foo, 1) + + 10.times { |i| Foo.class_variable_set(:"@@bar#{i}", i) } + + assert_equal(Foo.foo, 1) + RUBY + end + def test_struct_instance_variables assert_ractor(<<~'RUBY') StructIvar = Struct.new(:member) do @@ -117,6 +219,16 @@ class TestRactor < Test::Unit::TestCase RUBY end + def test_move_nested_hash_during_gc_with_yjit + assert_ractor(<<~'RUBY', timeout: 20, args: [{ "RUBY_YJIT_ENABLE" => "1" }]) + GC.stress = true + hash = { foo: { bar: "hello" }, baz: { qux: "there" } } + result = Ractor.new { Ractor.receive }.send(hash, move: true).value + assert_equal "hello", result[:foo][:bar] + assert_equal "there", result[:baz][:qux] + RUBY + end + def test_fork_raise_isolation_error assert_ractor(<<~'RUBY') ractor = Ractor.new do @@ -164,7 +276,7 @@ class TestRactor < Test::Unit::TestCase # [Bug #21398] def test_port_receive_dnt_with_port_send - omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|darwin/ + omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/ assert_ractor(<<~'RUBY', timeout: 90) THREADS = 10 JOBS_PER_THREAD = 50 @@ -209,6 +321,57 @@ class TestRactor < Test::Unit::TestCase RUBY end + def test_mn_threads + # Ideally, we would assert that vm->ractor.sched.max_cpu equals sysconf(_SC_NPROCESSORS_ONLN) + # when RUBY_MAX_CPU is not set. + assert_ractor(<<~'RUBY', args: [{ "RUBY_MN_THREADS" => "1" }]) + require "etc" + n = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 8 + rs = n.times.map { Ractor.new { :ok } } + assert_equal [:ok] * n, rs.map(&:value) + RUBY + end + + def test_symbol_proc_is_shareable + pr = :symbol.to_proc + assert_make_shareable(pr) + end + + # [Bug #21775] + def test_ifunc_proc_not_shareable + h = Hash.new { self } + pr = h.to_proc + assert_unshareable(pr, /not supported yet/, exception: RuntimeError) + end + + def test_copy_unshareable_object_error_message + assert_ractor(<<~'RUBY') + pr = proc {} + err = assert_raise(Ractor::Error) do + Ractor.new(pr) {}.join + end + assert_match(/can not copy Proc object/, err.message) + RUBY + end + + def test_ractor_new_raises_isolation_error_if_outer_variables_are_accessed + assert_raise(Ractor::IsolationError) do + channel = Ractor::Port.new + Ractor.new(channel) do + inbound_work = Ractor::Port.new + channel << inbound_work + end + end + end + + def test_ractor_new_raises_isolation_error_if_proc_uses_yield + assert_raise(Ractor::IsolationError) do + Ractor.new do + yield + end + end + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) |
