summaryrefslogtreecommitdiff
path: root/test/-ext-
diff options
context:
space:
mode:
Diffstat (limited to 'test/-ext-')
-rw-r--r--test/-ext-/box/test_load_ext.rb97
-rw-r--r--test/-ext-/bug_reporter/test_bug_reporter.rb17
-rw-r--r--test/-ext-/debug/test_debug.rb67
-rw-r--r--test/-ext-/debug/test_profile_frames.rb70
-rw-r--r--test/-ext-/gvl/test_last_thread.rb3
-rw-r--r--test/-ext-/integer/test_my_integer.rb34
-rw-r--r--test/-ext-/iseq_load/test_iseq_load.rb2
-rw-r--r--test/-ext-/load/test_resolve_symbol.rb27
-rw-r--r--test/-ext-/load/test_stringify_symbols.rb35
-rw-r--r--test/-ext-/marshal/test_internal_ivar.rb10
-rw-r--r--test/-ext-/postponed_job/test_postponed_job.rb72
-rw-r--r--test/-ext-/required.rb10
-rw-r--r--test/-ext-/scheduler/test_interrupt_with_scheduler.rb54
-rw-r--r--test/-ext-/stack/test_stack_overflow.rb55
-rw-r--r--test/-ext-/string/test_capacity.rb8
-rw-r--r--test/-ext-/string/test_fstring.rb20
-rw-r--r--test/-ext-/string/test_interned_str.rb5
-rw-r--r--test/-ext-/string/test_set_len.rb47
-rw-r--r--test/-ext-/string/test_too_many_dummy_encodings.rb2
-rw-r--r--test/-ext-/struct/test_data.rb18
-rw-r--r--test/-ext-/symbol/test_type.rb21
-rw-r--r--test/-ext-/test_abi.rb12
-rw-r--r--test/-ext-/test_bug-3571.rb4
-rw-r--r--test/-ext-/test_ensure_and_callcc.rb40
-rw-r--r--test/-ext-/thread/helper.rb51
-rw-r--r--test/-ext-/thread/test_instrumentation_api.rb276
-rw-r--r--test/-ext-/thread/test_lock_native_thread.rb54
-rw-r--r--test/-ext-/thread_fd/test_thread_fd_close.rb24
-rw-r--r--test/-ext-/tracepoint/test_tracepoint.rb21
29 files changed, 1000 insertions, 156 deletions
diff --git a/test/-ext-/box/test_load_ext.rb b/test/-ext-/box/test_load_ext.rb
new file mode 100644
index 0000000000..ea3744375e
--- /dev/null
+++ b/test/-ext-/box/test_load_ext.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_Load_Extensions < Test::Unit::TestCase
+ ENV_ENABLE_BOX = {'RUBY_BOX' => '1'}
+
+ def test_load_extension
+ pend
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require '-test-/box/yay1'
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+ end;
+ end
+
+ def test_extension_contamination_in_global
+ pend
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require '-test-/box/yay1'
+ yay1 = Yay
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+
+ require '-test-/box/yay2'
+ assert_equal "2.0.0", Yay.version
+ v = Yay.yay
+ assert(v == "yay" || v == "yaaay") # "yay" on Linux, "yaaay" on macOS, Win32
+ end;
+ end
+
+ def test_load_extension_in_box
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay1'
+ assert_equal "1.0.0", ns::Yay.version
+ assert_raise(NameError) { Yay }
+ end;
+ end
+
+ def test_different_version_extensions
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns1 = Ruby::Box.new
+ ns2 = Ruby::Box.new
+ ns1.require('-test-/box/yay1')
+ ns2.require('-test-/box/yay2')
+
+ assert_raise(NameError) { Yay }
+ assert_not_nil ns1::Yay
+ assert_not_nil ns2::Yay
+ assert_equal "1.0.0", ns1::Yay::VERSION
+ assert_equal "2.0.0", ns2::Yay::VERSION
+ assert_equal "1.0.0", ns1::Yay.version
+ assert_equal "2.0.0", ns2::Yay.version
+ end;
+ end
+
+ def test_loading_extensions_from_global_to_local
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require '-test-/box/yay1'
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay2'
+ assert_equal "2.0.0", ns::Yay.version
+ assert_equal "yaaay", ns::Yay.yay
+
+ assert_equal "yay", Yay.yay
+ end;
+ end
+
+ def test_loading_extensions_from_local_to_global
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay1'
+ assert_equal "1.0.0", ns::Yay.version
+ assert_equal "yay", ns::Yay.yay
+
+
+ require '-test-/box/yay2'
+ assert_equal "2.0.0", Yay.version
+ assert_equal "yaaay", Yay.yay
+
+ assert_equal "yay", ns::Yay.yay
+ end;
+ end
+end
diff --git a/test/-ext-/bug_reporter/test_bug_reporter.rb b/test/-ext-/bug_reporter/test_bug_reporter.rb
index 81d2ca4674..83fdba2282 100644
--- a/test/-ext-/bug_reporter/test_bug_reporter.rb
+++ b/test/-ext-/bug_reporter/test_bug_reporter.rb
@@ -2,17 +2,12 @@
require 'test/unit'
require 'tmpdir'
require_relative '../../lib/jit_support'
+require_relative '../../lib/parser_support'
class TestBugReporter < Test::Unit::TestCase
- def yjit_enabled?
- defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
- end
-
def test_bug_reporter_add
- omit if ENV['RUBY_ON_BUG']
-
description = RUBY_DESCRIPTION
- description = description.sub(/\+MJIT /, '') unless JITSupport.mjit_force_enabled?
+ description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess?
expected_stderr = [
:*,
/\[BUG\]\sSegmentation\sfault.*\n/,
@@ -24,9 +19,11 @@ class TestBugReporter < Test::Unit::TestCase
tmpdir = Dir.mktmpdir
no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
- args = ["--disable-gems", "-r-test-/bug_reporter",
- "-C", tmpdir]
- args.push("--yjit") if yjit_enabled? # We want the printed description to match this process's RUBY_DESCRIPTION
+ args = ["-r-test-/bug_reporter", "-C", tmpdir]
+ # We want the printed description to match this process's RUBY_DESCRIPTION
+ args.push("--yjit") if JITSupport.yjit_enabled?
+ args.push("--zjit") if JITSupport.zjit_enabled?
+ args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
stdin = "#{no_core}register_sample_bug_reporter(12345); Process.kill :SEGV, $$"
assert_in_out_err(args, stdin, [], expected_stderr, encoding: "ASCII-8BIT")
ensure
diff --git a/test/-ext-/debug/test_debug.rb b/test/-ext-/debug/test_debug.rb
index 8a351d74fa..c9263d76fa 100644
--- a/test/-ext-/debug/test_debug.rb
+++ b/test/-ext-/debug/test_debug.rb
@@ -26,11 +26,14 @@ class TestDebug < Test::Unit::TestCase
count[:iseq] += 1
assert_instance_of(RubyVM::InstructionSequence, iseq, msg)
- # check same location
- assert_equal(loc.path, iseq.path, msg)
- assert_equal(loc.absolute_path, iseq.absolute_path, msg)
- assert_equal(loc.label, iseq.label, msg)
- assert_operator(loc.lineno, :>=, iseq.first_lineno, msg)
+ # Backtraces and source locations don't match for :c_trace methods
+ unless iseq.disasm.include?('C_TRACE')
+ # check same location
+ assert_equal(loc.path, iseq.path, msg)
+ assert_equal(loc.absolute_path, iseq.absolute_path, msg)
+ #assert_equal(loc.label, iseq.label, msg)
+ assert_operator(loc.lineno, :>=, iseq.first_lineno, msg)
+ end
end
assert_instance_of(Thread::Backtrace::Location, loc, msg)
@@ -73,3 +76,57 @@ class TestDebug < Test::Unit::TestCase
assert_equal true, x, '[Bug #15105]'
end
end
+
+# This is a YJIT test, but we can't test this without a C extension that calls
+# rb_debug_inspector_open(), so we're testing it using "-test-/debug" here.
+class TestDebugWithYJIT < Test::Unit::TestCase
+ class LocalSetArray
+ def to_a
+ Bug::Debug.inspector.each do |_, binding,|
+ binding.local_variable_set(:local, :ok) if binding
+ end
+ [:ok]
+ end
+ end
+
+ class DebugArray
+ def to_a
+ Bug::Debug.inspector
+ [:ok]
+ end
+ end
+
+ def test_yjit_invalidates_getlocal_after_splatarray
+ val = getlocal_after_splatarray(LocalSetArray.new)
+ assert_equal [:ok, :ok], val
+ end
+
+ def test_yjit_invalidates_setlocal_after_splatarray
+ val = setlocal_after_splatarray(DebugArray.new)
+ assert_equal [:ok], val
+ end
+
+ def test_yjit_invalidates_setlocal_after_proc_call
+ val = setlocal_after_proc_call(proc { Bug::Debug.inspector; :ok })
+ assert_equal :ok, val
+ end
+
+ private
+
+ def getlocal_after_splatarray(array)
+ local = 1
+ [*array, local]
+ end
+
+ def setlocal_after_splatarray(array)
+ local = *array # setlocal followed by splatarray
+ itself # split a block using a C call
+ local # getlocal
+ end
+
+ def setlocal_after_proc_call(block)
+ local = block.call # setlocal followed by OPTIMIZED_METHOD_TYPE_CALL
+ itself # split a block using a C call
+ local # getlocal
+ end
+end if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
diff --git a/test/-ext-/debug/test_profile_frames.rb b/test/-ext-/debug/test_profile_frames.rb
index d6ae953dd2..d79c94c468 100644
--- a/test/-ext-/debug/test_profile_frames.rb
+++ b/test/-ext-/debug/test_profile_frames.rb
@@ -14,6 +14,8 @@ class SampleClassForTestProfileFrames
end
class Sample2
+ EVAL_LINE = __LINE__ + 3
+
def baz(block)
instance_eval "def zab(block) block.call end"
[self, zab(block)]
@@ -37,6 +39,20 @@ class SampleClassForTestProfileFrames
end
end
+class SampleClassForTestProfileThreadFrames
+ def initialize(mutex)
+ @mutex = mutex
+ end
+
+ def foo(block)
+ bar(block)
+ end
+
+ def bar(block)
+ block.call
+ end
+end
+
class TestProfileFrames < Test::Unit::TestCase
def test_profile_frames
obj, frames = Fiber.new{
@@ -112,7 +128,7 @@ class TestProfileFrames < Test::Unit::TestCase
"SampleClassForTestProfileFrames#foo",
"TestProfileFrames#test_profile_frames",
]
- paths = [ nil, file=__FILE__, "(eval)", file, file, file, file, file, file, nil ]
+ paths = [ nil, file=__FILE__, "(eval at #{__FILE__}:#{SampleClassForTestProfileFrames::Sample2::EVAL_LINE})", file, file, file, file, file, file, nil ]
absolute_paths = [ "<cfunc>", file, nil, file, file, file, file, file, file, nil ]
assert_equal(labels.size, frames.size)
@@ -137,21 +153,65 @@ class TestProfileFrames < Test::Unit::TestCase
}
end
+ def test_profile_thread_frames
+ mutex = Mutex.new
+ th = Thread.new do
+ mutex.lock
+ Thread.stop
+ SampleClassForTestProfileThreadFrames.new(mutex).foo(lambda { mutex.unlock; loop { sleep(1) } } )
+ end
+
+ # ensure execution has reached SampleClassForTestProfileThreadFrames#bar before running profile_thread_frames
+ loop { break if th.status == "sleep"; sleep 0.1 }
+ th.run
+ mutex.lock # wait until SampleClassForTestProfileThreadFrames#bar has been called
+
+ frames = Bug::Debug.profile_thread_frames(th, 0, 10)
+
+ full_labels = [
+ "Kernel#sleep",
+ "TestProfileFrames#test_profile_thread_frames",
+ "Kernel#loop",
+ "TestProfileFrames#test_profile_thread_frames",
+ "SampleClassForTestProfileThreadFrames#bar",
+ "SampleClassForTestProfileThreadFrames#foo",
+ "TestProfileFrames#test_profile_thread_frames",
+ ]
+
+ frames.each.with_index do |frame, i|
+ assert_equal(full_labels[i], frame)
+ end
+
+ ensure
+ th.kill
+ th.join
+ end
+
+
def test_matches_backtrace_locations_main_thread
assert_equal(Thread.current, Thread.main)
# Keep these in the same line, so the backtraces match exactly
backtrace_locations, profile_frames = [Thread.current.backtrace_locations, Bug::Debug.profile_frames(0, 100)]
- assert_equal(backtrace_locations.size, profile_frames.size)
+ errmsg = "backtrace_locations:\n " + backtrace_locations.map.with_index{|loc, i| "#{i} #{loc}"}.join("\n ")
+ errmsg += "\n\nprofile_frames:\n " + profile_frames.map.with_index{|(path, absolute_path, _, base_label, _, _, _, _, _, full_label, lineno), i|
+ if lineno
+ "#{i} #{absolute_path}:#{lineno} // #{full_label}"
+ else
+ "#{i} #{absolute_path} #{full_label}"
+ end
+ }.join("\n ")
+ assert_equal(backtrace_locations.size, profile_frames.size, errmsg)
# The first entries are not going to match, since one is #backtrace_locations and the other #profile_frames
backtrace_locations.shift
profile_frames.shift
# The rest of the stack is expected to look the same...
- backtrace_locations.zip(profile_frames).each.with_index do |(location, (path, absolute_path, _, base_label, _, _, _, _, _, _, lineno)), i|
+ backtrace_locations.zip(profile_frames).each.with_index do |(location, (path, absolute_path, _, base_label, label, _, _, _, _, _, lineno)), i|
next if absolute_path == "<cfunc>" # ...except for cfunc frames
+ next if label in "Array#each" | "Array#map" # ...except for :c_trace method frames
err_msg = "#{i}th frame"
assert_equal(location.absolute_path, absolute_path, err_msg)
@@ -177,4 +237,8 @@ class TestProfileFrames < Test::Unit::TestCase
a
end;
end
+
+ def test_start
+ assert_equal Bug::Debug.profile_frames(0, 10).tap(&:shift), Bug::Debug.profile_frames(1, 9)
+ end
end
diff --git a/test/-ext-/gvl/test_last_thread.rb b/test/-ext-/gvl/test_last_thread.rb
index f1bebafeea..bcda0e3385 100644
--- a/test/-ext-/gvl/test_last_thread.rb
+++ b/test/-ext-/gvl/test_last_thread.rb
@@ -15,8 +15,7 @@ class TestLastThread < Test::Unit::TestCase
t1 = Time.now
t = t1 - t0
- assert_in_delta(1.0, t, 0.16)
+ assert_in_delta(1.0, t, 0.8)
end;
end
end
-
diff --git a/test/-ext-/integer/test_my_integer.rb b/test/-ext-/integer/test_my_integer.rb
index 1b6f8489f8..0dfa234921 100644
--- a/test/-ext-/integer/test_my_integer.rb
+++ b/test/-ext-/integer/test_my_integer.rb
@@ -8,19 +8,13 @@ class Test_MyInteger < Test::Unit::TestCase
Bug::Integer::MyInteger.new.to_f
end
- begin
- Bug::Integer::MyInteger.class_eval do
- def to_f
- end
+ int = Class.new(Bug::Integer::MyInteger) do
+ def to_f
end
+ end
- assert_nothing_raised do
- Bug::Integer::MyInteger.new.to_f
- end
- ensure
- Bug::Integer::MyInteger.class_eval do
- remove_method :to_f
- end
+ assert_nothing_raised do
+ int.new.to_f
end
end
@@ -29,20 +23,14 @@ class Test_MyInteger < Test::Unit::TestCase
Bug::Integer::MyInteger.new <=> 0
end
- begin
- Bug::Integer::MyInteger.class_eval do
- def <=>(other)
- 0
- end
+ int = Class.new(Bug::Integer::MyInteger) do
+ def <=>(other)
+ 0
end
+ end
- assert_nothing_raised do
- Bug::Integer::MyInteger.new <=> 0
- end
- ensure
- Bug::Integer::MyInteger.class_eval do
- remove_method :<=>
- end
+ assert_nothing_raised do
+ int.new <=> 0
end
end
end
diff --git a/test/-ext-/iseq_load/test_iseq_load.rb b/test/-ext-/iseq_load/test_iseq_load.rb
index 6e5bc8e811..864ce1afbb 100644
--- a/test/-ext-/iseq_load/test_iseq_load.rb
+++ b/test/-ext-/iseq_load/test_iseq_load.rb
@@ -123,6 +123,8 @@ class TestIseqLoad < Test::Unit::TestCase
assert_equal false, test_break_ensure_def_method
omit "failing due to exception entry sp mismatch"
assert_iseq_roundtrip(src)
+ ensure
+ Object.undef_method(:test_break_ensure_def_method) rescue nil
end
def test_kwarg
diff --git a/test/-ext-/load/test_resolve_symbol.rb b/test/-ext-/load/test_resolve_symbol.rb
new file mode 100644
index 0000000000..471d3acebd
--- /dev/null
+++ b/test/-ext-/load/test_resolve_symbol.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_Load_ResolveSymbol < Test::Unit::TestCase
+ def test_load_resolve_symbol_resolver
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ feature = "Feature #20005"
+ assert_raise(LoadError, "resolve_symbol_target is not loaded") {
+ require '-test-/load/resolve_symbol_resolver'
+ }
+ require '-test-/load/resolve_symbol_target'
+ assert_nothing_raised(LoadError, "#{feature} resolver can be loaded") {
+ require '-test-/load/resolve_symbol_resolver'
+ }
+ assert_not_nil ResolveSymbolResolver
+ assert_equal "from target", ResolveSymbolResolver.any_method
+
+ assert_raise(LoadError, "tries to resolve missing feature name, and it should raise LoadError") {
+ ResolveSymbolResolver.try_resolve_fname
+ }
+ assert_raise(LoadError, "tries to resolve missing symbol name, and it should raise LoadError") {
+ ResolveSymbolResolver.try_resolve_sname
+ }
+ end;
+ end
+end
diff --git a/test/-ext-/load/test_stringify_symbols.rb b/test/-ext-/load/test_stringify_symbols.rb
new file mode 100644
index 0000000000..0d9736b591
--- /dev/null
+++ b/test/-ext-/load/test_stringify_symbols.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_Load_stringify_symbols < Test::Unit::TestCase
+ def test_load_stringify_symbol_required_extensions
+ require '-test-/load/stringify_symbols'
+ require '-test-/load/stringify_target'
+ r1 = StringifySymbols.stringify_symbol("-test-/load/stringify_target", "stt_any_method")
+ assert_not_nil r1
+ r2 = StringifySymbols.stringify_symbol("-test-/load/stringify_target.so", "stt_any_method")
+ assert_equal r1, r2, "resolved symbols should be equal even with or without .so suffix"
+ end
+
+ def test_load_stringify_symbol_statically_linked
+ require '-test-/load/stringify_symbols'
+ # "complex.so" is actually not a statically linked extension.
+ # But it is registered in $LOADED_FEATURES, so it can be a target of this test.
+ r1 = StringifySymbols.stringify_symbol("complex", "rb_complex_minus")
+ assert_not_nil r1
+ r2 = StringifySymbols.stringify_symbol("complex.so", "rb_complex_minus")
+ assert_equal r1, r2
+ end
+
+ def test_load_stringify_symbol_missing_target
+ require '-test-/load/stringify_symbols'
+ r1 = assert_nothing_raised {
+ StringifySymbols.stringify_symbol("something_missing", "unknown_method")
+ }
+ assert_nil r1
+ r2 = assert_nothing_raised {
+ StringifySymbols.stringify_symbol("complex.so", "unknown_method")
+ }
+ assert_nil r2
+ end
+end
diff --git a/test/-ext-/marshal/test_internal_ivar.rb b/test/-ext-/marshal/test_internal_ivar.rb
index a32138f6e8..8b4667fdf9 100644
--- a/test/-ext-/marshal/test_internal_ivar.rb
+++ b/test/-ext-/marshal/test_internal_ivar.rb
@@ -7,11 +7,16 @@ module Bug end
module Bug::Marshal
class TestInternalIVar < Test::Unit::TestCase
def test_marshal
- v = InternalIVar.new("hello", "world", "bye")
+ v = InternalIVar.new("hello", "world", "bye", "hi")
assert_equal("hello", v.normal)
assert_equal("world", v.internal)
assert_equal("bye", v.encoding_short)
- dump = assert_warn(/instance variable `E' on class \S+ is not dumped/) {
+ assert_equal("hi", v.encoding_long)
+ warnings = ->(s) {
+ w = s.scan(/instance variable '(.+?)' on class \S+ is not dumped/)
+ assert_equal(%w[E K encoding], w.flatten.sort)
+ }
+ dump = assert_warn(warnings) {
::Marshal.dump(v)
}
v = assert_nothing_raised {break ::Marshal.load(dump)}
@@ -19,6 +24,7 @@ module Bug::Marshal
assert_equal("hello", v.normal)
assert_nil(v.internal)
assert_nil(v.encoding_short)
+ assert_nil(v.encoding_long)
end
end
end
diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb
index fee0172d11..8c2b3e95d1 100644
--- a/test/-ext-/postponed_job/test_postponed_job.rb
+++ b/test/-ext-/postponed_job/test_postponed_job.rb
@@ -2,34 +2,70 @@
require 'test/unit'
require '-test-/postponed_job'
-module Bug
- def self.postponed_job_call_direct_wrapper(*args)
- postponed_job_call_direct(*args)
+class TestPostponed_job < Test::Unit::TestCase
+ def test_preregister_and_trigger
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ Bug.postponed_job_preregister_and_call_without_sleep(counters = [])
+ # i.e. rb_postponed_job_trigger performs coalescing
+ assert_equal([3], counters)
+
+ # i.e. rb_postponed_job_trigger resets after interrupts are checked
+ Bug.postponed_job_preregister_and_call_with_sleep(counters = [])
+ assert_equal([1, 2, 3], counters)
+ RUBY
end
- def self.postponed_job_register_wrapper(*args)
- postponed_job_register(*args)
+ def test_multiple_preregistration
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ handles = Bug.postponed_job_preregister_multiple_times
+ # i.e. rb_postponed_job_preregister returns the same handle if preregistered multiple times
+ assert_equal [handles[0]], handles.uniq
+ RUBY
end
-end
-class TestPostponed_job < Test::Unit::TestCase
- def test_register
- direct, registered = [], []
+ def test_multiple_preregistration_with_new_data
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ values = Bug.postponed_job_preregister_calls_with_last_argument
+ # i.e. the callback is called with the last argument it was preregistered with
+ assert_equal [3, 4], values
+ RUBY
+ end
- Bug.postponed_job_call_direct_wrapper(direct)
- Bug.postponed_job_register_wrapper(registered)
+ def test_legacy_register
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ direct, registered = [], []
- assert_equal([0], direct)
- assert_equal([3], registered)
+ Bug.postponed_job_call_direct(direct)
+ Bug.postponed_job_register(registered)
- Bug.postponed_job_register_one(ary = [])
- assert_equal [1], ary
+ assert_equal([0], direct)
+ assert_equal([3], registered)
+
+ Bug.postponed_job_register_one(ary = [])
+ assert_equal [1], ary
+ RUBY
+ end
+
+ def test_legacy_register_one_same
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ # Registering the same job three times should result in three of the same handle
+ handles = Bug.postponed_job_register_one_same
+ assert_equal [handles[0]], handles.uniq
+ RUBY
end
if Bug.respond_to?(:postponed_job_register_in_c_thread)
- def test_register_in_c_thread
- assert Bug.postponed_job_register_in_c_thread(ary = [])
- assert_equal [1], ary
+ def test_legacy_register_in_c_thread
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ assert Bug.postponed_job_register_in_c_thread(ary = [])
+ assert_equal [1], ary
+ RUBY
end
end
end
diff --git a/test/-ext-/required.rb b/test/-ext-/required.rb
new file mode 100644
index 0000000000..70514355ff
--- /dev/null
+++ b/test/-ext-/required.rb
@@ -0,0 +1,10 @@
+require 'continuation'
+cont = nil
+a = [*1..10].reject do |i|
+ callcc {|c| cont = c} if !cont and i == 10
+ false
+end
+if a.size < 1000
+ a.unshift(:x)
+ cont.call
+end
diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb
new file mode 100644
index 0000000000..eb7a0647e5
--- /dev/null
+++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+require 'test/unit'
+require 'timeout'
+require_relative '../../fiber/scheduler'
+
+class TestSchedulerInterruptHandling < Test::Unit::TestCase
+ def setup
+ pend("No fork support") unless Process.respond_to?(:fork)
+ require '-test-/scheduler'
+ end
+
+ # Test without Thread.handle_interrupt - should work regardless of fix
+ def test_without_handle_interrupt_signal_works
+ IO.pipe do |input, output|
+ pid = fork do
+ STDERR.reopen(output)
+
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Signal.trap(:INT) do
+ ::Thread.current.raise(Interrupt)
+ end
+
+ Fiber.schedule do
+ # Yield to the scheduler:
+ sleep(0)
+
+ Bug::Scheduler.blocking_loop(output)
+ end
+ end
+
+ output.close
+ assert_equal "x", input.read(1)
+
+ Process.kill(:INT, pid)
+
+ reaper = Thread.new do
+ Process.waitpid2(pid)
+ end
+
+ unless reaper.join(10)
+ Process.kill(:KILL, pid)
+ end
+
+ _, status = reaper.value
+
+ # It should be interrupted (not killed):
+ assert_not_equal 0, status.exitstatus
+ assert_equal true, status.signaled?
+ assert_equal Signal.list["INT"], status.termsig
+ end
+ end
+end
diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb
new file mode 100644
index 0000000000..3d7f00331d
--- /dev/null
+++ b/test/-ext-/stack/test_stack_overflow.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_StackOverflow < Test::Unit::TestCase
+ def setup
+ omit "Stack overflow tests are not supported on this platform: #{RUBY_PLATFORM.inspect}" unless RUBY_PLATFORM =~ /x86_64-linux|darwin/
+
+ require '-test-/stack'
+
+ omit "Stack overflow tests are not supported with ASAN" if Thread.asan?
+ end
+
+ def test_overflow
+ assert_separately([], <<~RUBY)
+ # GC may try to scan the top of the stack and cause a SEGV.
+ GC.disable
+ require '-test-/stack'
+
+ assert_raise(SystemStackError) do
+ Thread.stack_overflow
+ end
+ RUBY
+ end
+
+ def test_thread_stack_overflow
+ assert_separately([], <<~RUBY)
+ require '-test-/stack'
+ GC.disable
+
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+ Thread.stack_overflow
+ end
+
+ assert_raise(SystemStackError) do
+ thread.join
+ end
+ RUBY
+ end
+
+ def test_fiber_stack_overflow
+ assert_separately([], <<~RUBY)
+ require '-test-/stack'
+ GC.disable
+
+ fiber = Fiber.new do
+ Thread.stack_overflow
+ end
+
+ assert_raise(SystemStackError) do
+ fiber.resume
+ end
+ RUBY
+ end
+end
diff --git a/test/-ext-/string/test_capacity.rb b/test/-ext-/string/test_capacity.rb
index 0cb7c00761..df000f7cdb 100644
--- a/test/-ext-/string/test_capacity.rb
+++ b/test/-ext-/string/test_capacity.rb
@@ -23,7 +23,7 @@ class Test_StringCapacity < Test::Unit::TestCase
def test_s_new_capacity
assert_equal("", String.new(capacity: 1000))
assert_equal(String, String.new(capacity: 1000).class)
- assert_equal(10000, capa(String.new(capacity: 10000)))
+ assert_equal(10_000, capa(String.new(capacity: 10_000)))
assert_equal("", String.new(capacity: -1000))
assert_equal(capa(String.new(capacity: -10000)), capa(String.new(capacity: -1000)))
@@ -66,11 +66,7 @@ class Test_StringCapacity < Test::Unit::TestCase
end
def embed_header_size
- if GC.using_rvargc?
- 2 * RbConfig::SIZEOF['void*'] + RbConfig::SIZEOF['long']
- else
- 2 * RbConfig::SIZEOF['void*']
- end
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
end
def max_embed_len
diff --git a/test/-ext-/string/test_fstring.rb b/test/-ext-/string/test_fstring.rb
index 5a3456c566..fcec6be543 100644
--- a/test/-ext-/string/test_fstring.rb
+++ b/test/-ext-/string/test_fstring.rb
@@ -15,19 +15,27 @@ class Test_String_Fstring < Test::Unit::TestCase
def test_rb_enc_interned_str_autoloaded_encoding
assert_separately([], <<~RUBY)
require '-test-/string'
- assert_include(Encoding::Windows_31J.inspect, 'autoload')
- Bug::String.rb_enc_interned_str(Encoding::Windows_31J)
+ assert_include(Encoding::CESU_8.inspect, 'autoload')
+ Bug::String.rb_enc_interned_str(Encoding::CESU_8)
RUBY
end
+ def test_rb_enc_interned_str_null_encoding
+ assert_equal Encoding::ASCII_8BIT, Bug::String.rb_enc_interned_str(nil).encoding
+ end
+
def test_rb_enc_str_new_autoloaded_encoding
assert_separately([], <<~RUBY)
require '-test-/string'
- assert_include(Encoding::Windows_31J.inspect, 'autoload')
- Bug::String.rb_enc_str_new(Encoding::Windows_31J)
+ assert_include(Encoding::CESU_8.inspect, 'autoload')
+ Bug::String.rb_enc_str_new(Encoding::CESU_8)
RUBY
end
+ def test_rb_enc_str_new_null_encoding
+ assert_equal Encoding::ASCII_8BIT, Bug::String.rb_enc_str_new(nil).encoding
+ end
+
def test_instance_variable
str = __method__.to_s * 3
str.instance_variable_set(:@test, 42)
@@ -49,6 +57,10 @@ class Test_String_Fstring < Test::Unit::TestCase
assert_raise(TypeError) {fstr.singleton_class}
end
+ def test_fake_str
+ assert_equal([*"a".."z"].join(""), Bug::String.fstring_fake_str)
+ end
+
class S < String
end
diff --git a/test/-ext-/string/test_interned_str.rb b/test/-ext-/string/test_interned_str.rb
index 340dba41e8..a81cb59aa5 100644
--- a/test/-ext-/string/test_interned_str.rb
+++ b/test/-ext-/string/test_interned_str.rb
@@ -9,4 +9,9 @@ class Test_RbInternedStr < Test::Unit::TestCase
src << "b" * 20
assert_equal "a" * 20, interned_str
end
+
+ def test_interned_str_encoding
+ src = :ascii.name
+ assert_equal Encoding::US_ASCII, Bug::String.rb_interned_str_dup(src).encoding
+ end
end
diff --git a/test/-ext-/string/test_set_len.rb b/test/-ext-/string/test_set_len.rb
index 67ba961194..1531d76167 100644
--- a/test/-ext-/string/test_set_len.rb
+++ b/test/-ext-/string/test_set_len.rb
@@ -34,4 +34,51 @@ class Test_StrSetLen < Test::Unit::TestCase
assert_equal 128, Bug::String.capacity(str)
assert_equal 127, str.set_len(127).bytesize, bug12757
end
+
+ def test_coderange_after_append
+ u = -"\u3042"
+ str = Bug::String.new(encoding: Encoding::UTF_8)
+ bsize = u.bytesize
+ str.append(u)
+ assert_equal 0, str.bytesize
+ str.set_len(bsize)
+ assert_equal bsize, str.bytesize
+ assert_predicate str, :valid_encoding?
+ assert_not_predicate str, :ascii_only?
+ assert_equal u, str
+ end
+
+ def test_coderange_after_trunc
+ u = -"\u3042"
+ bsize = u.bytesize
+ str = Bug::String.new(u)
+ str.set_len(bsize - 1)
+ assert_equal bsize - 1, str.bytesize
+ assert_not_predicate str, :valid_encoding?
+ assert_not_predicate str, :ascii_only?
+ str.append(u.byteslice(-1))
+ str.set_len(bsize)
+ assert_equal bsize, str.bytesize
+ assert_predicate str, :valid_encoding?
+ assert_not_predicate str, :ascii_only?
+ assert_equal u, str
+ end
+
+ def test_valid_encoding_after_resized
+ s = "\0\0".force_encoding(Encoding::UTF_16BE)
+ str = Bug::String.new(s)
+ assert_predicate str, :valid_encoding?
+ str.resize(1)
+ assert_not_predicate str, :valid_encoding?
+ str.resize(2)
+ assert_predicate str, :valid_encoding?
+ str.resize(3)
+ assert_not_predicate str, :valid_encoding?
+
+ s = "\xDB\x00\xDC\x00".force_encoding(Encoding::UTF_16BE)
+ str = Bug::String.new(s)
+ assert_predicate str, :valid_encoding?
+ str.resize(2)
+ assert_not_predicate str, :valid_encoding?
+ end
end
diff --git a/test/-ext-/string/test_too_many_dummy_encodings.rb b/test/-ext-/string/test_too_many_dummy_encodings.rb
index 4d71fd1d72..b96b40db7b 100644
--- a/test/-ext-/string/test_too_many_dummy_encodings.rb
+++ b/test/-ext-/string/test_too_many_dummy_encodings.rb
@@ -4,7 +4,7 @@ require "-test-/string"
class Test_TooManyDummyEncodings < Test::Unit::TestCase
def test_exceed_encoding_table_size
- assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
require "-test-/string"
assert_raise_with_message(EncodingError, /too many encoding/) do
diff --git a/test/-ext-/struct/test_data.rb b/test/-ext-/struct/test_data.rb
new file mode 100644
index 0000000000..8dbc9113a5
--- /dev/null
+++ b/test/-ext-/struct/test_data.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/struct"
+
+class Bug::Struct::Test_Data < Test::Unit::TestCase
+ def test_data_new_default
+ klass = Bug::Struct.data_new(false)
+ assert_equal Data, klass.superclass
+ assert_equal %i[mem1 mem2], klass.members
+ end
+
+ def test_data_new_superclass
+ superclass = Data.define
+ klass = Bug::Struct.data_new(superclass)
+ assert_equal superclass, klass.superclass
+ assert_equal %i[mem1 mem2], klass.members
+ end
+end
diff --git a/test/-ext-/symbol/test_type.rb b/test/-ext-/symbol/test_type.rb
index fdee692fe4..ed019062fa 100644
--- a/test/-ext-/symbol/test_type.rb
+++ b/test/-ext-/symbol/test_type.rb
@@ -123,16 +123,25 @@ module Test_Symbol
def test_check_id_invalid_type
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) {
- Bug::Symbol.pinneddown?(cx)
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) {
+ Bug::Symbol.pinneddown?(cx)
+ }
+ end
end
def test_check_symbol_invalid_type
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) {
- Bug::Symbol.find(cx)
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) {
+ Bug::Symbol.find(cx)
+ }
+ end
+ end
+
+ def test_const_name_type
+ sym = "\xb5".force_encoding(Encoding::Windows_1253)
+ assert_not_operator Bug::Symbol, :const?, sym, sym.encode(Encoding::UTF_8)
end
end
end
diff --git a/test/-ext-/test_abi.rb b/test/-ext-/test_abi.rb
index d3ea6bb9b1..7f30feb944 100644
--- a/test/-ext-/test_abi.rb
+++ b/test/-ext-/test_abi.rb
@@ -9,7 +9,11 @@ class TestABI < Test::Unit::TestCase
assert_separately [], <<~RUBY
err = assert_raise(LoadError) { require "-test-/abi" }
assert_match(/incompatible ABI version/, err.message)
- assert_include err.message, "/-test-/abi."
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
RUBY
end
@@ -27,7 +31,11 @@ class TestABI < Test::Unit::TestCase
assert_separately [{ "RUBY_ABI_CHECK" => "1" }], <<~RUBY
err = assert_raise(LoadError) { require "-test-/abi" }
assert_match(/incompatible ABI version/, err.message)
- assert_include err.message, "/-test-/abi."
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
RUBY
end
diff --git a/test/-ext-/test_bug-3571.rb b/test/-ext-/test_bug-3571.rb
index c75d2e8523..5952ce2a33 100644
--- a/test/-ext-/test_bug-3571.rb
+++ b/test/-ext-/test_bug-3571.rb
@@ -13,8 +13,8 @@ end
SRC
out = [
"start() function is unimplemented on this machine",
- "-:2:in `start'",
- "-:2:in `<main>'",
+ "-:2:in 'Bug.start'",
+ "-:2:in '<main>'",
]
assert_in_out_err(%w"-r-test-/bug_3571", src, [], out, bug3571)
end
diff --git a/test/-ext-/test_ensure_and_callcc.rb b/test/-ext-/test_ensure_and_callcc.rb
new file mode 100644
index 0000000000..9303a094ea
--- /dev/null
+++ b/test/-ext-/test_ensure_and_callcc.rb
@@ -0,0 +1,40 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestEnsureAndCallcc < Test::Unit::TestCase
+ def test_bug20655_dir_chdir_using_rb_ensure
+ require 'tmpdir'
+ need_continuation
+ called = 0
+ tmp = nil
+ Dir.mktmpdir do |tmpdir|
+ Dir.chdir(tmpdir) do
+ tmp = Dir.pwd
+ cont = nil
+ callcc{|c| cont = c}
+ assert_equal(tmp, Dir.pwd, "BUG #20655: ensure called and pwd was changed unexpectedly")
+ called += 1
+ cont.call if called < 10
+ end
+ end
+ end
+
+ def test_bug20655_extension_using_rb_ensure
+ need_continuation
+ require '-test-/ensure_and_callcc'
+ EnsureAndCallcc.reset
+ assert_equal(0, EnsureAndCallcc.ensure_called)
+ EnsureAndCallcc.require_with_ensure(File.join(__dir__, 'required'))
+ assert_equal(1, EnsureAndCallcc.ensure_called,
+ "BUG #20655: ensure called unexpectedly in the required script even without exceptions")
+ end
+
+ private
+ def need_continuation
+ unless respond_to?(:callcc, true)
+ EnvUtil.suppress_warning {require 'continuation'}
+ end
+ omit 'requires callcc support' unless respond_to?(:callcc, true)
+ end
+end
diff --git a/test/-ext-/thread/helper.rb b/test/-ext-/thread/helper.rb
new file mode 100644
index 0000000000..3ea2057d15
--- /dev/null
+++ b/test/-ext-/thread/helper.rb
@@ -0,0 +1,51 @@
+module ThreadInstrumentation
+ module TestHelper
+ private
+
+ def record
+ Bug::ThreadInstrumentation.register_callback(!ENV["GVL_DEBUG"])
+ yield
+ ensure
+ timeline = Bug::ThreadInstrumentation.unregister_callback
+ if $!
+ raise
+ else
+ return timeline
+ end
+ end
+
+ def timeline_for(thread, timeline)
+ timeline.select { |t, _| t == thread }.map(&:last)
+ end
+
+ def assert_consistent_timeline(events)
+ refute_predicate events, :empty?
+
+ previous_event = nil
+ events.each do |event|
+ refute_equal :exited, previous_event, "`exited` must be the final event: #{events.inspect}"
+ case event
+ when :started
+ assert_nil previous_event, "`started` must be the first event: #{events.inspect}"
+ when :ready
+ unless previous_event.nil?
+ assert %i(started suspended).include?(previous_event), "`ready` must be preceded by `started` or `suspended`: #{events.inspect}"
+ end
+ when :resumed
+ unless previous_event.nil?
+ assert_equal :ready, previous_event, "`resumed` must be preceded by `ready`: #{events.inspect}"
+ end
+ when :suspended
+ unless previous_event.nil?
+ assert_equal :resumed, previous_event, "`suspended` must be preceded by `resumed`: #{events.inspect}"
+ end
+ when :exited
+ unless previous_event.nil?
+ assert %i(resumed suspended).include?(previous_event), "`exited` must be preceded by `resumed` or `suspended`: #{events.inspect}"
+ end
+ end
+ previous_event = event
+ end
+ end
+ end
+end
diff --git a/test/-ext-/thread/test_instrumentation_api.rb b/test/-ext-/thread/test_instrumentation_api.rb
index dd620e7380..ba41069304 100644
--- a/test/-ext-/thread/test_instrumentation_api.rb
+++ b/test/-ext-/thread/test_instrumentation_api.rb
@@ -1,91 +1,291 @@
# frozen_string_literal: false
require 'envutil'
+require_relative "helper"
class TestThreadInstrumentation < Test::Unit::TestCase
+ include ThreadInstrumentation::TestHelper
+
def setup
pend("No windows support") if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
require '-test-/thread/instrumentation'
- Thread.list.each do |thread|
- if thread != Thread.current
- thread.kill
- thread.join rescue nil
- end
- end
- assert_equal [Thread.current], Thread.list
-
- Bug::ThreadInstrumentation.reset_counters
- Bug::ThreadInstrumentation::register_callback
+ cleanup_threads
end
def teardown
return if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
- Bug::ThreadInstrumentation::unregister_callback
+ Bug::ThreadInstrumentation.unregister_callback
+ cleanup_threads
end
THREADS_COUNT = 3
- def test_thread_instrumentation
- threads = threaded_cpu_work
- assert_equal [false] * THREADS_COUNT, threads.map(&:status)
- counters = Bug::ThreadInstrumentation.counters
- assert_join_counters(counters)
- assert_global_join_counters(counters)
+ def test_single_thread_timeline
+ thread = nil
+ full_timeline = record do
+ thread = Thread.new { 1 + 1 }
+ thread.join
+ end
+ assert_equal %i(started ready resumed suspended exited), timeline_for(thread, full_timeline)
+ ensure
+ thread&.kill
+ end
+
+ def test_thread_pass_single_thread
+ full_timeline = record do
+ Thread.pass
+ end
+ assert_equal [], timeline_for(Thread.current, full_timeline)
+ end
+
+ def test_thread_pass_multi_thread
+ thread = Thread.new do
+ cpu_bound_work(0.5)
+ end
+
+ full_timeline = record do
+ Thread.pass
+ end
+
+ assert_equal %i(suspended ready resumed), timeline_for(Thread.current, full_timeline)
+ ensure
+ thread&.kill
+ thread&.join
+ end
+
+ def test_multi_thread_timeline
+ threads = nil
+ full_timeline = record do
+ threads = threaded_cpu_bound_work(1.0)
+ results = threads.map(&:value)
+ results.each do |r|
+ refute_equal false, r
+ end
+ assert_equal [false] * THREADS_COUNT, threads.map(&:status)
+ end
+
+ threads.each do |thread|
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_operator timeline.count(:suspended), :>=, 1, "Expected threads to yield suspended at least once: #{timeline.inspect}"
+ end
+
+ timeline = timeline_for(Thread.current, full_timeline)
+ assert_consistent_timeline(timeline)
+ ensure
+ threads&.each(&:kill)
+ end
+
+ def test_join_suspends # Bug #18900
+ thread = other_thread = nil
+ full_timeline = record do
+ other_thread = Thread.new { sleep 0.3 }
+ thread = Thread.new { other_thread.join }
+ thread.join
+ end
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
+ ensure
+ other_thread&.kill
+ thread&.kill
+ end
+
+ def test_io_release_gvl
+ r, w = IO.pipe
+ thread = nil
+ full_timeline = record do
+ thread = Thread.new do
+ w.write("Hello\n")
+ end
+ thread.join
+ end
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
+ ensure
+ r&.close
+ w&.close
+ end
+
+ def test_queue_releases_gvl
+ queue1 = Queue.new
+ queue2 = Queue.new
+
+ thread = nil
+
+ full_timeline = record do
+ thread = Thread.new do
+ queue1 << true
+ queue2.pop
+ end
+
+ queue1.pop
+ queue2 << true
+ thread.join
+ end
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
+ end
+
+ def test_blocking_on_ractor
+ assert_ractor(<<-"RUBY", require_relative: "helper", require: "-test-/thread/instrumentation")
+ include ThreadInstrumentation::TestHelper
+
+ ractor = Ractor.new {
+ Ractor.receive # wait until woke
+ Thread.current
+ }
+
+ # Wait for the main thread to block, then wake the ractor
+ Thread.new do
+ while Thread.main.status != "sleep"
+ Thread.pass
+ end
+ ractor.send true
+ end
+
+ full_timeline = record do
+ ractor.value
+ end
+
+ timeline = timeline_for(Thread.current, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(suspended ready resumed), timeline
+ RUBY
+ end
+
+ def test_sleeping_inside_ractor
+ omit "This test is flaky and intermittently failing now on ModGC workflow" if ENV['GITHUB_WORKFLOW'] == 'ModGC'
+
+ assert_ractor(<<-"RUBY", require_relative: "helper", require: "-test-/thread/instrumentation")
+ include ThreadInstrumentation::TestHelper
+
+ thread = nil
+
+ full_timeline = record do
+ thread = Ractor.new{
+ sleep 0.1
+ Thread.current
+ }.value
+ sleep 0.1
+ end
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
+ RUBY
+ end
+
+ def test_thread_blocked_forever_on_mutex
+ mutex = Mutex.new
+ mutex.lock
+ thread = nil
+
+ full_timeline = record do
+ thread = Thread.new do
+ mutex.lock
+ end
+ 10.times { Thread.pass }
+ sleep 0.1
+ end
+
+ mutex.unlock
+ thread.join
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended), timeline
end
- def test_join_counters # Bug #18900
- thr = Thread.new { fib(30) }
- Bug::ThreadInstrumentation.reset_counters
- thr.join
- assert_join_counters(Bug::ThreadInstrumentation.local_counters)
+ def test_thread_blocked_temporarily_on_mutex
+ mutex = Mutex.new
+ mutex.lock
+ thread = nil
+
+ full_timeline = record do
+ thread = Thread.new do
+ mutex.lock
+ end
+ 10.times { Thread.pass }
+ sleep 0.1
+ mutex.unlock
+ 10.times { Thread.pass }
+ sleep 0.1
+ end
+
+ thread.join
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
end
def test_thread_instrumentation_fork_safe
skip "No fork()" unless Process.respond_to?(:fork)
- thread_statuses = counters = nil
+ thread_statuses = full_timeline = nil
IO.popen("-") do |read_pipe|
if read_pipe
thread_statuses = Marshal.load(read_pipe)
- counters = Marshal.load(read_pipe)
+ full_timeline = Marshal.load(read_pipe)
else
- Bug::ThreadInstrumentation.reset_counters
- threads = threaded_cpu_work
+ threads = threaded_cpu_bound_work.each(&:join)
Marshal.dump(threads.map(&:status), STDOUT)
- Marshal.dump(Bug::ThreadInstrumentation.counters, STDOUT)
+ full_timeline = Bug::ThreadInstrumentation.unregister_callback.map { |t, e| [t.to_s, e ] }
+ Marshal.dump(full_timeline, STDOUT)
end
end
assert_predicate $?, :success?
assert_equal [false] * THREADS_COUNT, thread_statuses
- assert_join_counters(counters)
- assert_global_join_counters(counters)
+ thread_names = full_timeline.map(&:first).uniq
+ thread_names.each do |thread_name|
+ assert_consistent_timeline(timeline_for(thread_name, full_timeline))
+ end
end
def test_thread_instrumentation_unregister
- Bug::ThreadInstrumentation::unregister_callback
assert Bug::ThreadInstrumentation::register_and_unregister_callbacks
end
private
- def fib(n = 20)
+ def fib(n = 30)
return n if n <= 1
fib(n-1) + fib(n-2)
end
- def threaded_cpu_work(size = 20)
- THREADS_COUNT.times.map { Thread.new { fib(size) } }.each(&:join)
+ def cpu_bound_work(duration)
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
+ i = 0
+ while deadline > Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ fib(25)
+ i += 1
+ end
+ i > 0 ? i : false
end
- def assert_join_counters(counters)
- counters.each_with_index do |c, i|
- assert_operator c, :>, 0, "Call counters[#{i}]: #{counters.inspect}"
+ def threaded_cpu_bound_work(duration = 0.5)
+ THREADS_COUNT.times.map do
+ Thread.new do
+ cpu_bound_work(duration)
+ end
end
end
- def assert_global_join_counters(counters)
- assert_equal THREADS_COUNT, counters.first
+ def cleanup_threads
+ Thread.list.each do |thread|
+ if thread != Thread.current
+ thread.kill
+ thread.join rescue nil
+ end
+ end
+ assert_equal [Thread.current], Thread.list
end
end
diff --git a/test/-ext-/thread/test_lock_native_thread.rb b/test/-ext-/thread/test_lock_native_thread.rb
new file mode 100644
index 0000000000..b4044b2b93
--- /dev/null
+++ b/test/-ext-/thread/test_lock_native_thread.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: false
+
+require 'envutil'
+
+mn_supported_p = -> do
+ out, *_ = EnvUtil.invoke_ruby([{'RUBY_MN_THREADS' => '1'}, '-v'], '', true)
+ return /\+MN/ =~ out
+end
+
+if mn_supported_p.call
+ # test only on MN threads
+else
+ return
+end
+
+class TestThreadLockNativeThread < Test::Unit::TestCase
+ def test_lock_native_thread
+ omit "LSAN reports memory leak because NT is not freed for MN thread" if Test::Sanitizers.lsan_enabled?
+
+ assert_separately([{'RUBY_MN_THREADS' => '1'}], <<-RUBY)
+ require '-test-/thread/lock_native_thread'
+
+ Thread.new{
+ assert_equal true, Thread.current.lock_native_thread
+ }.join
+
+ # main thread already has DNT
+ assert_equal false, Thread.current.lock_native_thread
+ RUBY
+ end
+
+ def test_lock_native_thread_tls
+ omit "LSAN reports memory leak because NT is not freed for MN thread" if Test::Sanitizers.lsan_enabled?
+
+ assert_separately([{'RUBY_MN_THREADS' => '1'}], <<-RUBY)
+ require '-test-/thread/lock_native_thread'
+ tn = 10
+ ln = 1_000
+
+ ts = tn.times.map{|i|
+ Thread.new(i){|i|
+ Thread.current.set_tls i
+ assert_equal true, Thread.current.lock_native_thread
+
+ ln.times{
+ assert_equal i, Thread.current.get_tls
+ Thread.pass
+ }
+ }
+ }
+ ts.each(&:join)
+ RUBY
+ end
+end
diff --git a/test/-ext-/thread_fd/test_thread_fd_close.rb b/test/-ext-/thread_fd/test_thread_fd_close.rb
deleted file mode 100644
index 1d2ef63635..0000000000
--- a/test/-ext-/thread_fd/test_thread_fd_close.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require '-test-/thread_fd'
-
-class TestThreadFdClose < Test::Unit::TestCase
-
- def test_thread_fd_close
- IO.pipe do |r, w|
- th = Thread.new do
- begin
- assert_raise(IOError) {
- r.read(4)
- }
- ensure
- w.syswrite('done')
- end
- end
- Thread.pass until th.stop?
- IO.thread_fd_close(r.fileno)
- assert_equal 'done', r.read(4)
- th.join
- end
- end
-end
diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb
index 9d1679602a..603fd01fd5 100644
--- a/test/-ext-/tracepoint/test_tracepoint.rb
+++ b/test/-ext-/tracepoint/test_tracepoint.rb
@@ -11,6 +11,7 @@ class TestTracepointObj < Test::Unit::TestCase
def test_tracks_objspace_events
result = EnvUtil.suppress_warning {eval(<<-EOS, nil, __FILE__, __LINE__+1)}
+ # frozen_string_literal: false
Bug.tracepoint_track_objspace_events {
99
'abc'
@@ -65,15 +66,15 @@ class TestTracepointObj < Test::Unit::TestCase
count = 0
hook = proc {count += 1}
def run(hook)
- stress, GC.stress = GC.stress, false
- Bug.after_gc_start_hook = hook
- begin
- GC.stress = true
- 3.times {Object.new}
- ensure
- GC.stress = stress
- Bug.after_gc_start_hook = nil
- end
+ stress, GC.stress = GC.stress, false
+ Bug.after_gc_start_hook = hook
+ begin
+ GC.stress = true
+ 3.times {Object.new}
+ ensure
+ GC.stress = stress
+ Bug.after_gc_start_hook = nil
+ end
end
run(hook)
puts count
@@ -82,7 +83,7 @@ class TestTracepointObj < Test::Unit::TestCase
end
def test_teardown_with_active_GC_end_hook
- assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}')
+ assert_ruby_status([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start')
end
end