summaryrefslogtreecommitdiff
path: root/test/ruby/test_ractor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_ractor.rb')
-rw-r--r--test/ruby/test_ractor.rb215
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)