summaryrefslogtreecommitdiff
path: root/test/-ext-
diff options
context:
space:
mode:
Diffstat (limited to 'test/-ext-')
-rw-r--r--test/-ext-/arith_seq/test_arith_seq_beg_len_step.rb52
-rw-r--r--test/-ext-/arith_seq/test_arith_seq_extract.rb40
-rw-r--r--test/-ext-/array/test_resize.rb36
-rw-r--r--test/-ext-/array/test_to_ary_concat.rb20
-rw-r--r--test/-ext-/bignum/test_big2str.rb28
-rw-r--r--test/-ext-/bignum/test_bigzero.rb18
-rw-r--r--test/-ext-/bignum/test_div.rb27
-rw-r--r--test/-ext-/bignum/test_mul.rb136
-rw-r--r--test/-ext-/bignum/test_pack.rb396
-rw-r--r--test/-ext-/bignum/test_str2big.rb36
-rw-r--r--test/-ext-/box/test_load_ext.rb97
-rw-r--r--test/-ext-/bug_reporter/test_bug_reporter.rb32
-rw-r--r--test/-ext-/class/test_class2name.rb19
-rw-r--r--test/-ext-/debug/test_debug.rb132
-rw-r--r--test/-ext-/debug/test_profile_frames.rb244
-rw-r--r--test/-ext-/econv/test_append.rb23
-rw-r--r--test/-ext-/eval/test_eval.rb12
-rw-r--r--test/-ext-/exception/test_data_error.rb14
-rw-r--r--test/-ext-/exception/test_enc_raise.rb16
-rw-r--r--test/-ext-/exception/test_ensured.rb32
-rw-r--r--test/-ext-/exception/test_exception_at_throwing.rb18
-rw-r--r--test/-ext-/file/test_stat.rb15
-rw-r--r--test/-ext-/float/test_nextafter.rb65
-rw-r--r--test/-ext-/funcall/test_passing_block.rb65
-rw-r--r--test/-ext-/gvl/test_last_thread.rb21
-rw-r--r--test/-ext-/gvl/test_ubf_async_safe.rb20
-rw-r--r--test/-ext-/hash/test_delete.rb20
-rw-r--r--test/-ext-/integer/test_integer.rb26
-rw-r--r--test/-ext-/integer/test_my_integer.rb36
-rw-r--r--test/-ext-/iseq_load/test_iseq_load.rb158
-rw-r--r--test/-ext-/iter/test_iter_break.rb16
-rw-r--r--test/-ext-/iter/test_yield_block.rb34
-rw-r--r--test/-ext-/load/script.rb2
-rw-r--r--test/-ext-/load/test_dot_dot.rb11
-rw-r--r--test/-ext-/load/test_protect.rb14
-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.rb30
-rw-r--r--test/-ext-/marshal/test_usrmarshal.rb33
-rw-r--r--test/-ext-/method/test_arity.rb38
-rw-r--r--test/-ext-/num2int/test_num2int.rb265
-rw-r--r--test/-ext-/path_to_class/test_path_to_class.rb13
-rw-r--r--test/-ext-/popen_deadlock/test_popen_deadlock.rb36
-rw-r--r--test/-ext-/postponed_job/test_postponed_job.rb36
-rw-r--r--test/-ext-/proc/test_bmethod.rb38
-rw-r--r--test/-ext-/rational/test_rat.rb70
-rw-r--r--test/-ext-/required.rb10
-rw-r--r--test/-ext-/scheduler/test_interrupt_with_scheduler.rb54
-rw-r--r--test/-ext-/st/test_foreach.rb16
-rw-r--r--test/-ext-/st/test_numhash.rb50
-rw-r--r--test/-ext-/st/test_update.rb51
-rw-r--r--test/-ext-/stack/test_stack_overflow.rb55
-rw-r--r--test/-ext-/string/test_capacity.rb80
-rw-r--r--test/-ext-/string/test_coderange.rb60
-rw-r--r--test/-ext-/string/test_cstr.rb168
-rw-r--r--test/-ext-/string/test_ellipsize.rb47
-rw-r--r--test/-ext-/string/test_enc_associate.rb24
-rw-r--r--test/-ext-/string/test_enc_str_buf_cat.rb25
-rw-r--r--test/-ext-/string/test_external_new.rb17
-rw-r--r--test/-ext-/string/test_fstring.rb72
-rw-r--r--test/-ext-/string/test_interned_str.rb17
-rw-r--r--test/-ext-/string/test_modify_expand.rb26
-rw-r--r--test/-ext-/string/test_nofree.rb13
-rw-r--r--test/-ext-/string/test_normalize.rb110
-rw-r--r--test/-ext-/string/test_qsort.rb20
-rw-r--r--test/-ext-/string/test_rb_str_dup.rb18
-rw-r--r--test/-ext-/string/test_set_len.rb84
-rw-r--r--test/-ext-/string/test_too_many_dummy_encodings.rb15
-rw-r--r--test/-ext-/struct/test_data.rb18
-rw-r--r--test/-ext-/struct/test_duplicate.rb22
-rw-r--r--test/-ext-/struct/test_len.rb10
-rw-r--r--test/-ext-/struct/test_member.rb14
-rw-r--r--test/-ext-/symbol/noninterned_name.rb17
-rw-r--r--test/-ext-/symbol/test_inadvertent_creation.rb493
-rw-r--r--test/-ext-/symbol/test_type.rb147
-rw-r--r--test/-ext-/test_abi.rb55
-rw-r--r--test/-ext-/test_bug-14834.rb12
-rw-r--r--test/-ext-/test_bug-3571.rb21
-rw-r--r--test/-ext-/test_bug-5832.rb22
-rw-r--r--test/-ext-/test_ensure_and_callcc.rb40
-rw-r--r--test/-ext-/test_enumerator_kw.rb11
-rw-r--r--test/-ext-/test_notimplement.rb44
-rw-r--r--test/-ext-/test_printf.rb184
-rw-r--r--test/-ext-/test_random.rb45
-rw-r--r--test/-ext-/test_recursion.rb36
-rw-r--r--test/-ext-/test_scan_args.rb259
-rw-r--r--test/-ext-/thread/helper.rb51
-rw-r--r--test/-ext-/thread/test_instrumentation_api.rb291
-rw-r--r--test/-ext-/thread/test_lock_native_thread.rb54
-rw-r--r--test/-ext-/time/test_new.rb44
-rw-r--r--test/-ext-/tracepoint/test_tracepoint.rb89
-rw-r--r--test/-ext-/typeddata/test_typeddata.rb29
-rw-r--r--test/-ext-/vm/test_at_exit.rb19
-rw-r--r--test/-ext-/wait/test_wait.rb36
-rw-r--r--test/-ext-/win32/test_console_attr.rb55
-rw-r--r--test/-ext-/win32/test_dln.rb39
-rw-r--r--test/-ext-/win32/test_fd_setsize.rb25
97 files changed, 5766 insertions, 0 deletions
diff --git a/test/-ext-/arith_seq/test_arith_seq_beg_len_step.rb b/test/-ext-/arith_seq/test_arith_seq_beg_len_step.rb
new file mode 100644
index 0000000000..4320c1f20d
--- /dev/null
+++ b/test/-ext-/arith_seq/test_arith_seq_beg_len_step.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_ArithSeq < Test::Unit::TestCase
+ def test_beg_len_step
+ assert_separately([], <<-"end;") #do
+ require '-test-/arith_seq/beg_len_step'
+
+ r, = Enumerator::ArithmeticSequence.__beg_len_step__([1, 2, 3], 0, 0)
+ assert_equal(false, r)
+
+ r, = Enumerator::ArithmeticSequence.__beg_len_step__([1, 2, 3], 1, 0)
+ assert_equal(false, r)
+
+ r, = Enumerator::ArithmeticSequence.__beg_len_step__([1, 2, 3], 3, 0)
+ assert_equal(false, r)
+
+ r, = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 0, 0)
+ assert_equal(nil, r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 1, 0)
+ assert_equal([true, 1, 0, 1], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 2, 0)
+ assert_equal([true, 1, 1, 1], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 3, 0)
+ assert_equal([true, 1, 2, 1], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 4, 0)
+ assert_equal([true, 1, 3, 1], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 5, 0)
+ assert_equal([true, 1, 3, 1], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__((-10..10).step(2), 24, 0)
+ assert_equal([true, 14, 0, 2], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__((-10..10).step(3), 24, 0)
+ assert_equal([true, 14, 0, 3], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__((-10..10).step(3), 22, 0)
+ assert_equal([true, 12, 0, 3], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__((-10..10).step(-3), 22, 0)
+ assert_equal([true, 10, 3, -3], r)
+
+ r = Enumerator::ArithmeticSequence.__beg_len_step__(1..3, 0, 1)
+ assert_equal([true, 1, 3, 1], r)
+ end;
+ end
+end
diff --git a/test/-ext-/arith_seq/test_arith_seq_extract.rb b/test/-ext-/arith_seq/test_arith_seq_extract.rb
new file mode 100644
index 0000000000..d64fe6a7b5
--- /dev/null
+++ b/test/-ext-/arith_seq/test_arith_seq_extract.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_ArithSeq < Test::Unit::TestCase
+ def test_extract_with_arith_seq
+ assert_separately([], <<-"end;") #do
+ require '-test-/arith_seq/extract'
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__(1.step(10, 2))
+ assert_equal([1, 10, 2, 0, 1], [b, e, s, f, r])
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__((1..10) % 2)
+ assert_equal([1, 10, 2, 0, 1], [b, e, s, f, r])
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__((1...10) % 2)
+ assert_equal([1, 10, 2, 1, 1], [b, e, s, f, r])
+ end;
+ end
+
+ def test_extract_with_range
+ assert_separately([], <<-"end;") #do
+ require '-test-/arith_seq/extract'
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__(1..10)
+ assert_equal([1, 10, 1, 0, 1], [b, e, s, f, r])
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__(1...10)
+ assert_equal([1, 10, 1, 1, 1], [b, e, s, f, r])
+ end;
+ end
+
+ def test_extract_with_others
+ assert_separately([], <<-"end;") #do
+ require '-test-/arith_seq/extract'
+
+ b, e, s, f, r = Enumerator::ArithmeticSequence.__extract__(nil)
+ assert_equal([nil, nil, nil, nil, 0], [b, e, s, f, r])
+ end;
+ end
+end
diff --git a/test/-ext-/array/test_resize.rb b/test/-ext-/array/test_resize.rb
new file mode 100644
index 0000000000..d393620c55
--- /dev/null
+++ b/test/-ext-/array/test_resize.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/array/resize'
+
+class Test_Array < Test::Unit::TestCase
+ using Module.new {
+ refine(Array) {
+ def __resize__(len) ::Bug::Array.__resize__(self, len); end
+ }
+ }
+
+ class TestResize < Test::Unit::TestCase
+ def test_expand
+ feature = '[ruby-dev:42912]'
+ ary = [*1..10]
+ ary.__resize__(10)
+ assert_equal(10, ary.size, feature)
+ assert_equal([*1..10], ary, feature)
+ ary.__resize__(100)
+ assert_equal(100, ary.size, feature)
+ assert_equal([*1..10]+[nil]*90, ary, feature)
+ ary.__resize__(20)
+ assert_equal(20, ary.size, feature)
+ assert_equal([*1..10]+[nil]*10, ary, feature)
+ ary.__resize__(2)
+ assert_equal(2, ary.size, feature)
+ assert_equal([1,2], ary, feature)
+ ary.__resize__(3)
+ assert_equal(3, ary.size, feature)
+ assert_equal([1,2,nil], ary, feature)
+ ary.__resize__(10)
+ assert_equal(10, ary.size, feature)
+ assert_equal([1,2]+[nil]*8, ary, feature)
+ end
+ end
+end
diff --git a/test/-ext-/array/test_to_ary_concat.rb b/test/-ext-/array/test_to_ary_concat.rb
new file mode 100644
index 0000000000..feb1bc1109
--- /dev/null
+++ b/test/-ext-/array/test_to_ary_concat.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/array/to_ary_concat'
+
+class TestConcatStress < Test::Unit::TestCase
+ def setup
+ @stress_level = GC.stress
+ GC.stress = true
+ end
+
+ def teardown
+ GC.stress = @stress_level
+ end
+
+ def test_concat
+ arr = [nil]
+ bar = Bug::Bar.new
+ arr.concat(bar)
+ end
+end
diff --git a/test/-ext-/bignum/test_big2str.rb b/test/-ext-/bignum/test_big2str.rb
new file mode 100644
index 0000000000..88e35a7294
--- /dev/null
+++ b/test/-ext-/bignum/test_big2str.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_Big2str < Test::Unit::TestCase
+
+ SIZEOF_BDIGIT = Bug::Bignum::SIZEOF_BDIGIT
+ BITSPERDIG = Bug::Bignum::BITSPERDIG
+ BDIGMAX = (1 << BITSPERDIG) - 1
+
+ def test_big2str_generic
+ x = 10**1000
+ assert_equal("1" + "0" * 1000, Bug::Bignum.big2str_generic(x, 10))
+ end
+
+ def test_big2str_poweroftwo
+ e = BITSPERDIG*2
+ x = 0b10**e
+ assert_equal("1" + "0" * e, Bug::Bignum.big2str_poweroftwo(x, 2))
+ end
+
+ def test_big2str_gmp
+ x = 10**1000
+ assert_equal("1" + "0" * 1000, Bug::Bignum.big2str_gmp(x, 10))
+ rescue NotImplementedError
+ end
+
+end
diff --git a/test/-ext-/bignum/test_bigzero.rb b/test/-ext-/bignum/test_bigzero.rb
new file mode 100644
index 0000000000..6dfa3486c1
--- /dev/null
+++ b/test/-ext-/bignum/test_bigzero.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_BigZero < Test::Unit::TestCase
+ def test_equal_0
+ bug8204 = '[ruby-core:53893] [Bug #8204]'
+ (0..10).each do |i|
+ assert_equal(0, Bug::Bignum.zero(i), "#{bug8204} Bignum.zero(#{i})")
+ end
+ end
+
+ def test_zero?
+ (0..10).each do |i|
+ assert_equal(true, Bug::Bignum.zero(i).zero?)
+ end
+ end
+end
diff --git a/test/-ext-/bignum/test_div.rb b/test/-ext-/bignum/test_div.rb
new file mode 100644
index 0000000000..0c6f635ae8
--- /dev/null
+++ b/test/-ext-/bignum/test_div.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_Div < Test::Unit::TestCase
+
+ SIZEOF_BDIGIT = Bug::Bignum::SIZEOF_BDIGIT
+ BITSPERDIG = Bug::Bignum::BITSPERDIG
+ BDIGMAX = (1 << BITSPERDIG) - 1
+
+ def test_divrem_normal
+ x = (1 << (BITSPERDIG*2)) | (2 << BITSPERDIG) | 3
+ y = (1 << BITSPERDIG) | 1
+ q = (1 << BITSPERDIG) | 1
+ r = 2
+ assert_equal([q, r], Bug::Bignum.big_divrem_normal(x, y))
+ end
+
+ def test_divrem_gmp
+ x = (1 << (BITSPERDIG*2)) | (2 << BITSPERDIG) | 3
+ y = (1 << BITSPERDIG) | 1
+ q = (1 << BITSPERDIG) | 1
+ r = 2
+ assert_equal([q, r], Bug::Bignum.big_divrem_gmp(x, y))
+ rescue NotImplementedError
+ end
+end
diff --git a/test/-ext-/bignum/test_mul.rb b/test/-ext-/bignum/test_mul.rb
new file mode 100644
index 0000000000..95186bbf76
--- /dev/null
+++ b/test/-ext-/bignum/test_mul.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_Mul < Test::Unit::TestCase
+
+ SIZEOF_BDIGIT = Bug::Bignum::SIZEOF_BDIGIT
+ BITSPERDIG = Bug::Bignum::BITSPERDIG
+ BDIGMAX = (1 << BITSPERDIG) - 1
+
+ def test_mul_normal
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG) | 1
+ z = (1 << (BITSPERDIG*2)) | (2 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_mul_normal(x, y))
+ end
+
+ def test_mul_normal_zero_in_x
+ x = (1 << (2*BITSPERDIG)) | 1
+ y = (1 << BITSPERDIG) | 1
+ z = (1 << (BITSPERDIG*3)) | (1 << (BITSPERDIG*2)) | (1 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_mul_normal(x, y))
+ end
+
+ def test_mul_normal_zero_in_y
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << (2*BITSPERDIG)) | 1
+ z = (1 << (BITSPERDIG*3)) | (1 << (BITSPERDIG*2)) | (1 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_mul_normal(x, y))
+ end
+
+ def test_mul_normal_max_max
+ x = (1 << (2*BITSPERDIG)) - 1
+ y = (1 << (2*BITSPERDIG)) - 1
+ z = (1 << (4*BITSPERDIG)) - (1 << (2*BITSPERDIG+1)) + 1
+ assert_equal(z, Bug::Bignum.big_mul_normal(x, y))
+ end
+
+ def test_sq_fast
+ x = (1 << BITSPERDIG) | 1
+ z = (1 << 2*BITSPERDIG) | (2 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_sq_fast(x))
+ end
+
+ def test_sq_fast_max2
+ x = (BDIGMAX << BITSPERDIG) | BDIGMAX
+ assert_equal(Bug::Bignum.big_mul_normal(x, x), Bug::Bignum.big_sq_fast(x))
+ end
+
+ def test_sq_fast_zero_in_middle
+ x = (BDIGMAX << 2*BITSPERDIG) | BDIGMAX
+ assert_equal(Bug::Bignum.big_mul_normal(x, x), Bug::Bignum.big_sq_fast(x))
+ end
+
+ def test_mul_balance
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG) | 1
+ z = (1 << (BITSPERDIG*2)) | (2 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_mul_balance(x, y))
+ end
+
+ def test_mul_balance_2x16
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG*16) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_balance(x, y))
+ end
+
+ def test_mul_balance_2x17
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG*17) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_balance(x, y))
+ end
+
+ def test_mul_karatsuba
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG) | 1
+ z = (1 << (BITSPERDIG*2)) | (2 << BITSPERDIG) | 1
+ assert_equal(z, Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_odd_y
+ x = (1 << BITSPERDIG) | 1
+ y = (1 << (2*BITSPERDIG)) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_odd_xy
+ x = (1 << (2*BITSPERDIG)) | 1
+ y = (1 << (2*BITSPERDIG)) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_x1_gt_x0
+ x = (2 << BITSPERDIG) | 1
+ y = (1 << BITSPERDIG) | 2
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_y1_gt_y0
+ x = (1 << BITSPERDIG) | 2
+ y = (2 << BITSPERDIG) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_x1_gt_x0_and_y1_gt_y0
+ x = (2 << BITSPERDIG) | 1
+ y = (2 << BITSPERDIG) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_carry2
+ x = (1 << BITSPERDIG) | BDIGMAX
+ y = (1 << BITSPERDIG) | BDIGMAX
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_karatsuba_borrow
+ x = (BDIGMAX << BITSPERDIG) | 1
+ y = (BDIGMAX << BITSPERDIG) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_karatsuba(x, y))
+ end
+
+ def test_mul_toom3
+ x = (1 << 2*BITSPERDIG) | (1 << BITSPERDIG) | 1
+ y = (1 << 2*BITSPERDIG) | (1 << BITSPERDIG) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_toom3(x, y))
+ end
+
+ def test_mul_gmp
+ x = (1 << 2*BITSPERDIG) | (1 << BITSPERDIG) | 1
+ y = (1 << 2*BITSPERDIG) | (1 << BITSPERDIG) | 1
+ assert_equal(Bug::Bignum.big_mul_normal(x, y), Bug::Bignum.big_mul_gmp(x, y))
+ rescue NotImplementedError
+ end
+
+end
diff --git a/test/-ext-/bignum/test_pack.rb b/test/-ext-/bignum/test_pack.rb
new file mode 100644
index 0000000000..b7aea9ab68
--- /dev/null
+++ b/test/-ext-/bignum/test_pack.rb
@@ -0,0 +1,396 @@
+# coding: ASCII-8BIT
+# frozen_string_literal: false
+
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_Pack < Test::Unit::TestCase
+
+ MSWORD_FIRST = Bug::Bignum::INTEGER_PACK_MSWORD_FIRST
+ LSWORD_FIRST = Bug::Bignum::INTEGER_PACK_LSWORD_FIRST
+ MSBYTE_FIRST = Bug::Bignum::INTEGER_PACK_MSBYTE_FIRST
+ LSBYTE_FIRST = Bug::Bignum::INTEGER_PACK_LSBYTE_FIRST
+ NATIVE_BYTE_ORDER = Bug::Bignum::INTEGER_PACK_NATIVE_BYTE_ORDER
+ TWOCOMP = Bug::Bignum::INTEGER_PACK_2COMP
+ LITTLE_ENDIAN = Bug::Bignum::INTEGER_PACK_LITTLE_ENDIAN
+ BIG_ENDIAN = Bug::Bignum::INTEGER_PACK_BIG_ENDIAN
+ NEGATIVE = Bug::Bignum::INTEGER_PACK_NEGATIVE
+ GENERIC = Bug::Bignum::INTEGER_PACK_FORCE_GENERIC_IMPLEMENTATION
+
+ def test_pack_zero
+ assert_equal([0, ""], Bug::Bignum.test_pack(0, 0, 1, 0, BIG_ENDIAN))
+ end
+
+ def test_pack_argument_check
+ assert_raise(ArgumentError) { Bug::Bignum.test_pack_raw(0, "", 2, 1, 0, MSBYTE_FIRST) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_pack_raw(0, "", 0, 1, 0, MSWORD_FIRST) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_pack_raw(0, "", 0, 0, 0, BIG_ENDIAN) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_pack_raw(0, "", 0, 1, 8, BIG_ENDIAN) }
+
+ # assume sizeof(ssize_t) == sizeof(intptr_t)
+ assert_raise(ArgumentError) { Bug::Bignum.test_pack_raw(0, "", 1 << ([""].pack("p").length * 8 - 1), 0, BIG_ENDIAN) }
+ end
+
+ def test_pack_wordsize
+ assert_equal([1, "\x01"], Bug::Bignum.test_pack(1, 1, 1, 0, BIG_ENDIAN))
+ assert_equal([1, "\x00\x01"], Bug::Bignum.test_pack(1, 1, 2, 0, BIG_ENDIAN))
+ assert_equal([1, "\x00\x00\x01"], Bug::Bignum.test_pack(1, 1, 3, 0, BIG_ENDIAN))
+ assert_equal([1, "\x01"], Bug::Bignum.test_pack(1, 1, 1, 0, LITTLE_ENDIAN))
+ assert_equal([1, "\x01\x00"], Bug::Bignum.test_pack(1, 1, 2, 0, LITTLE_ENDIAN))
+ assert_equal([1, "\x01\x00\x00"], Bug::Bignum.test_pack(1, 1, 3, 0, LITTLE_ENDIAN))
+ end
+
+ def test_pack_fixed_buffer
+ assert_equal([0, "\x00\x00"], Bug::Bignum.test_pack(0, 2, 1, 0, BIG_ENDIAN))
+ assert_equal([1, "\x00\x01"], Bug::Bignum.test_pack(0x01, 2, 1, 0, BIG_ENDIAN))
+ assert_equal([1, "\x02\x01"], Bug::Bignum.test_pack(0x0201, 2, 1, 0, BIG_ENDIAN))
+ assert_equal([2, "\x02\x01"], Bug::Bignum.test_pack(0x030201, 2, 1, 0, BIG_ENDIAN))
+ assert_equal([2, "\x02\x01"], Bug::Bignum.test_pack(0x04030201, 2, 1, 0, BIG_ENDIAN))
+ assert_equal([0, "\x00\x00"], Bug::Bignum.test_pack(0, 2, 1, 0, LITTLE_ENDIAN))
+ assert_equal([1, "\x01\x00"], Bug::Bignum.test_pack(0x01, 2, 1, 0, LITTLE_ENDIAN))
+ assert_equal([1, "\x01\x02"], Bug::Bignum.test_pack(0x0201, 2, 1, 0, LITTLE_ENDIAN))
+ assert_equal([2, "\x01\x02"], Bug::Bignum.test_pack(0x030201, 2, 1, 0, LITTLE_ENDIAN))
+ assert_equal([2, "\x01\x02"], Bug::Bignum.test_pack(0x04030201, 2, 1, 0, LITTLE_ENDIAN))
+ end
+
+ def test_pack_wordorder_and_endian
+ assert_equal([1, "\x12\x34\x56\x78"], Bug::Bignum.test_pack(0x12345678, 2, 2, 0, MSWORD_FIRST|MSBYTE_FIRST))
+ assert_equal([1, "\x34\x12\x78\x56"], Bug::Bignum.test_pack(0x12345678, 2, 2, 0, MSWORD_FIRST|LSBYTE_FIRST))
+ assert_equal([1, "\x56\x78\x12\x34"], Bug::Bignum.test_pack(0x12345678, 2, 2, 0, LSWORD_FIRST|MSBYTE_FIRST))
+ assert_equal([1, "\x78\x56\x34\x12"], Bug::Bignum.test_pack(0x12345678, 2, 2, 0, LSWORD_FIRST|LSBYTE_FIRST))
+ end
+
+ def test_pack_native_endian
+ assert_equal([1, [0x1234].pack("S!")], Bug::Bignum.test_pack(0x1234, 1, 2, 0, MSWORD_FIRST|NATIVE_BYTE_ORDER))
+ end
+
+ def test_pack_nail
+ assert_equal([1, "\x01\x00\x00\x00\x01\x01"], Bug::Bignum.test_pack(0b100011, 6, 1, 7, BIG_ENDIAN))
+ assert_equal([1, "\x01\x02\x03\x04\x05\x06\x07\x08"], Bug::Bignum.test_pack(0x12345678, 8, 1, 4, BIG_ENDIAN))
+ assert_equal([1, "\x00\x12\x00\x34\x00\x56\x00\x78"], Bug::Bignum.test_pack(0x12345678, 4, 2, 8, BIG_ENDIAN))
+ end
+
+ def test_pack_overflow
+ assert_equal([-2, "\x1"], Bug::Bignum.test_pack((-0x11), 1, 1, 4, BIG_ENDIAN))
+ assert_equal([-2, "\x0"], Bug::Bignum.test_pack((-0x10), 1, 1, 4, BIG_ENDIAN))
+ assert_equal([-1, "\xF"], Bug::Bignum.test_pack((-0x0F), 1, 1, 4, BIG_ENDIAN))
+ assert_equal([+1, "\xF"], Bug::Bignum.test_pack((+0x0F), 1, 1, 4, BIG_ENDIAN))
+ assert_equal([+2, "\x0"], Bug::Bignum.test_pack((+0x10), 1, 1, 4, BIG_ENDIAN))
+ assert_equal([+2, "\x1"], Bug::Bignum.test_pack((+0x11), 1, 1, 4, BIG_ENDIAN))
+
+ assert_equal([-2, "\x01"], Bug::Bignum.test_pack((-0x101), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([-2, "\x00"], Bug::Bignum.test_pack((-0x100), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([-1, "\xFF"], Bug::Bignum.test_pack((-0x0FF), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([+1, "\xFF"], Bug::Bignum.test_pack((+0x0FF), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x00"], Bug::Bignum.test_pack((+0x100), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x01"], Bug::Bignum.test_pack((+0x101), 1, 1, 0, BIG_ENDIAN))
+
+ assert_equal([-2, "\x00\x00\x00\x00\x00\x00\x00\x01"], Bug::Bignum.test_pack((-0x10000000000000001), 2, 4, 0, BIG_ENDIAN))
+ assert_equal([-2, "\x00\x00\x00\x00\x00\x00\x00\x00"], Bug::Bignum.test_pack((-0x10000000000000000), 2, 4, 0, BIG_ENDIAN))
+ assert_equal([-1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"], Bug::Bignum.test_pack((-0x0FFFFFFFFFFFFFFFF), 2, 4, 0, BIG_ENDIAN))
+ assert_equal([+1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"], Bug::Bignum.test_pack((+0x0FFFFFFFFFFFFFFFF), 2, 4, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x00\x00\x00\x00\x00\x00\x00\x00"], Bug::Bignum.test_pack((+0x10000000000000000), 2, 4, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x00\x00\x00\x00\x00\x00\x00\x01"], Bug::Bignum.test_pack((+0x10000000000000001), 2, 4, 0, BIG_ENDIAN))
+
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ n = 256**w
+ assert_equal([-2, "\x00"*(w-1)+"\x01"], Bug::Bignum.test_pack((-n-1), numwords, wordsize, 0, BIG_ENDIAN))
+ assert_equal([-2, "\x00"*w], Bug::Bignum.test_pack((-n ), numwords, wordsize, 0, BIG_ENDIAN))
+ assert_equal([-1, "\xFF"*w], Bug::Bignum.test_pack((-n+1), numwords, wordsize, 0, BIG_ENDIAN))
+ assert_equal([+1, "\xFF"*w], Bug::Bignum.test_pack((+n-1), numwords, wordsize, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x00"*w], Bug::Bignum.test_pack((+n ), numwords, wordsize, 0, BIG_ENDIAN))
+ assert_equal([+2, "\x00"*(w-1)+"\x01"], Bug::Bignum.test_pack((+n+1), numwords, wordsize, 0, BIG_ENDIAN))
+ }
+ }
+
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ n = 256**w
+ assert_equal([-2, "\x01"+"\x00"*(w-1)], Bug::Bignum.test_pack((-n-1), numwords, wordsize, 0, LITTLE_ENDIAN))
+ assert_equal([-2, "\x00"*w], Bug::Bignum.test_pack((-n ), numwords, wordsize, 0, LITTLE_ENDIAN))
+ assert_equal([-1, "\xFF"*w], Bug::Bignum.test_pack((-n+1), numwords, wordsize, 0, LITTLE_ENDIAN))
+ assert_equal([+1, "\xFF"*w], Bug::Bignum.test_pack((+n-1), numwords, wordsize, 0, LITTLE_ENDIAN))
+ assert_equal([+2, "\x00"*w], Bug::Bignum.test_pack((+n ), numwords, wordsize, 0, LITTLE_ENDIAN))
+ assert_equal([+2, "\x01"+"\x00"*(w-1)], Bug::Bignum.test_pack((+n+1), numwords, wordsize, 0, LITTLE_ENDIAN))
+ }
+ }
+ end
+
+ def test_pack_sign
+ assert_equal([-1, "\x01"], Bug::Bignum.test_pack((-1), 1, 1, 0, BIG_ENDIAN))
+ assert_equal([-1, "\x80\x70\x60\x50\x40\x30\x20\x10"], Bug::Bignum.test_pack((-0x8070605040302010), 8, 1, 0, BIG_ENDIAN))
+ end
+
+ def test_pack_orders
+ [MSWORD_FIRST, LSWORD_FIRST].each {|word_order|
+ [MSBYTE_FIRST, LSBYTE_FIRST, NATIVE_BYTE_ORDER].each {|byte_order|
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ n = 0;
+ 0.upto(w) {|i|
+ n |= ((i+1) % 256) << (i*8)
+ }
+ assert_equal(Bug::Bignum.test_pack(n, numwords, wordsize, 0, word_order|byte_order|GENERIC),
+ Bug::Bignum.test_pack(n, numwords, wordsize, 0, word_order|byte_order),
+ "#{'%#x' % n}.test_pack(#{numwords}, #{wordsize}, 0, #{'%#x' % (word_order|byte_order)})")
+ }
+ }
+ }
+ }
+ end
+
+ def test_pack2comp_zero
+ assert_equal([0, ""], Bug::Bignum.test_pack(0, 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ end
+
+ def test_pack2comp_emptybuf
+ assert_equal([-2, ""], Bug::Bignum.test_pack((-3), 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-2, ""], Bug::Bignum.test_pack((-2), 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, ""], Bug::Bignum.test_pack((-1), 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([ 0, ""], Bug::Bignum.test_pack(0, 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, ""], Bug::Bignum.test_pack(1, 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, ""], Bug::Bignum.test_pack(2, 0, 1, 0, TWOCOMP|BIG_ENDIAN))
+ end
+
+ def test_pack2comp_nearly_zero
+ assert_equal([-1, "\xFE"], Bug::Bignum.test_pack((-2), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\xFF"], Bug::Bignum.test_pack((-1), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([ 0, "\x00"], Bug::Bignum.test_pack(0, 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\x01"], Bug::Bignum.test_pack(1, 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\x02"], Bug::Bignum.test_pack(2, 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ end
+
+ def test_pack2comp_overflow
+ assert_equal([-2, "\xF"], Bug::Bignum.test_pack((-0x11), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x0"], Bug::Bignum.test_pack((-0x10), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x1"], Bug::Bignum.test_pack((-0x0F), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\xF"], Bug::Bignum.test_pack((+0x0F), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x0"], Bug::Bignum.test_pack((+0x10), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x1"], Bug::Bignum.test_pack((+0x11), 1, 1, 4, TWOCOMP|BIG_ENDIAN))
+
+ assert_equal([-2, "\xFF"], Bug::Bignum.test_pack((-0x101), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x00"], Bug::Bignum.test_pack((-0x100), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x01"], Bug::Bignum.test_pack((-0x0FF), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\xFF"], Bug::Bignum.test_pack((+0x0FF), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x00"], Bug::Bignum.test_pack((+0x100), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x01"], Bug::Bignum.test_pack((+0x101), 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+
+ assert_equal([-2, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"], Bug::Bignum.test_pack((-0x10000000000000001), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x00\x00\x00\x00\x00\x00\x00\x00"], Bug::Bignum.test_pack((-0x10000000000000000), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x00\x00\x00\x00\x00\x00\x00\x01"], Bug::Bignum.test_pack((-0x0FFFFFFFFFFFFFFFF), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"], Bug::Bignum.test_pack((+0x0FFFFFFFFFFFFFFFF), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x00\x00\x00\x00\x00\x00\x00\x00"], Bug::Bignum.test_pack((+0x10000000000000000), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x00\x00\x00\x00\x00\x00\x00\x01"], Bug::Bignum.test_pack((+0x10000000000000001), 2, 4, 0, TWOCOMP|BIG_ENDIAN))
+
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ n = 256**w
+ assert_equal([-2, "\xFF"*w ], Bug::Bignum.test_pack((-n-1), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x00"*w], Bug::Bignum.test_pack((-n ), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([-1, "\x00"*(w-1)+"\x01"], Bug::Bignum.test_pack((-n+1), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+1, "\xFF"*w], Bug::Bignum.test_pack((+n-1), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x00"*w], Bug::Bignum.test_pack((+n ), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal([+2, "\x00"*(w-1)+"\x01"], Bug::Bignum.test_pack((+n+1), numwords, wordsize, 0, TWOCOMP|BIG_ENDIAN))
+ }
+ }
+
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ n = 256**w
+ assert_equal([-2, "\xFF"*w ], Bug::Bignum.test_pack((-n-1), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ assert_equal([-1, "\x00"*w], Bug::Bignum.test_pack((-n ), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ assert_equal([-1, "\x01"+"\x00"*(w-1)], Bug::Bignum.test_pack((-n+1), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ assert_equal([+1, "\xFF"*w], Bug::Bignum.test_pack((+n-1), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ assert_equal([+2, "\x00"*w], Bug::Bignum.test_pack((+n ), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ assert_equal([+2, "\x01"+"\x00"*(w-1)], Bug::Bignum.test_pack((+n+1), numwords, wordsize, 0, TWOCOMP|LITTLE_ENDIAN))
+ }
+ }
+
+ 2.upto(16) {|wordsize|
+ w = wordsize
+ b = 8*wordsize-1
+ n = 2**b
+ assert_equal([-2, "\x7F"+"\xFF"*(w-2)+"\xFF"], Bug::Bignum.test_pack((-n-1), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ assert_equal([-1, "\x00"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((-n ), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ assert_equal([-1, "\x00"+"\x00"*(w-2)+"\x01"], Bug::Bignum.test_pack((-n+1), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ assert_equal([+1, "\x7F"+"\xFF"*(w-2)+"\xFF"], Bug::Bignum.test_pack((+n-1), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ assert_equal([+2, "\x00"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((+n ), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ assert_equal([+2, "\x00"+"\x00"*(w-2)+"\x01"], Bug::Bignum.test_pack((+n+1), 1, wordsize, 1, TWOCOMP|MSBYTE_FIRST))
+ }
+
+ 2.upto(16) {|wordsize|
+ w = wordsize
+ b = 8*wordsize-1
+ n = 2**b
+ assert_equal([-2, "\xFF"+"\xFF"*(w-2)+"\x7F"], Bug::Bignum.test_pack((-n-1), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ assert_equal([-1, "\x00"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((-n ), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ assert_equal([-1, "\x01"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((-n+1), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ assert_equal([+1, "\xFF"+"\xFF"*(w-2)+"\x7F"], Bug::Bignum.test_pack((+n-1), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ assert_equal([+2, "\x00"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((+n ), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ assert_equal([+2, "\x01"+"\x00"*(w-2)+"\x00"], Bug::Bignum.test_pack((+n+1), 1, wordsize, 1, TWOCOMP|LSBYTE_FIRST))
+ }
+
+ end
+
+ def test_unpack_zero
+ assert_equal(0, Bug::Bignum.test_unpack("", 0, 1, 0, BIG_ENDIAN))
+ end
+
+ def test_unpack_argument_check
+ assert_raise(ArgumentError) { Bug::Bignum.test_unpack("x", 2, 1, 0, MSBYTE_FIRST) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_unpack("x", 1, 1, 0, MSWORD_FIRST) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_unpack("x", 1, 0, 0, BIG_ENDIAN) }
+ assert_raise(ArgumentError) { Bug::Bignum.test_unpack("x", 1, 1, 8, BIG_ENDIAN) }
+
+ # assume sizeof(ssize_t) == sizeof(intptr_t)
+ assert_raise(ArgumentError) { Bug::Bignum.test_unpack("x", 1, 1 << ([""].pack("p").length * 8 - 1), 0, BIG_ENDIAN) }
+ end
+
+ def test_unpack_wordsize
+ assert_equal(1, Bug::Bignum.test_unpack("\x01", 1, 1, 0, BIG_ENDIAN))
+ assert_equal(1, Bug::Bignum.test_unpack("\x00\x01", 1, 2, 0, BIG_ENDIAN))
+ assert_equal(1, Bug::Bignum.test_unpack("\x00\x00\x01", 1, 3, 0, BIG_ENDIAN))
+ assert_equal(1, Bug::Bignum.test_unpack("\x01", 1, 1, 0, LITTLE_ENDIAN))
+ assert_equal(1, Bug::Bignum.test_unpack("\x01\x00", 1, 2, 0, LITTLE_ENDIAN))
+ assert_equal(1, Bug::Bignum.test_unpack("\x01\x00\x00", 1, 3, 0, LITTLE_ENDIAN))
+ end
+
+ def test_unpack_wordorder_and_endian
+ assert_equal(0x01020304, Bug::Bignum.test_unpack("\x01\x02\x03\x04", 2, 2, 0, MSWORD_FIRST|MSBYTE_FIRST))
+ assert_equal(0x02010403, Bug::Bignum.test_unpack("\x01\x02\x03\x04", 2, 2, 0, MSWORD_FIRST|LSBYTE_FIRST))
+ assert_equal(0x03040102, Bug::Bignum.test_unpack("\x01\x02\x03\x04", 2, 2, 0, LSWORD_FIRST|MSBYTE_FIRST))
+ assert_equal(0x04030201, Bug::Bignum.test_unpack("\x01\x02\x03\x04", 2, 2, 0, LSWORD_FIRST|LSBYTE_FIRST))
+ end
+
+ def test_unpack_native_endian
+ assert_equal("\x12\x34".unpack("S!")[0], Bug::Bignum.test_unpack("\x12\x34", 1, 2, 0, MSWORD_FIRST|NATIVE_BYTE_ORDER))
+ end
+
+ def test_unpack_nail
+ assert_equal(0b100011, Bug::Bignum.test_unpack("\x01\x00\x00\x00\x01\x01", 6, 1, 7, BIG_ENDIAN))
+ assert_equal(0x12345678, Bug::Bignum.test_unpack("\x01\x02\x03\x04\x05\x06\x07\x08", 8, 1, 4, BIG_ENDIAN))
+ assert_equal(0x12345678, Bug::Bignum.test_unpack("\x00\x12\x00\x34\x00\x56\x00\x78", 4, 2, 8, BIG_ENDIAN))
+ end
+
+ def test_unpack_sign
+ assert_equal(-1, Bug::Bignum.test_unpack("\x01", 1, 1, 0, BIG_ENDIAN|NEGATIVE))
+ assert_equal(-0x8070605040302010, Bug::Bignum.test_unpack("\x80\x70\x60\x50\x40\x30\x20\x10", 8, 1, 0, BIG_ENDIAN|NEGATIVE))
+ end
+
+ def test_unpack_orders
+ [MSWORD_FIRST, LSWORD_FIRST].each {|word_order|
+ [MSBYTE_FIRST, LSBYTE_FIRST, NATIVE_BYTE_ORDER].each {|byte_order|
+ 1.upto(16) {|wordsize|
+ 1.upto(20) {|numwords|
+ w = numwords*wordsize
+ ary = []
+ 0.upto(w) {|i|
+ ary << ((i+1) % 256);
+ }
+ str = ary.pack("C*")
+ flags = word_order|byte_order
+ assert_equal(Bug::Bignum.test_unpack(str, numwords, wordsize, 0, flags|GENERIC),
+ Bug::Bignum.test_unpack(str, numwords, wordsize, 0, flags),
+ "Bug::Bignum.test_unpack(#{str.dump}, #{numwords}, #{wordsize}, 0, #{'%#x' % flags})")
+ }
+ }
+ }
+ }
+ end
+
+ def test_unpack2comp_single_byte
+ assert_equal(-128, Bug::Bignum.test_unpack("\x80", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( -2, Bug::Bignum.test_unpack("\xFE", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( -1, Bug::Bignum.test_unpack("\xFF", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( 0, Bug::Bignum.test_unpack("\x00", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( 1, Bug::Bignum.test_unpack("\x01", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( 2, Bug::Bignum.test_unpack("\x02", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal( 127, Bug::Bignum.test_unpack("\x7F", 1, 1, 0, TWOCOMP|BIG_ENDIAN))
+ end
+
+ def test_unpack2comp_sequence_of_ff
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*2, 2, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*3, 3, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*4, 4, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*5, 5, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*6, 6, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*7, 7, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*8, 8, 1, 0, TWOCOMP|BIG_ENDIAN))
+ assert_equal(-1, Bug::Bignum.test_unpack("\xFF"*9, 9, 1, 0, TWOCOMP|BIG_ENDIAN))
+ end
+
+ def test_unpack2comp_negative_single_byte
+ assert_equal(-256, Bug::Bignum.test_unpack("\x00", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal(-255, Bug::Bignum.test_unpack("\x01", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal(-254, Bug::Bignum.test_unpack("\x02", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal(-129, Bug::Bignum.test_unpack("\x7F", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal(-128, Bug::Bignum.test_unpack("\x80", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal( -2, Bug::Bignum.test_unpack("\xFE", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ assert_equal( -1, Bug::Bignum.test_unpack("\xFF", 1, 1, 0, TWOCOMP|BIG_ENDIAN|NEGATIVE))
+ end
+
+ def test_unpack2comp_negative_zero
+ 0.upto(100) {|n|
+ str = "\x00"*n
+ flags = TWOCOMP|BIG_ENDIAN|NEGATIVE
+ assert_equal(-(256**n), Bug::Bignum.test_unpack(str, n, 1, 0, flags))
+ flags = TWOCOMP|LITTLE_ENDIAN|NEGATIVE
+ assert_equal(-(256**n), Bug::Bignum.test_unpack(str, n, 1, 0, flags),
+ "Bug::Bignum.test_unpack(#{str.dump}, #{n}, 1, 0, #{'%#x' % flags})")
+ }
+ end
+
+ def test_numbits_2comp
+ assert_equal(4, Bug::Bignum.test_numbits_2comp_without_sign(-9))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(-8))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(-7))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(-6))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(-5))
+ assert_equal(2, Bug::Bignum.test_numbits_2comp_without_sign(-4))
+ assert_equal(2, Bug::Bignum.test_numbits_2comp_without_sign(-3))
+ assert_equal(1, Bug::Bignum.test_numbits_2comp_without_sign(-2))
+ assert_equal(0, Bug::Bignum.test_numbits_2comp_without_sign(-1))
+ assert_equal(0, Bug::Bignum.test_numbits_2comp_without_sign(0))
+ assert_equal(1, Bug::Bignum.test_numbits_2comp_without_sign(1))
+ assert_equal(2, Bug::Bignum.test_numbits_2comp_without_sign(2))
+ assert_equal(2, Bug::Bignum.test_numbits_2comp_without_sign(3))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(4))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(5))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(6))
+ assert_equal(3, Bug::Bignum.test_numbits_2comp_without_sign(7))
+ assert_equal(4, Bug::Bignum.test_numbits_2comp_without_sign(8))
+ assert_equal(4, Bug::Bignum.test_numbits_2comp_without_sign(9))
+ end
+
+ def test_numbytes_2comp
+ assert_equal(6, Bug::Bignum.test_numbytes_2comp_with_sign(-0x8000000001))
+ assert_equal(5, Bug::Bignum.test_numbytes_2comp_with_sign(-0x8000000000))
+ assert_equal(5, Bug::Bignum.test_numbytes_2comp_with_sign(-0x80000001))
+ assert_equal(4, Bug::Bignum.test_numbytes_2comp_with_sign(-0x80000000))
+ assert_equal(4, Bug::Bignum.test_numbytes_2comp_with_sign(-0x800001))
+ assert_equal(3, Bug::Bignum.test_numbytes_2comp_with_sign(-0x800000))
+ assert_equal(3, Bug::Bignum.test_numbytes_2comp_with_sign(-0x8001))
+ assert_equal(2, Bug::Bignum.test_numbytes_2comp_with_sign(-0x8000))
+ assert_equal(2, Bug::Bignum.test_numbytes_2comp_with_sign(-0x81))
+ assert_equal(1, Bug::Bignum.test_numbytes_2comp_with_sign(-0x80))
+ assert_equal(1, Bug::Bignum.test_numbytes_2comp_with_sign(-1))
+ assert_equal(1, Bug::Bignum.test_numbytes_2comp_with_sign(0))
+ assert_equal(1, Bug::Bignum.test_numbytes_2comp_with_sign(1))
+ assert_equal(1, Bug::Bignum.test_numbytes_2comp_with_sign(0x7f))
+ assert_equal(2, Bug::Bignum.test_numbytes_2comp_with_sign(0x80))
+ assert_equal(2, Bug::Bignum.test_numbytes_2comp_with_sign(0x7fff))
+ assert_equal(3, Bug::Bignum.test_numbytes_2comp_with_sign(0x8000))
+ assert_equal(3, Bug::Bignum.test_numbytes_2comp_with_sign(0x7fffff))
+ assert_equal(4, Bug::Bignum.test_numbytes_2comp_with_sign(0x800000))
+ assert_equal(4, Bug::Bignum.test_numbytes_2comp_with_sign(0x7fffffff))
+ assert_equal(5, Bug::Bignum.test_numbytes_2comp_with_sign(0x80000000))
+ assert_equal(5, Bug::Bignum.test_numbytes_2comp_with_sign(0x7fffffffff))
+ assert_equal(6, Bug::Bignum.test_numbytes_2comp_with_sign(0x8000000000))
+ end
+end
diff --git a/test/-ext-/bignum/test_str2big.rb b/test/-ext-/bignum/test_str2big.rb
new file mode 100644
index 0000000000..d27e4d23f7
--- /dev/null
+++ b/test/-ext-/bignum/test_str2big.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/bignum"
+
+class TestBignum_Str2big < Test::Unit::TestCase
+
+ SIZEOF_BDIGIT = Bug::Bignum::SIZEOF_BDIGIT
+ BITSPERDIG = Bug::Bignum::BITSPERDIG
+ BDIGMAX = (1 << BITSPERDIG) - 1
+
+ def test_str2big_poweroftwo
+ s = "1" + "0" * 1000
+ n = 16 ** 1000
+ assert_equal(n, Bug::Bignum.str2big_poweroftwo(s, 16, true))
+ end
+
+ def test_str2big_normal
+ s = "1" + "0" * 1000
+ n = 10 ** 1000
+ assert_equal(n, Bug::Bignum.str2big_normal(s, 10, true))
+ end
+
+ def test_str2big_karatsuba
+ s = "1" + "0" * 1000
+ n = 10 ** 1000
+ assert_equal(n, Bug::Bignum.str2big_karatsuba(s, 10, true))
+ end
+
+ def test_str2big_gmp
+ s = "1" + "0" * 1000
+ n = 10 ** 1000
+ assert_equal(n, Bug::Bignum.str2big_gmp(s, 10, true))
+ rescue NotImplementedError
+ end
+
+end
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
new file mode 100644
index 0000000000..83fdba2282
--- /dev/null
+++ b/test/-ext-/bug_reporter/test_bug_reporter.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tmpdir'
+require_relative '../../lib/jit_support'
+require_relative '../../lib/parser_support'
+
+class TestBugReporter < Test::Unit::TestCase
+ def test_bug_reporter_add
+ description = RUBY_DESCRIPTION
+ description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess?
+ expected_stderr = [
+ :*,
+ /\[BUG\]\sSegmentation\sfault.*\n/,
+ /#{ Regexp.quote(description) }\n\n/,
+ :*,
+ /Sample bug reporter: 12345/,
+ :*
+ ]
+ tmpdir = Dir.mktmpdir
+
+ no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
+ 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
+ FileUtils.rm_rf(tmpdir) if tmpdir
+ end
+end
diff --git a/test/-ext-/class/test_class2name.rb b/test/-ext-/class/test_class2name.rb
new file mode 100644
index 0000000000..e61964d9eb
--- /dev/null
+++ b/test/-ext-/class/test_class2name.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/class"
+
+class Test_Class < Test::Unit::TestCase
+ class Test_Class2Name < superclass
+ def test_toplevel_class
+ assert_equal("Object", Bug::Class.class2name(::Object))
+ end
+
+ def test_toplevel_module
+ assert_equal("Kernel", Bug::Class.class2name(::Kernel))
+ end
+
+ def test_singleton_class
+ assert_equal("Object", Bug::Class.class2name(::Object.new.singleton_class))
+ end
+ end
+end
diff --git a/test/-ext-/debug/test_debug.rb b/test/-ext-/debug/test_debug.rb
new file mode 100644
index 0000000000..c9263d76fa
--- /dev/null
+++ b/test/-ext-/debug/test_debug.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/debug'
+
+class TestDebug < Test::Unit::TestCase
+
+ def binds_check(binds, msg = nil)
+ count = Hash.new(0)
+ assert_instance_of(Array, binds, msg)
+ binds.each{|(_self, bind, klass, iseq, loc)|
+ if _self == self
+ count[:self] += 1
+ end
+
+ if bind
+ assert_instance_of(Binding, bind, msg)
+ count[:bind] += 1
+ end
+
+ if klass
+ assert(klass.instance_of?(Module) || klass.instance_of?(Class), msg)
+ count[:class] += 1
+ end
+
+ if iseq
+ count[:iseq] += 1
+ assert_instance_of(RubyVM::InstructionSequence, iseq, 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)
+
+ }
+ assert_operator(0, :<, count[:self], msg)
+ assert_operator(0, :<, count[:bind], msg)
+ assert_operator(0, :<, count[:iseq], msg)
+ assert_operator(0, :<, count[:class], msg)
+ end
+
+ def test_inspector_open
+ binds = Bug::Debug.inspector
+ binds_check binds
+ end
+
+ def inspector_in_eval
+ eval("Bug::Debug.inspector")
+ end
+
+ def test_inspector_open_in_eval
+ bug7635 = '[ruby-core:51640]'
+ binds = inspector_in_eval
+ binds_check binds, bug7635
+ end
+
+ class MyRelation
+ include Enumerable
+
+ def each
+ yield :each_entry
+ end
+ end
+
+ def test_lazy_block
+ x = MyRelation.new.any? do
+ Bug::Debug.inspector
+ true
+ end
+ 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
new file mode 100644
index 0000000000..d79c94c468
--- /dev/null
+++ b/test/-ext-/debug/test_profile_frames.rb
@@ -0,0 +1,244 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/debug'
+
+class SampleClassForTestProfileFrames
+ class << self
+ attr_accessor :sample4
+ end
+
+ self.sample4 = Module.new do
+ def self.corge(block)
+ Sample2.new.baz(block)
+ end
+ end
+
+ class Sample2
+ EVAL_LINE = __LINE__ + 3
+
+ def baz(block)
+ instance_eval "def zab(block) block.call end"
+ [self, zab(block)]
+ end
+ end
+
+ module Sample3
+ class << self
+ def qux(block)
+ SampleClassForTestProfileFrames.sample4.corge(block)
+ end
+ end
+ end
+
+ def self.bar(block)
+ Sample3.qux(block)
+ end
+
+ def foo(block)
+ self.class.bar(block)
+ 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{
+ Fiber.yield SampleClassForTestProfileFrames.new.foo(lambda{ Bug::Debug.profile_frames(0, 10) })
+ }.resume
+
+ labels = [
+ nil,
+ "test_profile_frames",
+ "zab",
+ "baz",
+ "corge",
+ "qux",
+ "bar",
+ "foo",
+ "test_profile_frames",
+ ]
+ base_labels = [
+ nil,
+ "test_profile_frames",
+ "zab",
+ "baz",
+ "corge",
+ "qux",
+ "bar",
+ "foo",
+ "test_profile_frames",
+ ]
+ full_labels = [
+ "Bug::Debug.profile_frames",
+ "TestProfileFrames#test_profile_frames",
+ "#{obj.inspect}.zab",
+ "SampleClassForTestProfileFrames::Sample2#baz",
+ "#{SampleClassForTestProfileFrames.sample4.inspect}.corge",
+ "SampleClassForTestProfileFrames::Sample3.qux",
+ "SampleClassForTestProfileFrames.bar",
+ "SampleClassForTestProfileFrames#foo",
+ "TestProfileFrames#test_profile_frames",
+ ]
+ classes = [
+ Bug::Debug,
+ TestProfileFrames,
+ obj,
+ SampleClassForTestProfileFrames::Sample2,
+ SampleClassForTestProfileFrames.sample4,
+ SampleClassForTestProfileFrames::Sample3,
+ SampleClassForTestProfileFrames, # singleton method
+ SampleClassForTestProfileFrames,
+ TestProfileFrames,
+ ]
+ singleton_method_p = [
+ true, false, true, false, true, true, true, false, false, false,
+ ]
+ method_names = [
+ "profile_frames",
+ "test_profile_frames",
+ "zab",
+ "baz",
+ "corge",
+ "qux",
+ "bar",
+ "foo",
+ "test_profile_frames",
+ ]
+ qualified_method_names = [
+ "Bug::Debug.profile_frames",
+ "TestProfileFrames#test_profile_frames",
+ "#{obj.inspect}.zab",
+ "SampleClassForTestProfileFrames::Sample2#baz",
+ "#{SampleClassForTestProfileFrames.sample4.inspect}.corge",
+ "SampleClassForTestProfileFrames::Sample3.qux",
+ "SampleClassForTestProfileFrames.bar",
+ "SampleClassForTestProfileFrames#foo",
+ "TestProfileFrames#test_profile_frames",
+ ]
+ 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)
+
+ frames.each.with_index{|(path, absolute_path, label, base_label, full_label, first_lineno,
+ classpath, singleton_p, method_name, qualified_method_name), i|
+ err_msg = "#{i}th frame"
+ assert_equal(paths[i], path, err_msg)
+ assert_equal(absolute_paths[i], absolute_path, err_msg)
+ assert_equal(labels[i], label, err_msg)
+ assert_equal(base_labels[i], base_label, err_msg)
+ assert_equal(singleton_method_p[i], singleton_p, err_msg)
+ assert_equal(method_names[i], method_name, err_msg)
+ assert_equal(qualified_method_names[i], qualified_method_name, err_msg)
+ assert_equal(full_labels[i], full_label, err_msg)
+ assert_match(classes[i].inspect, classpath, err_msg)
+ if label == method_name
+ c = classes[i]
+ m = singleton_p ? c.method(method_name) : c.instance_method(method_name)
+ assert_equal(m.source_location[1], first_lineno, err_msg)
+ end
+ }
+ 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)]
+
+ 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, 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)
+ assert_equal(location.base_label, base_label, err_msg)
+ assert_equal(location.lineno, lineno, err_msg)
+ assert_equal(location.path, path, err_msg)
+ end
+ end
+
+ def test_ifunc_frame
+ bug11851 = '[ruby-core:72409] [Bug #11851]'
+ assert_ruby_status([], <<~'end;', bug11851) # do
+ require '-test-/debug'
+ class A
+ include Bug::Debug
+ def x
+ profile_frames(0, 10)
+ end
+ end
+ def a
+ [A.new].each(&:x)
+ end
+ 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-/econv/test_append.rb b/test/-ext-/econv/test_append.rb
new file mode 100644
index 0000000000..f8c1d2add6
--- /dev/null
+++ b/test/-ext-/econv/test_append.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/econv"
+
+class Test_EConvAppend < Test::Unit::TestCase
+ def test_econv_str_append_valid
+ ec = Bug::EConv.new("utf-8", "cp932")
+ dst = "\u3044".encode("cp932")
+ ret = ec.append("\u3042"*30, dst)
+ assert_same(dst, ret)
+ assert_not_predicate(dst, :ascii_only?)
+ assert_predicate(dst, :valid_encoding?)
+ end
+
+ def test_econv_str_append_broken
+ ec = Bug::EConv.new("utf-8", "cp932")
+ dst = ""
+ ret = ec.append("\u3042"*30, dst)
+ assert_same(dst, ret)
+ assert_not_predicate(dst, :ascii_only?)
+ assert_not_predicate(dst, :valid_encoding?)
+ end
+end
diff --git a/test/-ext-/eval/test_eval.rb b/test/-ext-/eval/test_eval.rb
new file mode 100644
index 0000000000..e37d301b2e
--- /dev/null
+++ b/test/-ext-/eval/test_eval.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/eval"
+
+class EvalTest < Test::Unit::TestCase
+ def test_rb_eval_string
+ _a = 1
+ assert_equal [self, 1, __method__], rb_eval_string(%q{
+ [self, _a, __method__]
+ })
+ end
+end
diff --git a/test/-ext-/exception/test_data_error.rb b/test/-ext-/exception/test_data_error.rb
new file mode 100644
index 0000000000..0fec4ac1ec
--- /dev/null
+++ b/test/-ext-/exception/test_data_error.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+module Bug
+ class Test_ExceptionDE < Test::Unit::TestCase
+ def test_cleanup_data_error
+ bug9167 = '[ruby-core:58643] [Bug #9167]'
+ assert_normal_exit(<<-'end;', bug9167) # do
+ require '-test-/exception'
+ raise Bug::Exception::DataError, "Error"
+ end;
+ end
+ end
+end
diff --git a/test/-ext-/exception/test_enc_raise.rb b/test/-ext-/exception/test_enc_raise.rb
new file mode 100644
index 0000000000..706f0e2772
--- /dev/null
+++ b/test/-ext-/exception/test_enc_raise.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/exception'
+
+module Bug
+ class Test_ExceptionER < Test::Unit::TestCase
+ def test_enc_raise
+ feature5650 = '[ruby-core:41160]'
+ Encoding.list.each do |enc|
+ next unless enc.ascii_compatible?
+ e = assert_raise(Bug::Exception) {Bug::Exception.enc_raise(enc, "[Feature #5650]")}
+ assert_equal(enc, e.message.encoding, feature5650)
+ end
+ end
+ end
+end
diff --git a/test/-ext-/exception/test_ensured.rb b/test/-ext-/exception/test_ensured.rb
new file mode 100644
index 0000000000..c250e46bab
--- /dev/null
+++ b/test/-ext-/exception/test_ensured.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+module Bug
+ class Bug7802 < RuntimeError
+ end
+
+ class Test_ExceptionE < Test::Unit::TestCase
+ def test_ensured
+ assert_separately([], <<-'end;') # do
+
+ require '-test-/exception'
+
+ module Bug
+ class Bug7802 < RuntimeError
+ def try_method
+ raise self
+ end
+
+ def ensured_method
+ [1].detect {|i| true}
+ end
+ end
+ end
+
+ assert_raise(Bug::Bug7802, '[ruby-core:52022] [Bug #7802]') {
+ Bug::Exception.ensured(Bug::Bug7802.new)
+ }
+ end;
+ end
+ end
+end
diff --git a/test/-ext-/exception/test_exception_at_throwing.rb b/test/-ext-/exception/test_exception_at_throwing.rb
new file mode 100644
index 0000000000..4bce348a25
--- /dev/null
+++ b/test/-ext-/exception/test_exception_at_throwing.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+module Bug
+ class Test_ExceptionAT < Test::Unit::TestCase
+ def test_exception_at_throwing
+ assert_separately(%w[-r-test-/exception], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ e = RuntimeError.new("[Bug #13176]")
+ assert_raise_with_message(e.class, e.message) do
+ catch do |t|
+ Bug::Exception.ensure_raise(nil, e) {throw t}
+ end
+ end
+ end;
+ end
+ end
+end
diff --git a/test/-ext-/file/test_stat.rb b/test/-ext-/file/test_stat.rb
new file mode 100644
index 0000000000..9eebcd97b9
--- /dev/null
+++ b/test/-ext-/file/test_stat.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/file"
+
+class Test_FileStat < Test::Unit::TestCase
+ def test_stat_for_fd
+ st = open(__FILE__) {|f| Bug::File::Stat.for_fd(f.fileno)}
+ assert_equal(File.stat(__FILE__), st)
+ end
+
+ def test_stat_for_path
+ st = Bug::File::Stat.for_path(__FILE__)
+ assert_equal(File.stat(__FILE__), st)
+ end
+end
diff --git a/test/-ext-/float/test_nextafter.rb b/test/-ext-/float/test_nextafter.rb
new file mode 100644
index 0000000000..ea8d7c7fc7
--- /dev/null
+++ b/test/-ext-/float/test_nextafter.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/float"
+
+class TestFloatExt < Test::Unit::TestCase
+ NEXTAFTER_VALUES = [
+ -Float::INFINITY,
+ -Float::MAX,
+ -100.0,
+ -1.0-Float::EPSILON,
+ -1.0,
+ -Float::EPSILON,
+ -Float::MIN/2,
+ -Math.ldexp(0.5, Float::MIN_EXP - Float::MANT_DIG + 1),
+ -0.0,
+ 0.0,
+ Math.ldexp(0.5, Float::MIN_EXP - Float::MANT_DIG + 1),
+ Float::MIN/2,
+ Float::MIN,
+ Float::EPSILON,
+ 1.0,
+ 1.0+Float::EPSILON,
+ 100.0,
+ Float::MAX,
+ Float::INFINITY,
+ Float::NAN
+ ]
+
+ test_number = 0
+ NEXTAFTER_VALUES.each {|n1|
+ NEXTAFTER_VALUES.each {|n2|
+ tag = n2.infinite? ? "ruby" : "other"
+ test_name = "test_nextafter_#{test_number}_#{tag}_#{n1}_#{n2}"
+ test_number += 1
+ define_method(test_name) {
+ v1 = Bug::Float.missing_nextafter(n1, n2)
+ v2 = Bug::Float.system_nextafter(n1, n2)
+ assert_kind_of(Float, v1)
+ assert_kind_of(Float, v2)
+ if v1.nan?
+ assert(v2.nan?, "Bug::Float.system_nextafter(#{n1}, #{n2}).nan?")
+ else
+ assert_equal(v1, v2,
+ "Bug::Float.missing_nextafter(#{'%a' % n1}, #{'%a' % n2}) = #{'%a' % v1} != " +
+ "#{'%a' % v2} = Bug::Float.system_nextafter(#{'%a' % n1}, #{'%a' % n2})")
+ if v1 == 0
+ s1 = 1.0/v1 < 0 ? "negative-zero" : "positive-zero"
+ s2 = 1.0/v2 < 0 ? "negative-zero" : "positive-zero"
+ begin
+ assert_equal(s1, s2,
+ "Bug::Float.missing_nextafter(#{'%a' % n1}, #{'%a' % n2}) = #{'%a' % v1} != " +
+ "#{'%a' % v2} = Bug::Float.system_nextafter(#{'%a' % n1}, #{'%a' % n2})")
+ rescue Test::Unit::AssertionFailedError
+ if /aix/ =~ RUBY_PLATFORM
+ omit "Known bug in nextafter(3) on AIX"
+ end
+ raise $!
+ end
+ end
+ end
+ }
+ }
+ }
+
+end
diff --git a/test/-ext-/funcall/test_passing_block.rb b/test/-ext-/funcall/test_passing_block.rb
new file mode 100644
index 0000000000..8964403494
--- /dev/null
+++ b/test/-ext-/funcall/test_passing_block.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestFuncall < Test::Unit::TestCase
+ module Relay
+ def self.target(*args, **kw, &block)
+ yield(*args, **kw) if block
+ end
+ end
+ require '-test-/funcall'
+
+ def test_funcall_extra_args
+ assert_equal 'TestFuncall', TestFuncall.extra_args_name,
+ '[ruby-core:85266] [Bug #14425]'
+ end
+
+ def test_with_funcall2
+ ok = nil
+ Relay.with_funcall2("feature#4504") {|arg| ok = arg || true}
+ assert_nil(ok)
+ end
+
+ def test_with_funcall_passing_block
+ ok = nil
+ Relay.with_funcall_passing_block("feature#4504") {|arg| ok = arg || true}
+ assert_equal("feature#4504", ok)
+ end
+
+ def test_with_funcall_passing_block_kw
+ block = ->(*a, **kw) { [a, kw] }
+ assert_equal([[1], {}], Relay.with_funcall_passing_block_kw(0, 1, &block))
+ assert_equal([[{a: 1}], {}], Relay.with_funcall_passing_block_kw(0, a: 1, &block))
+ assert_equal([[], {a: 1}], Relay.with_funcall_passing_block_kw(1, a: 1, &block))
+ assert_equal([[1], {a: 1}], Relay.with_funcall_passing_block_kw(1, 1, a: 1, &block))
+ assert_equal([[], {a: 1}], Relay.with_funcall_passing_block_kw(3, a: 1, &block))
+ end
+
+ def test_with_funcallv_public_kw
+ o = Object.new
+ def o.foo(*args, **kw)
+ [args, kw]
+ end
+ def o.bar(*args, **kw)
+ [args, kw]
+ end
+ o.singleton_class.send(:private, :bar)
+ def o.baz(arg)
+ arg
+ end
+ assert_equal([[1], {}], Relay.with_funcallv_public_kw(o, :foo, 0, 1))
+ assert_equal([[{a: 1}], {}], Relay.with_funcallv_public_kw(o, :foo, 0, a: 1))
+ assert_equal([[], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 1, a: 1))
+ assert_equal([[1], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 1, 1, a: 1))
+ assert_equal([[], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 3, a: 1))
+ end
+
+ def test_with_yield_splat_kw
+ block = ->(*a, **kw) { [a, kw] }
+ assert_equal([[1], {}], Relay.with_yield_splat_kw(0, [1], &block))
+ assert_equal([[{a: 1}], {}], Relay.with_yield_splat_kw(0, [{a: 1}], &block))
+ assert_equal([[], {a: 1}], Relay.with_yield_splat_kw(1, [{a: 1}], &block))
+ assert_equal([[1], {a: 1}], Relay.with_yield_splat_kw(1, [1, {a: 1}], &block))
+ assert_equal([[], {a: 1}], Relay.with_yield_splat_kw(3, [{a: 1}], &block))
+ end
+end
diff --git a/test/-ext-/gvl/test_last_thread.rb b/test/-ext-/gvl/test_last_thread.rb
new file mode 100644
index 0000000000..bcda0e3385
--- /dev/null
+++ b/test/-ext-/gvl/test_last_thread.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: false
+class TestLastThread < Test::Unit::TestCase
+
+ # [Bug #11237]
+ def test_last_thread
+ assert_separately([], <<-"end;") #do
+ require '-test-/gvl/call_without_gvl'
+
+ Thread.new {
+ sleep 0.2
+ }
+
+ t0 = Time.now
+ Bug::Thread.runnable_sleep 1
+ t1 = Time.now
+ t = t1 - t0
+
+ assert_in_delta(1.0, t, 0.8)
+ end;
+ end
+end
diff --git a/test/-ext-/gvl/test_ubf_async_safe.rb b/test/-ext-/gvl/test_ubf_async_safe.rb
new file mode 100644
index 0000000000..3261b42475
--- /dev/null
+++ b/test/-ext-/gvl/test_ubf_async_safe.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+class TestUbfAsyncSafe < Test::Unit::TestCase
+ def test_ubf_async_safe
+ omit 'need fork for single-threaded test' unless Process.respond_to?(:fork)
+ IO.pipe do |r, w|
+ pid = fork do
+ require '-test-/gvl/call_without_gvl'
+ r.close
+ trap(:INT) { exit!(0) }
+ Bug::Thread.ubf_async_safe(w.fileno)
+ exit!(1)
+ end
+ w.close
+ assert IO.select([r], nil, nil, 30), 'child did not become ready'
+ Process.kill(:INT, pid)
+ _, st = Process.waitpid2(pid)
+ assert_predicate st, :success?, ':INT signal triggered exit'
+ end
+ end
+end
diff --git a/test/-ext-/hash/test_delete.rb b/test/-ext-/hash/test_delete.rb
new file mode 100644
index 0000000000..91e6ff67cb
--- /dev/null
+++ b/test/-ext-/hash/test_delete.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/hash'
+
+class Test_Hash < Test::Unit::TestCase
+ class TestDelete < Test::Unit::TestCase
+ def test_delete
+ hash = Bug::Hash.new
+ hash[1] = 2
+ called = false
+ assert_equal 1, hash.size
+ assert_equal [2], hash.delete!(1) {called = true}
+ assert_equal false, called, "block called"
+ assert_equal 0, hash.size
+ assert_equal nil, hash.delete!(1) {called = true}
+ assert_equal false, called, "block called"
+ assert_equal 0, hash.size
+ end
+ end
+end
diff --git a/test/-ext-/integer/test_integer.rb b/test/-ext-/integer/test_integer.rb
new file mode 100644
index 0000000000..26263211e6
--- /dev/null
+++ b/test/-ext-/integer/test_integer.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/integer'
+
+class Test_Integer < Test::Unit::TestCase
+ FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
+ FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX']
+
+ def test_fixnum_range
+ assert_bignum(FIXNUM_MIN-1)
+ assert_fixnum(FIXNUM_MIN)
+ assert_fixnum(FIXNUM_MAX)
+ assert_bignum(FIXNUM_MAX+1)
+ end
+
+ def test_positive_pow
+ assert_separately(%w[-r-test-/integer], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 3)
+ begin;
+ assert_equal(1, Bug::Integer.positive_pow(1, 1))
+ assert_equal(0, Bug::Integer.positive_pow(0, 1))
+ assert_equal(3, Bug::Integer.positive_pow(3, 1))
+ assert_equal(-3, Bug::Integer.positive_pow(-3, 1))
+ assert_equal(9, Bug::Integer.positive_pow(-3, 2))
+ end;
+ end
+end
diff --git a/test/-ext-/integer/test_my_integer.rb b/test/-ext-/integer/test_my_integer.rb
new file mode 100644
index 0000000000..0dfa234921
--- /dev/null
+++ b/test/-ext-/integer/test_my_integer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/integer"
+
+class Test_MyInteger < Test::Unit::TestCase
+ def test_my_integer_to_f
+ assert_raise(NotImplementedError) do
+ Bug::Integer::MyInteger.new.to_f
+ end
+
+ int = Class.new(Bug::Integer::MyInteger) do
+ def to_f
+ end
+ end
+
+ assert_nothing_raised do
+ int.new.to_f
+ end
+ end
+
+ def test_my_integer_cmp
+ assert_raise(NotImplementedError) do
+ Bug::Integer::MyInteger.new <=> 0
+ end
+
+ int = Class.new(Bug::Integer::MyInteger) do
+ def <=>(other)
+ 0
+ end
+ 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
new file mode 100644
index 0000000000..864ce1afbb
--- /dev/null
+++ b/test/-ext-/iseq_load/test_iseq_load.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestIseqLoad < Test::Unit::TestCase
+ require '-test-/iseq_load'
+ ISeq = RubyVM::InstructionSequence
+
+ def test_bug8543
+ assert_iseq_roundtrip "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ puts "tralivali"
+ def funct(a, b)
+ a**b
+ end
+ 3.times { |i| puts "Hello, world#{funct(2,i)}!" }
+ end;
+ end
+
+ def test_stressful_roundtrip
+ assert_separately(%w[-r-test-/iseq_load], "#{<<~"begin;"}\n#{<<~'end;;'}", timeout: 120)
+ begin;
+ ISeq = RubyVM::InstructionSequence
+ def assert_iseq_roundtrip(src, line=caller_locations(1,1)[0].lineno+1)
+ a = ISeq.compile(src, __FILE__, __FILE__, line).to_a
+ b = ISeq.iseq_load(a).to_a
+ assert_equal a, b, proc {diff(a, b)}
+ b = ISeq.iseq_load(b).to_a
+ assert_equal a, b, proc {diff(a, b)}
+ end
+ def test_bug8543
+ assert_iseq_roundtrip "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ puts "tralivali"
+ def funct(a, b)
+ a**b
+ end
+ 3.times { |i| puts "Hello, world#{funct(2,i)}!" }
+ end;
+ end
+ GC.stress = true
+ test_bug8543
+ end;;
+ end
+
+ def test_case_when
+ assert_iseq_roundtrip "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ def user_mask(target)
+ target.each_char.inject(0) do |mask, chr|
+ case chr
+ when "u"
+ mask | 04700
+ when "g"
+ mask | 02070
+ when "o"
+ mask | 01007
+ when "a"
+ mask | 07777
+ else
+ raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
+ end
+ end
+ end
+ end;
+ end
+
+ def test_splatsplat
+ assert_iseq_roundtrip("#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def splatsplat(**); end
+ end;
+ end
+
+ def test_hidden
+ assert_iseq_roundtrip("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def x(a, (b, *c), d: false); end
+ end;
+ end
+
+ def assert_iseq_roundtrip(src, line=caller_locations(1,1)[0].lineno+1)
+ a = ISeq.compile(src, __FILE__, __FILE__, line).to_a
+ b = ISeq.iseq_load(a).to_a
+ assert_equal a, b, proc {diff(a, b)}
+ b = ISeq.iseq_load(b).to_a
+ assert_equal a, b, proc {diff(a, b)}
+ end
+
+ def test_next_in_block_in_block
+ @next_broke = false
+ src, line = "#{<<~"begin;"}#{<<~'end;'}", __LINE__+2
+ begin;
+ 3.times { 3.times { next; @next_broke = true } }
+ end;
+ a = EnvUtil.suppress_warning {
+ ISeq.compile(src, __FILE__, __FILE__, line)
+ }.to_a
+ iseq = ISeq.iseq_load(a)
+ iseq.eval
+ assert_equal false, @next_broke
+ omit "failing due to stack_max mismatch"
+ assert_iseq_roundtrip(src)
+ end
+
+ def test_break_ensure
+ src, line = "#{<<~"begin;"}#{<<~'end;'}", __LINE__+2
+ begin;
+ def test_break_ensure_def_method
+ bad = true
+ while true
+ begin
+ break
+ ensure
+ bad = false
+ end
+ end
+ bad
+ end
+ end;
+ a = ISeq.compile(src, __FILE__, __FILE__, line).to_a
+ iseq = ISeq.iseq_load(a)
+ iseq.eval
+ 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
+ assert_iseq_roundtrip "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ def foo(kwarg: :foo)
+ kwarg
+ end
+ foo(kwarg: :bar)
+ end;
+ end
+
+ # FIXME: still failing
+ def test_require_integration
+ omit "iseq loader require integration tests still failing"
+ f = File.expand_path(__FILE__)
+ # $(top_srcdir)/test/ruby/test_....rb
+ 3.times { f = File.dirname(f) }
+ all_assertions do |all|
+ Dir[File.join(f, 'ruby', '*.rb')].each do |f|
+ all.for(f) do
+ iseq = ISeq.compile_file(f)
+ orig = iseq.to_a.freeze
+
+ loaded = ISeq.iseq_load(orig).to_a
+ assert loaded == orig, proc {"ISeq unmatch:\n"+diff(orig, loaded)}
+ end
+ end
+ end
+ end
+end
diff --git a/test/-ext-/iter/test_iter_break.rb b/test/-ext-/iter/test_iter_break.rb
new file mode 100644
index 0000000000..8c2379960a
--- /dev/null
+++ b/test/-ext-/iter/test_iter_break.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/iter'
+
+module TestIter
+end
+
+class TestIter::IterBreak < Test::Unit::TestCase
+ def test_iter_break
+ backport7896 = '[ruby-core:52607]'
+ assert_equal(nil, 1.times{Bug::Iter::Breakable.iter_break}, backport7896)
+
+ feature5895 = '[ruby-dev:45132]'
+ assert_equal(42, 1.times{Bug::Iter::Breakable.iter_break_value(42)}, feature5895)
+ end
+end
diff --git a/test/-ext-/iter/test_yield_block.rb b/test/-ext-/iter/test_yield_block.rb
new file mode 100644
index 0000000000..c03a8ee370
--- /dev/null
+++ b/test/-ext-/iter/test_yield_block.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/iter'
+
+module TestIter
+end
+
+class TestIter::YieldBlock < Test::Unit::TestCase
+ class YieldTest
+ include Bug::Iter::Yield
+ attr_reader :blockarg
+ def test(arg, &block)
+ block.call(arg) {|blockarg| @blockarg = blockarg}
+ end
+ def call_proc(&block)
+ block.call {}
+ end
+ def call_lambda(&block)
+ block.call(&->{})
+ end
+ end
+
+ def test_yield_block
+ a = YieldTest.new
+ a.yield_block(:test, "foo") {|x, &b| assert_kind_of(Proc, b); b.call(x)}
+ assert_equal("foo", a.blockarg)
+ end
+
+ def test_yield_lambda
+ a = YieldTest.new
+ assert_not_predicate a.yield_block(:call_proc) {|&b| b}, :lambda?
+ assert_predicate a.yield_block(:call_lambda) {|&b| b}, :lambda?
+ end
+end
diff --git a/test/-ext-/load/script.rb b/test/-ext-/load/script.rb
new file mode 100644
index 0000000000..4bc2480587
--- /dev/null
+++ b/test/-ext-/load/script.rb
@@ -0,0 +1,2 @@
+# frozen_string_literal: true
+raise "foo"
diff --git a/test/-ext-/load/test_dot_dot.rb b/test/-ext-/load/test_dot_dot.rb
new file mode 100644
index 0000000000..f087ff62c0
--- /dev/null
+++ b/test/-ext-/load/test_dot_dot.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_DotDot < Test::Unit::TestCase
+ def test_load_dot_dot
+ feature = '[ruby-dev:41774]'
+ assert_nothing_raised(LoadError, feature) {
+ require '-test-/load/dot.dot'
+ }
+ end
+end
diff --git a/test/-ext-/load/test_protect.rb b/test/-ext-/load/test_protect.rb
new file mode 100644
index 0000000000..83b179b34c
--- /dev/null
+++ b/test/-ext-/load/test_protect.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+require 'test/unit'
+require '-test-/load/protect'
+
+class Test_Load_Protect < Test::Unit::TestCase
+ def test_load_protect
+ assert_raise(LoadError) {
+ Bug.load_protect(__dir__+"/nonexistent.rb")
+ }
+ assert_raise_with_message(RuntimeError, "foo") {
+ Bug.load_protect(__dir__+"/script.rb")
+ }
+ end
+end
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
new file mode 100644
index 0000000000..8b4667fdf9
--- /dev/null
+++ b/test/-ext-/marshal/test_internal_ivar.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/marshal/internal_ivar'
+
+module Bug end
+
+module Bug::Marshal
+ class TestInternalIVar < Test::Unit::TestCase
+ def test_marshal
+ v = InternalIVar.new("hello", "world", "bye", "hi")
+ assert_equal("hello", v.normal)
+ assert_equal("world", v.internal)
+ assert_equal("bye", v.encoding_short)
+ 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)}
+ assert_instance_of(InternalIVar, v)
+ 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-/marshal/test_usrmarshal.rb b/test/-ext-/marshal/test_usrmarshal.rb
new file mode 100644
index 0000000000..263dcb8297
--- /dev/null
+++ b/test/-ext-/marshal/test_usrmarshal.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/marshal/usr'
+
+module Bug end
+
+module Bug::Marshal
+ class TestUsrMarshal < Test::Unit::TestCase
+ def old_dump
+ @old_dump ||=
+ begin
+ src = "module Bug; module Marshal; class UsrMarshal; def initialize(val) @value = val; end; end; ::Marshal.dump(UsrMarshal.new(42), STDOUT); end; end"
+ EnvUtil.invoke_ruby([], src, true)[0]
+ end
+ end
+
+ def test_marshal
+ v = ::Marshal.load(::Marshal.dump(UsrMarshal.new(42)))
+ assert_instance_of(UsrMarshal, v)
+ assert_equal(42, v.value)
+ end
+
+ def test_incompat
+ assert_raise_with_message(ArgumentError, "dump format error") {::Marshal.load(old_dump)}
+ end
+
+ def test_compat
+ out, err = EnvUtil.invoke_ruby(["-r-test-/marshal/usr", "-r-test-/marshal/compat", "-e", "::Marshal.dump(::Marshal.load(STDIN), STDOUT)"], old_dump, true, true)
+ assert_equal(::Marshal.dump(UsrMarshal.new(42)), out)
+ assert_equal("", err)
+ end
+ end
+end
diff --git a/test/-ext-/method/test_arity.rb b/test/-ext-/method/test_arity.rb
new file mode 100644
index 0000000000..cb8549e7c9
--- /dev/null
+++ b/test/-ext-/method/test_arity.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: false
+require '-test-/method'
+require 'test/unit'
+
+class Test_Method < Test::Unit::TestCase
+ class TestArity < Test::Unit::TestCase
+ class A
+ def foo0()
+ end
+ def foom1(*a)
+ end
+ def foom2(a,*b)
+ end
+ def foo1(a)
+ end
+ def foo2(a,b)
+ end
+ end
+
+ class B < A
+ private :foo1, :foo2
+ end
+
+ METHODS = {foo0: 0, foo1: 1, foo2: 2, foom1: -1, foom2: -2}
+
+ def test_base
+ METHODS.each do |name, arity|
+ assert_equal(arity, Bug::Method.mod_method_arity(A, name), "A##{name}")
+ end
+ end
+
+ def test_zsuper
+ METHODS.each do |name, arity|
+ assert_equal(arity, Bug::Method.mod_method_arity(B, name), "B##{name}")
+ end
+ end
+ end
+end
diff --git a/test/-ext-/num2int/test_num2int.rb b/test/-ext-/num2int/test_num2int.rb
new file mode 100644
index 0000000000..1ab1390920
--- /dev/null
+++ b/test/-ext-/num2int/test_num2int.rb
@@ -0,0 +1,265 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/num2int'
+require '-test-/integer'
+require 'rbconfig/sizeof'
+
+class TestNum2int < Test::Unit::TestCase
+ l = RbConfig::LIMITS
+
+ SHRT_MIN = l["SHRT_MIN"]
+ SHRT_MAX = l["SHRT_MAX"]
+ USHRT_MAX = l["USHRT_MAX"]
+
+ INT_MIN = l["INT_MIN"]
+ INT_MAX = l["INT_MAX"]
+ UINT_MAX = l["UINT_MAX"]
+
+ LONG_MAX = l["LONG_MAX"]
+ LONG_MIN = l["LONG_MIN"]
+ ULONG_MAX = l["ULONG_MAX"]
+
+ LLONG_MAX = l["LLONG_MAX"]
+ LLONG_MIN = l["LLONG_MIN"]
+ ULLONG_MAX = l["ULLONG_MAX"]
+
+ FIXNUM_MAX = l["FIXNUM_MAX"]
+ FIXNUM_MIN = l["FIXNUM_MIN"]
+
+ def fix2big(n)
+ 10000000000000000000000000000.coerce(n)[0]
+ end
+
+ def assert_num2i_success_internal(exp, func, arg)
+ mesg = "#{func}(#{arg.inspect})"
+ out = nil
+ assert_nothing_raised(mesg) {
+ out = Num2int.send(func, arg)
+ }
+ assert_equal(exp, out, mesg)
+ end
+
+ def assert_num2i_success(type, num, result=num)
+ func = "NUM2#{type}".upcase
+ assert_num2i_success_internal(result.to_s, func, num)
+ assert_num2i_success_internal(result.to_s, func, fix2big(num))
+ assert_num2i_success_internal(result.to_s, func, Rational(num, 1))
+ if num.to_f.to_i == num
+ assert_num2i_success_internal(result.to_s, func, num.to_f)
+ end
+ # The conversion functions such as NUM2INT uses (conceptually) to_int.
+ if (arg = num.to_f + 0.5) != num.to_f && arg.to_int == num
+ assert_num2i_success_internal(result.to_s, func, arg)
+ end
+ if (arg = num.to_f - 0.5) != num.to_f && arg.to_int == num
+ assert_num2i_success_internal(result.to_s, func, arg)
+ end
+ if (arg = num + Rational(1,2)) && arg.to_int == num
+ assert_num2i_success_internal(result.to_s, func, arg)
+ end
+ if (arg = num - Rational(1,2)) && arg.to_int == num
+ assert_num2i_success_internal(result.to_s, func, arg)
+ end
+ end
+
+ def assert_num2i_error_internal(func, arg)
+ assert_raise(RangeError, "#{func}(#{arg.inspect})") {
+ Num2int.send(func, arg)
+ }
+ end
+
+ def assert_num2i_error(type, num)
+ func = "NUM2#{type}".upcase
+ assert_num2i_error_internal(func, num)
+ assert_num2i_error_internal(func, fix2big(num))
+ assert_num2i_error_internal(func, Rational(num, 1))
+ if num.to_f.to_i == num
+ assert_num2i_error_internal(func, num.to_f)
+ end
+ # The conversion functions such as NUM2INT uses (conceptually) to_int.
+ if (arg = num.to_f + 0.5) != num.to_f && arg.to_int == num
+ assert_num2i_error_internal(func, arg)
+ end
+ if (arg = num.to_f - 0.5) != num.to_f && arg.to_int == num
+ assert_num2i_error_internal(func, arg)
+ end
+ if (arg = num + Rational(1,2)) && arg.to_int == num
+ assert_num2i_error_internal(func, arg)
+ end
+ if (arg = num - Rational(1,2)) && arg.to_int == num
+ assert_num2i_error_internal(func, arg)
+ end
+ end
+
+ def assert_fix2i_success_internal(exp, func, arg)
+ mesg = "#{func}(#{arg.inspect})"
+ out = nil
+ assert_nothing_raised(mesg) {
+ out = Num2int.send(func, arg)
+ }
+ assert_equal(exp, out, mesg)
+ end
+
+ def assert_fix2i_success(type, num, result=num)
+ return unless Bug::Integer.fixnum?(num)
+ func = "FIX2#{type}".upcase
+ assert_fix2i_success_internal(result.to_s, func, num)
+ end
+
+ def assert_fix2i_error_internal(func, arg)
+ assert_raise(RangeError, "#{func}(#{arg.inspect})") {
+ Num2int.send(func, arg)
+ }
+ end
+
+ def assert_fix2i_error(type, num)
+ return unless Bug::Integer.fixnum?(num)
+ func = "FIX2#{type}".upcase
+ assert_num2i_error_internal(func, num)
+ end
+
+ def test_num2short
+ assert_num2i_success(:short, SHRT_MIN)
+ assert_num2i_success(:short, SHRT_MIN+1)
+ assert_num2i_success(:short, SHRT_MAX)
+ assert_num2i_error(:short, SHRT_MIN-1)
+ assert_num2i_error(:short, SHRT_MAX+1)
+ end
+
+ def test_num2ushort
+ assert_num2i_success(:ushort, 0)
+ assert_num2i_success(:ushort, USHRT_MAX)
+ assert_num2i_success(:ushort, -1, USHRT_MAX)
+ assert_num2i_success(:ushort, SHRT_MIN, SHRT_MAX+1)
+ assert_num2i_success(:ushort, SHRT_MIN+1, SHRT_MAX+2)
+ assert_num2i_error(:ushort, SHRT_MIN-1)
+ assert_num2i_error(:ushort, USHRT_MAX+1)
+ end
+
+ def test_num2int
+ assert_num2i_success(:int, INT_MIN)
+ assert_num2i_success(:int, INT_MIN+1)
+ assert_num2i_success(:int, INT_MAX)
+ assert_num2i_error(:int, INT_MIN-1)
+ assert_num2i_error(:int, INT_MAX+1)
+ end
+
+ def test_num2uint
+ assert_num2i_success(:uint, 0)
+ assert_num2i_success(:uint, UINT_MAX)
+ assert_num2i_success(:uint, -1, UINT_MAX)
+ assert_num2i_success(:uint, INT_MIN, INT_MAX+1)
+ assert_num2i_success(:uint, INT_MIN+1, INT_MAX+2)
+ assert_num2i_error(:uint, INT_MIN-1)
+ assert_num2i_error(:uint, UINT_MAX+1)
+ end
+
+ def test_num2long
+ assert_num2i_success(:long, LONG_MIN)
+ assert_num2i_success(:long, LONG_MIN+1)
+ assert_num2i_success(:long, LONG_MAX)
+ assert_num2i_error(:long, LONG_MIN-1)
+ assert_num2i_error(:long, LONG_MAX+1)
+ assert_num2i_success(:long, FIXNUM_MIN)
+ assert_num2i_success(:long, FIXNUM_MIN+1)
+ assert_num2i_success(:long, FIXNUM_MIN-1)
+ assert_num2i_success(:long, FIXNUM_MAX)
+ assert_num2i_success(:long, FIXNUM_MAX+1)
+ end
+
+ def test_num2ulong
+ assert_num2i_success(:ulong, 0)
+ assert_num2i_success(:ulong, ULONG_MAX)
+ assert_num2i_success(:ulong, -1, ULONG_MAX)
+ assert_num2i_success(:ulong, LONG_MIN, LONG_MAX+1)
+ assert_num2i_success(:ulong, LONG_MIN+1, LONG_MAX+2)
+ assert_num2i_error(:ulong, LONG_MIN-1)
+ assert_num2i_error(:ulong, ULONG_MAX+1)
+ assert_num2i_success(:ulong, FIXNUM_MIN, ULONG_MAX-FIXNUM_MAX)
+ assert_num2i_success(:ulong, FIXNUM_MIN+1, ULONG_MAX-FIXNUM_MAX+1)
+ assert_num2i_success(:ulong, FIXNUM_MIN-1, ULONG_MAX-FIXNUM_MAX-1)
+ assert_num2i_success(:ulong, FIXNUM_MAX, FIXNUM_MAX)
+ assert_num2i_success(:ulong, FIXNUM_MAX+1, FIXNUM_MAX+1)
+ end
+
+ def test_num2ll
+ assert_num2i_success(:ll, LLONG_MIN)
+ assert_num2i_success(:ll, LLONG_MIN+1)
+ assert_num2i_success(:ll, LLONG_MAX)
+ assert_num2i_error(:ll, LLONG_MIN-1)
+ assert_num2i_error(:ll, LLONG_MAX+1)
+ assert_num2i_success(:ll, FIXNUM_MIN)
+ assert_num2i_success(:ll, FIXNUM_MIN+1)
+ assert_num2i_success(:ll, FIXNUM_MIN-1)
+ assert_num2i_success(:ll, FIXNUM_MAX)
+ assert_num2i_success(:ll, FIXNUM_MAX+1)
+ end if defined?(Num2int.NUM2LL)
+
+ def test_num2ull
+ assert_num2i_success(:ull, 0)
+ assert_num2i_success(:ull, ULLONG_MAX)
+ assert_num2i_success(:ull, -1, ULLONG_MAX)
+ assert_num2i_success(:ull, LLONG_MIN, LLONG_MAX+1)
+ assert_num2i_success(:ull, LLONG_MIN+1, LLONG_MAX+2)
+ assert_num2i_error(:ull, LLONG_MIN-1)
+ assert_num2i_error(:ull, ULLONG_MAX+1)
+ assert_num2i_success(:ull, FIXNUM_MIN, ULLONG_MAX-FIXNUM_MAX)
+ assert_num2i_success(:ull, FIXNUM_MIN+1, ULLONG_MAX-FIXNUM_MAX+1)
+ assert_num2i_success(:ull, FIXNUM_MIN-1, ULLONG_MAX-FIXNUM_MAX-1)
+ assert_num2i_success(:ull, FIXNUM_MAX)
+ assert_num2i_success(:ull, FIXNUM_MAX+1)
+ end if defined?(Num2int.NUM2ULL)
+
+ def test_fix2short
+ assert_fix2i_success(:short, 0)
+ assert_fix2i_success(:short, SHRT_MAX)
+ assert_fix2i_success(:short, SHRT_MIN)
+ assert_fix2i_success(:short, SHRT_MIN+1)
+ assert_fix2i_error(:short, SHRT_MAX+1)
+ assert_fix2i_error(:short, SHRT_MIN-1)
+ assert_fix2i_error(:short, FIXNUM_MAX)
+ assert_fix2i_error(:short, FIXNUM_MIN)
+ assert_fix2i_error(:short, FIXNUM_MIN+1)
+ end
+
+ def test_fix2int
+ assert_fix2i_success(:int, 0)
+ assert_fix2i_success(:int, INT_MAX)
+ assert_fix2i_success(:int, INT_MIN)
+ assert_fix2i_success(:int, INT_MIN+1)
+ assert_fix2i_error(:int, INT_MAX+1)
+ assert_fix2i_error(:int, INT_MIN-1)
+ assert_fix2i_error(:int, FIXNUM_MAX) if INT_MAX < FIXNUM_MAX
+ assert_fix2i_error(:int, FIXNUM_MIN) if FIXNUM_MIN < INT_MIN
+ assert_fix2i_error(:int, FIXNUM_MIN+1) if FIXNUM_MIN+1 < INT_MIN
+ end
+
+ def test_fix2uint
+ assert_fix2i_success(:uint, 0)
+ assert_fix2i_success(:uint, UINT_MAX)
+ assert_fix2i_success(:uint, INT_MAX)
+ assert_fix2i_success(:uint, INT_MIN, INT_MAX+1)
+ assert_fix2i_success(:uint, INT_MIN+1, INT_MAX+2)
+ assert_fix2i_error(:uint, UINT_MAX+1)
+ assert_fix2i_error(:uint, INT_MIN-1)
+ assert_fix2i_error(:uint, FIXNUM_MAX) if UINT_MAX < FIXNUM_MAX
+ assert_fix2i_error(:uint, FIXNUM_MIN) if FIXNUM_MIN < INT_MIN
+ assert_fix2i_error(:uint, FIXNUM_MIN+1) if FIXNUM_MIN+1 < INT_MIN
+ end
+
+ def test_fix2long
+ assert_fix2i_success(:long, 0)
+ assert_fix2i_success(:long, FIXNUM_MAX)
+ assert_fix2i_success(:long, FIXNUM_MIN)
+ assert_fix2i_success(:long, FIXNUM_MIN+1)
+ end
+
+ def test_fix2ulong
+ assert_fix2i_success(:ulong, 0)
+ assert_fix2i_success(:ulong, FIXNUM_MAX)
+ assert_fix2i_success(:ulong, -1, ULONG_MAX)
+ end
+
+end
+
+
diff --git a/test/-ext-/path_to_class/test_path_to_class.rb b/test/-ext-/path_to_class/test_path_to_class.rb
new file mode 100644
index 0000000000..b67f20b462
--- /dev/null
+++ b/test/-ext-/path_to_class/test_path_to_class.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_PathToClass < Test::Unit::TestCase
+ require '-test-/path_to_class'
+
+ def test_path_to_class
+ bug5691 = '[ruby-core:41410]'
+ assert_raise(ArgumentError, bug5691) {
+ Test_PathToClass.path_to_class("Test_PathToClass::Object")
+ }
+ end
+end
diff --git a/test/-ext-/popen_deadlock/test_popen_deadlock.rb b/test/-ext-/popen_deadlock/test_popen_deadlock.rb
new file mode 100644
index 0000000000..e6ba5e7c1a
--- /dev/null
+++ b/test/-ext-/popen_deadlock/test_popen_deadlock.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+begin
+ require '-test-/popen_deadlock/infinite_loop_dlsym'
+rescue LoadError
+ skip = true
+end
+
+class TestPopenDeadlock < Test::Unit::TestCase
+
+ # [Bug #11265]
+ def assert_popen_without_deadlock
+ assert_separately([], <<-"end;", timeout: 90) #do
+ require '-test-/popen_deadlock/infinite_loop_dlsym'
+
+ bug = '11265'.freeze
+ begin
+ t = Thread.new {
+ Thread.current.__infinite_loop_dlsym__("_ex_unwind")
+ }
+ str = IO.popen([ 'echo', bug ], 'r+') { |io| io.read }
+ assert_equal(bug, str.chomp)
+ ensure
+ t.kill if t
+ end
+ end;
+ end
+ private :assert_popen_without_deadlock
+
+ # 10 test methods are defined for showing progress reports
+ 10.times do |i|
+ define_method("test_popen_without_deadlock_#{i}") {
+ assert_popen_without_deadlock
+ }
+ end
+
+end unless skip #class TestPopenDeadlock
diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb
new file mode 100644
index 0000000000..01d6015de1
--- /dev/null
+++ b/test/-ext-/postponed_job/test_postponed_job.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/postponed_job'
+
+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 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
+
+ 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
+end
diff --git a/test/-ext-/proc/test_bmethod.rb b/test/-ext-/proc/test_bmethod.rb
new file mode 100644
index 0000000000..403d99bfd1
--- /dev/null
+++ b/test/-ext-/proc/test_bmethod.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/proc'
+
+class Test_Proc < Test::Unit::TestCase
+ class TestBMethod < Test::Unit::TestCase
+ end
+end
+
+class Test_Proc::TestBMethod
+ class Base
+ def foo(*a)
+ a
+ end
+ end
+
+ class Bound < Base
+ define_method(:foo, Bug::Proc.make_call_super(42))
+ define_method(:receiver, Bug::Proc.make_call_receiver(nil))
+ end
+
+ def test_super_in_bmethod
+ obj = Bound.new
+ assert_equal([1, 42], obj.foo(1))
+ end
+
+ def test_block_super
+ obj = Bound.new
+ result = nil
+ obj.foo(2) {|*a| result = a}
+ assert_equal([2, 42], result)
+ end
+
+ def test_receiver_in_bmethod
+ obj = Bound.new
+ assert_same(obj, obj.receiver)
+ end
+end
diff --git a/test/-ext-/rational/test_rat.rb b/test/-ext-/rational/test_rat.rb
new file mode 100644
index 0000000000..4e05df7c19
--- /dev/null
+++ b/test/-ext-/rational/test_rat.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/rational"
+
+class TestRational < Test::Unit::TestCase
+ class TestGCD < Test::Unit::TestCase
+
+ def test_gcd_normal
+ x = 2*2*3*3*3
+ y = 2*2*2*3*3
+ gcd = 2*2*3*3
+ assert_equal(gcd, Bug::Rational.gcd_normal(x, y))
+ end
+
+ def test_gcd_gmp
+ x = 2*2*3*3*3
+ y = 2*2*2*3*3
+ gcd = 2*2*3*3
+ assert_equal(gcd, Bug::Rational.gcd_gmp(x, y))
+ rescue NotImplementedError
+ end
+
+ def test_gcd_gmp_brute_force
+ -13.upto(13) {|x|
+ -13.upto(13) {|y|
+ assert_equal(Bug::Rational.gcd_normal(x, y), Bug::Rational.gcd_gmp(x, y))
+ }
+ }
+ rescue NotImplementedError
+ end
+ end
+
+ def test_rb_rational_raw
+ rat = Bug::Rational.raw(1, 2)
+ assert_equal(1, rat.numerator)
+ assert_equal(2, rat.denominator)
+
+ rat = Bug::Rational.raw(-1, 2)
+ assert_equal(-1, rat.numerator)
+ assert_equal(2, rat.denominator)
+
+ rat = Bug::Rational.raw(1, -2)
+ assert_equal(-1, rat.numerator)
+ assert_equal(2, rat.denominator)
+
+ assert_equal(1/2r, Bug::Rational.raw(1.0, 2.0))
+
+ assert_raise(TypeError) { Bug::Rational.raw("1", 2) }
+ assert_raise(TypeError) { Bug::Rational.raw(1, "2") }
+
+ class << (o = Object.new)
+ def to_i; 42; end
+ end
+
+ assert_raise(TypeError) { Bug::Rational.raw(o, 2) }
+ assert_raise(TypeError) { Bug::Rational.raw(1, o) }
+
+ class << (o = Object.new)
+ def to_int; 42; end
+ end
+
+ rat = Bug::Rational.raw(o, 2)
+ assert_equal(42, rat.numerator)
+ assert_equal(2, rat.denominator)
+
+ rat = Bug::Rational.raw(2, o)
+ assert_equal(2, rat.numerator)
+ assert_equal(42, rat.denominator)
+ 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-/st/test_foreach.rb b/test/-ext-/st/test_foreach.rb
new file mode 100644
index 0000000000..4b3eb870e4
--- /dev/null
+++ b/test/-ext-/st/test_foreach.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/st/foreach'
+
+class Test_StForeachUnpack < Test::Unit::TestCase
+ def test_st_foreach_check_unpack
+ assert_nil Bug.unp_st_foreach_check(:check), "goto unpacked_continue"
+ assert_nil Bug.unp_st_foreach_check(:delete1), "goto unpacked"
+ assert_nil Bug.unp_st_foreach_check(:delete2), "goto deleted"
+ end
+
+ def test_st_foreach_unpack
+ assert_nil Bug.unp_st_foreach(:unpacked), "goto unpacked"
+ assert_nil Bug.unp_st_foreach(:unpack_delete), "if (!ptr) return 0"
+ end
+end
diff --git a/test/-ext-/st/test_numhash.rb b/test/-ext-/st/test_numhash.rb
new file mode 100644
index 0000000000..97c3a755bb
--- /dev/null
+++ b/test/-ext-/st/test_numhash.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/st/numhash"
+
+class Bug::StNumHash
+ class Test_NumHash < Test::Unit::TestCase
+ def setup
+ @tbl = Bug::StNumHash.new
+ 5.times {|i| @tbl[i] = i}
+ end
+
+ def test_check
+ keys = []
+ @tbl.each do |k, v, t|
+ keys << k
+ t[5] = 5 if k == 3
+ true
+ end
+ assert_equal([*0..5], keys)
+ end
+
+ def test_update
+ assert_equal(true, @tbl.update(0) {@tbl[5] = :x})
+ assert_equal(:x, @tbl[0])
+ assert_equal(:x, @tbl[5])
+ end
+
+ def test_size_after_delete_safe
+ 10.downto(1) do |up|
+ tbl = Bug::StNumHash.new
+ 1.upto(up){|i| tbl[i] = i}
+ assert_equal(1, tbl.delete_safe(1))
+ assert_equal(up - 1, tbl.size, "delete_safe doesn't change size from #{up} to #{up-1}")
+ end
+ end
+
+ def test_delete_safe_on_iteration
+ 10.downto(1) do |up|
+ tbl = Bug::StNumHash.new
+ 1.upto(up){|i| tbl[i] = i}
+ assert_nothing_raised("delete_safe forces iteration to fail with size #{up}") do
+ tbl.each do |k, v, t|
+ assert_equal k, t.delete_safe(k)
+ true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/-ext-/st/test_update.rb b/test/-ext-/st/test_update.rb
new file mode 100644
index 0000000000..db86eeb8d7
--- /dev/null
+++ b/test/-ext-/st/test_update.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/st/update"
+
+class Bug::StTable
+ class Test_Update < Test::Unit::TestCase
+ def setup
+ @tbl = Bug::StTable.new
+ @tbl[:a] = 1
+ @tbl[:b] = 2
+ end
+
+ def test_notfound
+ assert_equal(false, @tbl.st_update(:c) {42})
+ assert_equal({a: 1, b: 2, c: 42}, @tbl)
+ end
+
+ def test_continue
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; false})
+ assert_equal({a: 1, b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+
+ def test_delete
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; nil})
+ assert_equal({b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+
+ def test_update
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; 3})
+ assert_equal({a: 3, b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+
+ def test_pass_objects_in_st_table
+ bug7330 = '[ruby-core:49220]'
+ key = "abc".freeze
+ value = "def"
+ @tbl[key] = value
+ @tbl.st_update("abc") {|*args|
+ assert_same(key, args[0], bug7330)
+ assert_same(value, args[1], bug7330)
+ nil
+ }
+ 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
new file mode 100644
index 0000000000..a23892142a
--- /dev/null
+++ b/test/-ext-/string/test_capacity.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+require 'test/unit'
+require '-test-/string'
+require 'rbconfig/sizeof'
+require 'objspace'
+
+class Test_StringCapacity < Test::Unit::TestCase
+ def test_capacity_embedded
+ assert_equal pool_slot_size(0) - embed_header_size - 1, capa('foo')
+ assert_equal max_embed_len, capa('1' * max_embed_len)
+ assert_equal max_embed_len, capa('1' * (max_embed_len - 1))
+ end
+
+ def test_capacity_shared
+ sym = ("a" * pool_slot_size(0)).to_sym
+ assert_equal 0, capa(sym.to_s)
+ end
+
+ def test_capacity_normal
+ assert_equal max_embed_len + 1, capa('1' * (max_embed_len + 1))
+ assert_equal max_embed_len + 100, capa('1' * (max_embed_len + 100))
+ end
+
+ def test_s_new_capacity
+ assert_equal("", String.new(capacity: 1000))
+ assert_equal(String, String.new(capacity: 1000).class)
+ 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)))
+ end
+
+ def test_io_read
+ s = String.new(capacity: 1000)
+ open(__FILE__) {|f|f.read(1024*1024, s)}
+ assert_equal(1024*1024, capa(s))
+ open(__FILE__) {|f|s = f.read(1024*1024)}
+ assert_operator(capa(s), :<=, s.bytesize+4096)
+ end
+
+ def test_literal_capacity
+ s = eval(%{
+ # frozen_string_literal: true
+ "#{"a" * (max_embed_len + 1)}"
+ })
+ assert_equal(s.length, capa(s))
+ end
+
+ def test_capacity_frozen
+ s = String.new("I am testing", capacity: 1000)
+ s << "a" * pool_slot_size(0)
+ s.freeze
+ assert_equal(s.length, capa(s))
+ end
+
+ def test_capacity_fstring
+ s = String.new("a" * max_embed_len, capacity: max_embed_len * 3)
+ s << "fstring capacity"
+ s = -s
+ assert_equal(s.length, capa(s))
+ end
+
+ private
+
+ def capa(str)
+ Bug::String.capacity(str)
+ end
+
+ def embed_header_size
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
+ end
+
+ def pool_slot_size(_idx = 0)
+ Integer(ObjectSpace.dump("")[/"slot_size":(\d+)/, 1])
+ end
+
+ def max_embed_len
+ GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] - embed_header_size - 1
+ end
+end
diff --git a/test/-ext-/string/test_coderange.rb b/test/-ext-/string/test_coderange.rb
new file mode 100644
index 0000000000..18b256fcea
--- /dev/null
+++ b/test/-ext-/string/test_coderange.rb
@@ -0,0 +1,60 @@
+# coding: ascii-8bit
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+require "rbconfig/sizeof"
+
+class Test_StringCoderange < Test::Unit::TestCase
+ def setup
+ @sizeof_voidp = RbConfig::SIZEOF["void*"]
+ @a8 = Encoding::ASCII_8BIT
+ @a7 = Encoding::US_ASCII
+ @u8 = Encoding::UTF_8
+ end
+
+ def test_ascii8bit
+ enc = @a8
+ str = "a"
+ str.force_encoding(enc)
+ assert_equal :"7bit", Bug::String.new(str).coderange_scan
+
+ str = "a\xBE".force_encoding(enc)
+ assert_equal :valid, Bug::String.new(str).coderange_scan
+ end
+
+ def test_usascii
+ enc = @a7
+ str = "a"
+ str.force_encoding(enc)
+ assert_equal :"7bit", Bug::String.new(str).coderange_scan
+
+ str = "a" * (@sizeof_voidp * 2)
+ str << "\xBE"
+ str.force_encoding(enc)
+ assert_equal :broken, Bug::String.new(str).coderange_scan
+ end
+
+ def test_utf8
+ enc = @u8
+ str = "a"
+ str.force_encoding(enc)
+ assert_equal :"7bit", Bug::String.new(str).coderange_scan
+
+ str = "a" * (@sizeof_voidp * 3)
+ str << "aa\xC2\x80"
+ str.force_encoding(enc)
+ assert_equal :valid, Bug::String.new(str).coderange_scan
+
+ str = "a" * (@sizeof_voidp * 2)
+ str << "\xC2\x80"
+ str << "a" * (@sizeof_voidp * 2)
+ str.force_encoding(enc)
+ assert_equal :valid, Bug::String.new(str).coderange_scan
+
+ str = "a" * (@sizeof_voidp * 2)
+ str << "\xC1\x80"
+ str << "a" * (@sizeof_voidp * 2)
+ str.force_encoding(enc)
+ assert_equal :broken, Bug::String.new(str).coderange_scan
+ end
+end
diff --git a/test/-ext-/string/test_cstr.rb b/test/-ext-/string/test_cstr.rb
new file mode 100644
index 0000000000..efc64119dc
--- /dev/null
+++ b/test/-ext-/string/test_cstr.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StringCStr < Test::Unit::TestCase
+ Bug4319 = '[ruby-dev:43094]'
+
+ def test_embed
+ s = Bug::String.new("abcdef")
+ s.set_len(3)
+ s.cstr_unterm('x')
+ assert_equal(0, s.cstr_term, Bug4319)
+ end
+
+ def test_long
+ s = Bug::String.new(Bug::String.new("abcdef")*100000)
+ s.cstr_unterm('x')
+ assert_equal(0, s.cstr_term, Bug4319)
+ end
+
+ def test_shared
+ s = Bug::String.new(Bug::String.new("abcdef")*5)
+ s = s.unterminated_substring(0, 29)
+ assert_equal(0, s.cstr_term, Bug4319)
+ end
+
+ def test_frozen
+ s0 = Bug::String.new("abcdefgh"*8)
+
+ [4, 4*3-1, 8*3-1, 64].each do |n|
+ s = Bug::String.new(s0[0, n])
+ s.cstr_unterm('x')
+ s.freeze
+ assert_equal(0, s.cstr_term)
+ WCHARS.each do |enc|
+ s = s0.encode(enc)
+ s.set_len(n - n % s[0].bytesize)
+ s.cstr_unterm('x')
+ s.freeze
+ assert_equal(0, s.cstr_term)
+ end
+ end
+ end
+
+ def test_rb_str_new_frozen_embed
+ # "rbconfi" is the smallest "maximum embeddable string". VWA adds
+ # a capacity field, which removes one pointer capacity for embedded objects,
+ # so if VWA is enabled, but there is only one size pool, then the
+ # maximum embeddable capacity on 32 bit machines is 8 bytes.
+ str = Bug::String.cstr_noembed("rbconfi")
+ str = Bug::String.rb_str_new_frozen(str)
+ assert_equal true, Bug::String.cstr_embedded?(str)
+ end
+
+ WCHARS = [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE]
+
+ def test_wchar_embed
+ WCHARS.each do |enc|
+ s = Bug::String.new("\u{4022}a".encode(enc))
+ s.cstr_unterm('x')
+ assert_nothing_raised(ArgumentError) {s.cstr_term}
+ s.set_len(s.bytesize / 2)
+ assert_equal(1, s.size)
+ s.cstr_unterm('x')
+ assert_equal(0, s.cstr_term)
+ end
+ end
+
+ def test_wchar_long
+ str = "\u{4022}abcdef"
+ n = 100
+ len = str.size * n
+ WCHARS.each do |enc|
+ s = Bug::String.new(Bug::String.new(str.encode(enc))*n)
+ s.cstr_unterm('x')
+ assert_nothing_raised(ArgumentError, enc.name) {s.cstr_term}
+ s.set_len(s.bytesize / 2)
+ assert_equal(len / 2, s.size, enc.name)
+ s.cstr_unterm('x')
+ assert_equal(0, s.cstr_term, enc.name)
+ end
+ end
+
+ def test_wchar_lstrip!
+ assert_wchars_term_char(" a") {|s| s.lstrip!}
+ end
+
+ def test_wchar_rstrip!
+ assert_wchars_term_char("a ") {|s| s.rstrip!}
+ end
+
+ def test_wchar_chop!
+ assert_wchars_term_char("a\n") {|s| s.chop!}
+ end
+
+ def test_wchar_chomp!
+ assert_wchars_term_char("a\n") {|s| s.chomp!}
+ end
+
+ def test_wchar_aset
+ assert_wchars_term_char("a"*30) {|s| s[29,1] = ""}
+ end
+
+ def test_wchar_sub!
+ assert_wchars_term_char("foobar") {|s| s.sub!(/#{"foo".encode(s.encoding)}/, "")}
+ end
+
+ def test_wchar_delete!
+ assert_wchars_term_char("foobar") {|s| s.delete!("ao".encode(s.encoding))}
+ end
+
+ def test_wchar_squeeze!
+ assert_wchars_term_char("foo!") {|s| s.squeeze!}
+ end
+
+ def test_wchar_tr!
+ assert_wchars_term_char("\u{3042}foobar") {|s|
+ enc = s.encoding
+ s.tr!("\u{3042}".encode(enc), "c".encode(enc))
+ }
+ end
+
+ def test_wchar_tr_s!
+ assert_wchars_term_char("\u{3042}foobar") {|s|
+ enc = s.encoding
+ s.tr_s!("\u{3042}".encode(enc), "c".encode(enc))
+ }
+ end
+
+ def test_wchar_replace
+ assert_wchars_term_char("abc") {|s|
+ w = s.dup
+ s.replace("abcdefghijklmnop")
+ s.replace(w)
+ }
+ end
+
+ def test_embedded_from_heap
+ gh821 = "[GH-821]"
+ embedded_string = "abcdefghi"
+ string = embedded_string.gsub("efg", "123")
+ {}[string] = 1
+ non_terminated = "#{string}#{nil}"
+ assert_nil(Bug::String.cstr_term_char(non_terminated), gh821)
+
+ result = {}
+ WCHARS.map do |enc|
+ embedded_string = "ab".encode(enc)
+ string = embedded_string.gsub("b".encode(enc), "1".encode(enc))
+ {}[string] = 1
+ non_terminated = "#{string}#{nil}"
+ c = Bug::String.cstr_term_char(non_terminated)
+ result[enc] = c if c
+ end
+ assert_empty(result, gh821)
+ end
+
+ def assert_wchars_term_char(str)
+ result = {}
+ WCHARS.map do |enc|
+ s = Bug::String.new(str.encode(enc))
+ yield s
+ c = s.cstr_term_char
+ result[enc] = c if c
+ end
+ assert_empty(result)
+ end
+end
diff --git a/test/-ext-/string/test_ellipsize.rb b/test/-ext-/string/test_ellipsize.rb
new file mode 100644
index 0000000000..d340abd58a
--- /dev/null
+++ b/test/-ext-/string/test_ellipsize.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StringEllipsize < Test::Unit::TestCase
+ def setup
+ @foobar = Bug::String.new("foobar")
+ end
+
+ def assert_equal_with_class(expected, result, *rest)
+ assert_equal(expected.encoding, result.encoding, *rest)
+ assert_equal(expected, result, result.encoding.name)
+ assert_instance_of(String, result, *rest)
+ end
+
+ def test_longer
+ assert_equal_with_class("", @foobar.ellipsize(0))
+ assert_equal_with_class(".", @foobar.ellipsize(1))
+ assert_equal_with_class("..", @foobar.ellipsize(2))
+ assert_equal_with_class("...", @foobar.ellipsize(3))
+ assert_equal_with_class("f...", @foobar.ellipsize(4))
+ assert_equal_with_class("fo...", @foobar.ellipsize(5))
+ end
+
+ def test_shorter
+ assert_same(@foobar, @foobar.ellipsize(6))
+ assert_same(@foobar, @foobar.ellipsize(7))
+ end
+
+ def test_negative_length
+ assert_raise(IndexError) {@foobar.ellipsize(-1)}
+ end
+
+ def test_nonascii
+ a = "\u3042"
+ Encoding.list.each do |enc|
+ next if enc.dummy?
+ begin
+ s = a.encode(enc)
+ e = "...".encode(enc)
+ rescue
+ else
+ assert_equal_with_class(s*12+e, Bug::String.new(s*20).ellipsize(15))
+ end
+ end
+ end
+end
diff --git a/test/-ext-/string/test_enc_associate.rb b/test/-ext-/string/test_enc_associate.rb
new file mode 100644
index 0000000000..95d1f00cd2
--- /dev/null
+++ b/test/-ext-/string/test_enc_associate.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StrEncAssociate < Test::Unit::TestCase
+ def test_frozen
+ s = Bug::String.new("abc")
+ s.force_encoding(Encoding::US_ASCII)
+ s.freeze
+ assert_raise(FrozenError) {s.associate_encoding!(Encoding::US_ASCII)}
+ assert_raise(FrozenError) {s.associate_encoding!(Encoding::UTF_8)}
+ end
+
+ Encoding.list.select(&:dummy?).each do |enc|
+ enc = enc.name.tr('-', '_')
+ define_method("test_dummy_encoding_index_#{enc}") do
+ assert_separately(["-r-test-/string", "-", enc], <<-"end;") #do
+ enc = Encoding.const_get(ARGV[0])
+ index = Bug::String.encoding_index(enc)
+ assert(index < 0xffff, "<%#x> expected but was\n<%#x>" % [index & 0xffff, index])
+ end;
+ end
+ end
+end
diff --git a/test/-ext-/string/test_enc_str_buf_cat.rb b/test/-ext-/string/test_enc_str_buf_cat.rb
new file mode 100644
index 0000000000..b9a63ec2de
--- /dev/null
+++ b/test/-ext-/string/test_enc_str_buf_cat.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StringEncStrBufCat < Test::Unit::TestCase
+ Bug6509 = '[ruby-dev:45688]'
+
+ def test_unknown
+ a8_str = "a\xBE".force_encoding(Encoding::ASCII_8BIT)
+ cr_unknown_str = [0x62].pack('C*')
+ assert_equal(true, a8_str.valid_encoding?, "an assertion for following tests")
+ assert_equal(:valid, Bug::String.new(a8_str).coderange, "an assertion for following tests")
+ assert_equal(:unknown, Bug::String.new(cr_unknown_str).coderange, "an assertion for following tests")
+ assert_equal(:valid, Bug::String.new(a8_str).enc_str_buf_cat(cr_unknown_str).coderange, Bug6509)
+ end
+
+ def test_str_conv_enc
+ str = Bug::String.new("aaa".encode("US-ASCII"))
+ assert_same(str, str.str_conv_enc_opts("UTF-8", "US-ASCII", 0, nil))
+
+ str = Bug::String.new("aaa".encode("UTF-16LE").force_encoding("UTF-8"))
+ assert_predicate(str, :ascii_only?) # cache coderange
+ assert_equal("aaa", str.str_conv_enc_opts("UTF-16LE", "UTF-8", 0, nil))
+ end
+end
diff --git a/test/-ext-/string/test_external_new.rb b/test/-ext-/string/test_external_new.rb
new file mode 100644
index 0000000000..f8ee773b16
--- /dev/null
+++ b/test/-ext-/string/test_external_new.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/string'
+
+class Test_StringExternalNew < Test::Unit::TestCase
+ def test_buf_new
+ assert_operator(0, :<=, Bug::String.capacity(Bug::String.buf_new(0)))
+ assert_operator(127, :<=, Bug::String.capacity(Bug::String.buf_new(127)))
+ assert_operator(128, :<=, Bug::String.capacity(Bug::String.buf_new(128)))
+ end
+
+ def test_external_new_with_enc
+ Encoding.list.each do |enc|
+ assert_equal(enc, Bug::String.external_new(0, enc).encoding)
+ end
+ end
+end
diff --git a/test/-ext-/string/test_fstring.rb b/test/-ext-/string/test_fstring.rb
new file mode 100644
index 0000000000..fcec6be543
--- /dev/null
+++ b/test/-ext-/string/test_fstring.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/string'
+require_relative '../symbol/noninterned_name'
+
+class Test_String_Fstring < Test::Unit::TestCase
+ include Test_Symbol::NonInterned
+
+ def assert_fstring(str)
+ fstr = Bug::String.fstring(str)
+ yield str
+ yield fstr
+ end
+
+ def test_rb_enc_interned_str_autoloaded_encoding
+ assert_separately([], <<~RUBY)
+ require '-test-/string'
+ 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::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)
+ str.freeze
+ assert_fstring(str) {|s| assert_send([s, :instance_variable_defined?, :@test])}
+ end
+
+ def test_singleton_method
+ str = __method__.to_s * 3
+ def str.foo
+ end
+ str.freeze
+ assert_fstring(str) {|s| assert_send([s, :respond_to?, :foo])}
+ end
+
+ def test_singleton_class
+ str = noninterned_name
+ fstr = Bug::String.fstring(str)
+ 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
+
+ def test_subclass
+ str = S.new(__method__.to_s * 3)
+ str.freeze
+ assert_fstring(str) {|s| assert_instance_of(S, s)}
+ end
+end
diff --git a/test/-ext-/string/test_interned_str.rb b/test/-ext-/string/test_interned_str.rb
new file mode 100644
index 0000000000..a81cb59aa5
--- /dev/null
+++ b/test/-ext-/string/test_interned_str.rb
@@ -0,0 +1,17 @@
+require 'test/unit'
+require '-test-/string'
+
+class Test_RbInternedStr < Test::Unit::TestCase
+ def test_interned_str
+ src = "a" * 20
+ interned_str = Bug::String.rb_interned_str_dup(src)
+ src.clear
+ 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_modify_expand.rb b/test/-ext-/string/test_modify_expand.rb
new file mode 100644
index 0000000000..9aa3b9a6ca
--- /dev/null
+++ b/test/-ext-/string/test_modify_expand.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+require "rbconfig/sizeof"
+
+class Test_StringModifyExpand < Test::Unit::TestCase
+ def test_modify_expand_memory_leak
+ assert_no_memory_leak(["-r-test-/string"],
+ <<-PRE, <<-CMD, "rb_str_modify_expand()", limit: 2.5)
+ s=Bug::String.new
+ PRE
+ size = $initial_size
+ 10.times{s.modify_expand!(size)}
+ s.replace("")
+ CMD
+ end
+
+ def test_integer_overflow
+ return if RbConfig::SIZEOF['size_t'] > RbConfig::SIZEOF['long']
+ bug12390 = '[ruby-core:75592] [Bug #12390]'
+ s = Bug::String.new
+ assert_raise(ArgumentError, bug12390) {
+ s.modify_expand!(RbConfig::LIMITS["LONG_MAX"])
+ }
+ end
+end
diff --git a/test/-ext-/string/test_nofree.rb b/test/-ext-/string/test_nofree.rb
new file mode 100644
index 0000000000..86681e4652
--- /dev/null
+++ b/test/-ext-/string/test_nofree.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_StringNoFree < Test::Unit::TestCase
+ def test_no_memory_leak
+ bug10942 = '[ruby-core:68436] [Bug #10942] no leak on nofree string'
+ code = '.times {Bug::String.nofree << "a" * 100}'
+ assert_no_memory_leak(%w(-r-test-/string),
+ "100_000#{code}",
+ "1_000_000#{code}",
+ bug10942, rss: true)
+ end
+end
diff --git a/test/-ext-/string/test_normalize.rb b/test/-ext-/string/test_normalize.rb
new file mode 100644
index 0000000000..80b21dff38
--- /dev/null
+++ b/test/-ext-/string/test_normalize.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+require "tempfile"
+
+class Test_StringNormalize < Test::Unit::TestCase
+=begin
+ def test_normalize_all
+ exclude = [
+ #0x340, 0x341, 0x343, 0x344
+ ]
+ (0x0080..0xFFFD).each do |n|
+ next if 0xD800 <= n && n <= 0xDFFF
+ next if exclude.include? n
+ code = n.to_s(16)
+ Tempfile.create("#{code}-#{n.chr(Encoding::UTF_8)}-") do |tempfile|
+ ary = Dir.glob(File.expand_path("../#{code}-*", tempfile.path))
+ assert_equal 1, ary.size
+ result = ary[0]
+ rn = result[/\/\h+-(.+?)-/, 1]
+ #assert_equal tempfile.path, result, "#{rn.dump} is not U+#{n.to_s(16)}"
+ r2 = Bug::String.new(result ).normalize_ospath
+ rn2 = r2[/\/\h+-(.+?)-/, 1]
+ if tempfile.path == result
+ if tempfile.path == r2
+ else
+ puts "U+#{n.to_s(16)} shouldn't be r2#{rn2.dump}"
+ end
+ else
+ if tempfile.path == r2
+ # puts "U+#{n.to_s(16)} shouldn't be r#{rn.dump}"
+ elsif result == r2
+ puts "U+#{n.to_s(16)} shouldn't be #{rn.dump}"
+ else
+ puts "U+#{n.to_s(16)} shouldn't be r#{rn.dump} r2#{rn2.dump}"
+ end
+ end
+ end
+ end
+ end
+=end
+
+ def test_normalize
+ %[
+ \u304C \u304B\u3099
+ \u3077 \u3075\u309A
+ \u308F\u3099 \u308F\u3099
+ \u30F4 \u30A6\u3099
+ \u30DD \u30DB\u309A
+ \u30AB\u303A \u30AB\u303A
+ \u00C1 A\u0301
+ B\u030A B\u030A
+ \u0386 \u0391\u0301
+ \u03D3 \u03D2\u0301
+ \u0401 \u0415\u0308
+ \u2260 =\u0338
+ \u{c548} \u{110b}\u{1161}\u{11ab}
+ ].scan(/(\S+)\s+(\S+)/) do |expected, src|
+ result = Bug::String.new(src).normalize_ospath
+ assert_equal expected, result,
+ "#{expected.dump} is expected but #{src.dump}"
+ end
+ end
+
+ def test_not_normalize_kc
+ %W[
+ \u2460
+ \u2162
+ \u3349
+ \u33A1
+ \u337B
+ \u2116
+ \u33CD
+ \u2121
+ \u32A4
+ \u3231
+ ].each do |src|
+ result = Bug::String.new(src).normalize_ospath
+ assert_equal src, result,
+ "#{src.dump} is expected not to be normalized, but #{result.dump}"
+ end
+ end
+
+ def test_dont_normalize_hfsplus
+ %W[
+ \u2190\u0338
+ \u219A
+ \u212B
+ \uF90A
+ \uF9F4
+ \uF961 \uF9DB
+ \uF96F \uF3AA
+ \uF915 \uF95C \uF9BF
+ \uFA0C
+ \uFA10
+ \uFA19
+ \uFA26
+ ].each do |src|
+ result = Bug::String.new(src).normalize_ospath
+ assert_equal src, result,
+ "#{src.dump} is expected not to be normalized, but #{result.dump}"
+ end
+ end
+
+ def test_invalid_sequence
+ assert_separately(%w[-r-test-/string], <<-'end;')
+ assert_equal("\u{fffd}", Bug::String.new("\xff").normalize_ospath)
+ end;
+ end
+end if Bug::String.method_defined?(:normalize_ospath)
diff --git a/test/-ext-/string/test_qsort.rb b/test/-ext-/string/test_qsort.rb
new file mode 100644
index 0000000000..94aff8c3c4
--- /dev/null
+++ b/test/-ext-/string/test_qsort.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StringQSort < Test::Unit::TestCase
+ def test_qsort
+ s = Bug::String.new("xxozfxx")
+ s.qsort!
+ assert_equal("foxxxxz", s)
+ end
+
+ def test_qsort_slice
+ s = Bug::String.new("xxofzx1")
+ s.qsort!(nil, nil, 3)
+ assert_equal("fzxxxo1", s)
+ s = Bug::String.new("xxofzx231")
+ s.qsort!(nil, nil, 3)
+ assert_equal("231fzxxxo", s)
+ end
+end
diff --git a/test/-ext-/string/test_rb_str_dup.rb b/test/-ext-/string/test_rb_str_dup.rb
new file mode 100644
index 0000000000..c76a90252f
--- /dev/null
+++ b/test/-ext-/string/test_rb_str_dup.rb
@@ -0,0 +1,18 @@
+require 'test/unit'
+require '-test-/string'
+
+class Test_RbStrDup < Test::Unit::TestCase
+ def test_nested_shared_non_frozen
+ orig_str = "a" * GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE]
+ str = Bug::String.rb_str_dup(Bug::String.rb_str_dup(orig_str))
+ assert_send([Bug::String, :shared_string?, str])
+ assert_not_send([Bug::String, :sharing_with_shared?, str], '[Bug #15792]')
+ end
+
+ def test_nested_shared_frozen
+ orig_str = "a" * GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE]
+ str = Bug::String.rb_str_dup(Bug::String.rb_str_dup(orig_str).freeze)
+ assert_send([Bug::String, :shared_string?, str])
+ assert_not_send([Bug::String, :sharing_with_shared?, str], '[Bug #15792]')
+ end
+end
diff --git a/test/-ext-/string/test_set_len.rb b/test/-ext-/string/test_set_len.rb
new file mode 100644
index 0000000000..41e14a293a
--- /dev/null
+++ b/test/-ext-/string/test_set_len.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_StrSetLen < Test::Unit::TestCase
+ def setup
+ # Make string long enough so that it is not embedded
+ @range_end = ("0".ord + GC.stat_heap(0, :slot_size)).chr
+ @s0 = [*"0"..@range_end].join("").freeze
+ @s1 = Bug::String.new(@s0)
+ end
+
+ def teardown
+ orig = [*"0"..@range_end].join("")
+ assert_equal(orig, @s0)
+ end
+
+ def test_non_shared
+ @s1.modify!
+ assert_equal("012", @s1.set_len(3))
+ end
+
+ def test_shared
+ assert_raise(RuntimeError) {
+ @s1.set_len(3)
+ }
+ end
+
+ def test_capacity_equals_to_new_size
+ bug12757 = "[ruby-core:77257] [Bug #12757]"
+ # fill to ensure capacity does not decrease with force_encoding
+ str = Bug::String.new("\x00" * 128, capacity: 128)
+ str.force_encoding("UTF-32BE")
+ 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
new file mode 100644
index 0000000000..b96b40db7b
--- /dev/null
+++ b/test/-ext-/string/test_too_many_dummy_encodings.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/string"
+
+class Test_TooManyDummyEncodings < Test::Unit::TestCase
+ def test_exceed_encoding_table_size
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require "-test-/string"
+ assert_raise_with_message(EncodingError, /too many encoding/) do
+ 1_000.times{|i| Bug::String.rb_define_dummy_encoding("R_#{i}") } # now 256 entries
+ end
+ end;
+ end
+end
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-/struct/test_duplicate.rb b/test/-ext-/struct/test_duplicate.rb
new file mode 100644
index 0000000000..265102d4fc
--- /dev/null
+++ b/test/-ext-/struct/test_duplicate.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/struct"
+
+class Bug::Struct::Test_Duplicate < Test::Unit::TestCase
+ def test_new_duplicate
+ bug12291 = '[ruby-core:74971] [Bug #12291]'
+ assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
+ Bug::Struct.new_duplicate(nil, "a")
+ }
+ assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
+ Bug::Struct.new_duplicate("X", "a")
+ }
+ end
+
+ def test_new_duplicate_under
+ bug12291 = '[ruby-core:74971] [Bug #12291]'
+ assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
+ Bug::Struct.new_duplicate_under("x", "a")
+ }
+ end
+end
diff --git a/test/-ext-/struct/test_len.rb b/test/-ext-/struct/test_len.rb
new file mode 100644
index 0000000000..2358e340e5
--- /dev/null
+++ b/test/-ext-/struct/test_len.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/struct"
+
+class Bug::Struct::Test_Len < Test::Unit::TestCase
+ def test_rstruct_len
+ klass = Bug::Struct.new(:a, :b, :c)
+ assert_equal 3, klass.new.rstruct_len
+ end
+end
diff --git a/test/-ext-/struct/test_member.rb b/test/-ext-/struct/test_member.rb
new file mode 100644
index 0000000000..94735cacb8
--- /dev/null
+++ b/test/-ext-/struct/test_member.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/struct"
+
+class Bug::Struct::Test_Member < Test::Unit::TestCase
+ S = Bug::Struct.new(:a)
+
+ def test_member_get
+ s = S.new(1)
+ assert_equal(1, s.get(:a))
+ assert_raise_with_message(NameError, /is not a struct member/) {s.get(:b)}
+ assert_raise_with_message(NameError, /\u{3042}/) {s.get(:"\u{3042}")}
+ end
+end
diff --git a/test/-ext-/symbol/noninterned_name.rb b/test/-ext-/symbol/noninterned_name.rb
new file mode 100644
index 0000000000..d8b8fb0c06
--- /dev/null
+++ b/test/-ext-/symbol/noninterned_name.rb
@@ -0,0 +1,17 @@
+# frozen-string-literal: true
+
+require "-test-/symbol"
+
+module Test_Symbol
+ module NonInterned
+ module_function
+
+ def noninterned_name(prefix = "")
+ prefix += "_#{Thread.current.object_id.to_s(36).tr('-', '_')}"
+ begin
+ name = "#{prefix}_#{rand(0x1000).to_s(16)}_#{Time.now.usec}"
+ end while Bug::Symbol.find(name)
+ name
+ end
+ end
+end
diff --git a/test/-ext-/symbol/test_inadvertent_creation.rb b/test/-ext-/symbol/test_inadvertent_creation.rb
new file mode 100644
index 0000000000..995e01ee15
--- /dev/null
+++ b/test/-ext-/symbol/test_inadvertent_creation.rb
@@ -0,0 +1,493 @@
+# frozen_string_literal: false
+require 'test/unit'
+require_relative 'noninterned_name'
+
+module Test_Symbol
+ class TestInadvertent < Test::Unit::TestCase
+ include NonInterned
+
+ def setup
+ @obj = Object.new
+ end
+
+ def assert_not_pinneddown(name, msg = nil)
+ assert_not_send([Bug::Symbol, :pinneddown?, name], msg)
+ end
+
+ def assert_not_interned(name, msg = nil)
+ assert_not_send([Bug::Symbol, :find, name], msg)
+ end
+
+ def assert_not_interned_error(obj, meth, name, msg = nil, &block)
+ e = assert_raise(NameError, msg) {obj.__send__(meth, name, &block)}
+ assert_not_pinneddown(name, msg)
+ e
+ end
+
+ def assert_not_interned_false(obj, meth, name, msg = nil)
+ assert_not_send([obj, meth, name], msg)
+ assert_not_pinneddown(name, msg)
+ end
+
+ Feature5072 = '[ruby-core:38367]'
+
+ def test_module_const_get
+ cl = Class.new
+ name = noninterned_name("A")
+
+ assert_not_interned_error(cl, :const_get, name, Feature5072)
+
+ assert_not_interned_error(cl, :const_get, name.to_sym)
+ end
+
+ def test_module_const_get_toplevel
+ bug12089 = '[ruby-dev:49498] [Bug #12089]'
+ name = noninterned_name("A")
+ e = assert_not_interned_error(Object, :const_get, name)
+ assert_equal(name, e.name)
+ assert_not_match(/Object::/, e.message, bug12089)
+ end
+
+ def test_module_const_defined?
+ cl = Class.new
+ name = noninterned_name("A")
+
+ assert_not_interned_false(cl, :const_defined?, name, Feature5072)
+
+ name = noninterned_name
+ assert_not_interned_error(cl, :const_defined?, name.to_sym)
+ end
+
+ def test_module_define_method_type_error
+ cl = Class.new
+ name = noninterned_name
+
+ assert_raise(TypeError) {cl.class_eval {define_method(name, "")}}
+ assert_not_interned(name)
+
+ assert_raise(TypeError) {cl.class_eval {define_method(name.to_sym, "")}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_module_define_method_argument_error
+ cl = Class.new
+ name = noninterned_name
+
+ assert_raise(ArgumentError) {cl.class_eval {define_method(name)}}
+ assert_not_interned(name)
+
+ assert_raise(ArgumentError) {cl.class_eval {define_method(name.to_sym)}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_respond_to_missing
+ feature5072 = Feature5072
+ c = Class.new do
+ def self.respond_to_missing?(*)
+ super
+ end
+ end
+ s = noninterned_name
+
+ # assert_not_interned_false(c, :respond_to?, s, feature5072)
+ assert_not_interned_false(c, :method_defined?, s, feature5072)
+ assert_not_interned_false(c, :public_method_defined?, s, feature5072)
+ assert_not_interned_false(c, :private_method_defined?, s, feature5072)
+ assert_not_interned_false(c, :protected_method_defined?, s, feature5072)
+ assert_not_interned_false(c, :const_defined?, noninterned_name("A"), feature5072)
+ assert_not_interned_false(c, :instance_variable_defined?, noninterned_name("@"), feature5072)
+ assert_not_interned_false(c, :class_variable_defined?, noninterned_name("@@"), feature5072)
+ end
+
+ def test_missing_method
+ bug10985 = '[ruby-core:68564] [Bug #10985]'
+ m = nil
+ c = Class.new do
+ def self.respond_to_missing?(*)
+ true
+ end
+ end
+
+ s = noninterned_name
+ assert_nothing_raised(NameError, bug10985) {m = c.method(s)}
+ assert_raise_with_message(NoMethodError, /#{s}/) {m.call}
+
+ s = noninterned_name
+ assert_nothing_raised(NameError, bug10985) {m = c.public_method(s.to_sym)}
+ assert_raise_with_message(NoMethodError, /#{s}/) {m.call}
+
+ s = noninterned_name
+ assert_nothing_raised(NameError, bug10985) {m = c.singleton_method(s.to_sym)}
+ assert_raise_with_message(NoMethodError, /#{s}/) {m.call}
+ end
+
+ Feature5079 = '[ruby-core:38404]'
+
+ def test_undefined_instance_variable
+ feature5079 = Feature5079
+ c = Class.new
+ iv = noninterned_name("@")
+
+ assert_not_interned_false(c, :instance_variable_get, iv, feature5079)
+ assert_not_interned_error(c, :remove_instance_variable, iv, feature5079)
+ end
+
+ def test_undefined_class_variable
+ feature5079 = Feature5079
+ c = Class.new
+ cv = noninterned_name("@@")
+
+ assert_not_interned_error(c, :class_variable_get, cv, feature5079)
+ assert_not_interned_error(c, :remove_class_variable, cv, feature5079)
+ end
+
+
+ def test_undefined_const
+ feature5079 = Feature5079
+ c = Class.new
+ s = noninterned_name("A")
+
+ assert_not_interned_error(c, :remove_const, s, feature5079)
+ end
+
+ def test_undefined_method
+ feature5079 = Feature5079
+ c = Class.new
+ s = noninterned_name
+
+ assert_not_interned_error(c, :method, s, feature5079)
+ assert_not_interned_error(c, :public_method, s, feature5079)
+ assert_not_interned_error(c, :instance_method, s, feature5079)
+ assert_not_interned_error(c, :public_instance_method, s, feature5079)
+ assert_not_interned_error(c, :singleton_method, s, feature5079)
+ end
+
+ Feature5089 = '[ruby-core:38447]'
+ def test_const_missing
+ feature5089 = Feature5089
+ c = Class.new do
+ def self.const_missing(const_name)
+ raise NameError, const_name.to_s
+ end
+ end
+ s = noninterned_name("A")
+
+ assert_not_interned_error(c, :const_get, s.to_sym, feature5089)
+ assert_not_interned_false(c, :autoload?, s.to_sym, feature5089)
+ end
+
+ def test_aliased_method
+ feature5089 = Feature5089
+ c = Class.new do
+ def self.alias_method(str)
+ super(:puts, str)
+ end
+ end
+ s = noninterned_name
+
+ assert_not_interned_error(c, :alias_method, s, feature5089)
+ assert_not_interned_error(c, :private_class_method, s, feature5089)
+ assert_not_interned_error(c, :private_constant, s, feature5089)
+ assert_not_interned_error(c, :private, s, feature5089)
+ assert_not_interned_error(c, :protected, s, feature5089)
+ assert_not_interned_error(c, :public, s, feature5089)
+ assert_not_interned_error(c, :public_class_method, s, feature5089)
+ assert_not_interned_error(c, :public_constant, s, feature5089)
+ assert_not_interned_error(c, :remove_method, s, feature5089)
+ assert_not_interned_error(c, :undef_method, s, feature5089)
+ assert_not_interned_error(c, :untrace_var, s, feature5089)
+ end
+
+ Feature5112 = '[ruby-core:38576]'
+
+ def test_public_send
+ name = noninterned_name
+ e = assert_raise(NoMethodError) {@obj.public_send(name, Feature5112)}
+ assert_not_interned(name)
+ assert_equal(name, e.name)
+ assert_equal([Feature5112], e.args)
+ end
+
+ def test_send
+ name = noninterned_name
+ e = assert_raise(NoMethodError) {@obj.send(name, Feature5112)}
+ assert_not_interned(name)
+ assert_equal(name, e.name)
+ assert_equal([Feature5112], e.args)
+ end
+
+ def test___send__
+ name = noninterned_name
+ e = assert_raise(NoMethodError) {@obj.__send__(name, Feature5112)}
+ assert_not_interned(name)
+ assert_equal(name, e.name)
+ assert_equal([Feature5112], e.args)
+ end
+
+ def test_thread_aref
+ Thread.current[:test] = nil
+ name = noninterned_name
+ assert_nil(Thread.current[name])
+ assert_not_interned(name)
+ end
+
+ def test_thread_key?
+ Thread.current[:test] = nil
+ name = noninterned_name
+ assert_not_send([Thread.current, :key?, name])
+ assert_not_interned(name)
+ end
+
+ def test_thread_variable_get
+ Thread.current.thread_variable_set(:test, nil)
+ name = noninterned_name
+ assert_nil(Thread.current.thread_variable_get(name))
+ assert_not_pinneddown(name)
+ end
+
+ def test_thread_variable_set
+ name = noninterned_name
+ Thread.current.thread_variable_set(name, 42)
+ assert_not_pinneddown(name)
+ end
+
+ def test_thread_variable?
+ Thread.current.thread_variable_set(:test, nil)
+ name = noninterned_name
+ assert_not_send([Thread.current, :thread_variable?, name])
+ assert_not_pinneddown(name)
+ end
+
+ def test_enumerable_inject_op
+ name = noninterned_name
+ assert_raise(NoMethodError) {[1, 2].inject(name)}
+ assert_not_interned(name)
+ end
+
+ def test_module_const_set
+ name = noninterned_name
+ mod = Module.new
+ assert_raise(NameError) {mod.const_set(name, true)}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.const_set(name.to_sym, true)}
+ assert_not_pinneddown(name)
+ end
+
+ def test_module_cvar_set
+ name = noninterned_name
+ mod = Module.new
+ assert_raise(NameError) {mod.class_variable_set(name, true)}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.class_variable_set(name.to_sym, true)}
+ assert_not_pinneddown(name)
+ end
+
+ def test_object_ivar_set
+ name = noninterned_name
+ obj = Object.new
+ assert_raise(NameError) {obj.instance_variable_set(name, true)}
+ assert_not_interned(name)
+ assert_raise(NameError) {obj.instance_variable_set(name.to_sym, true)}
+ assert_not_pinneddown(name)
+ end
+
+ def test_struct_new
+ name = noninterned_name
+ assert_raise(NameError) {Struct.new(name)}
+ assert_not_interned(name)
+ end
+
+ def test_struct_aref
+ s = Struct.new(:foo).new
+ name = noninterned_name
+ assert_raise(NameError) {s[name]}
+ assert_not_interned(name)
+ end
+
+ def test_struct_aset
+ s = Struct.new(:foo).new
+ name = noninterned_name
+ assert_raise(NameError) {s[name] = true}
+ assert_not_interned(name)
+ end
+
+ def test_invalid_attr
+ name = noninterned_name("*")
+ mod = Module.new
+ assert_raise(NameError) {mod.module_eval {attr(name)}}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.module_eval {attr(name.to_sym)}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_invalid_attr_reader
+ name = noninterned_name("*")
+ mod = Module.new
+ assert_raise(NameError) {mod.module_eval {attr_reader(name)}}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.module_eval {attr_reader(name.to_sym)}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_invalid_attr_writer
+ name = noninterned_name("*")
+ mod = Module.new
+ assert_raise(NameError) {mod.module_eval {attr_writer(name)}}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.module_eval {attr_writer(name.to_sym)}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_invalid_attr_accessor
+ name = noninterned_name("*")
+ mod = Module.new
+ assert_raise(NameError) {mod.module_eval {attr_accessor(name)}}
+ assert_not_interned(name)
+ assert_raise(NameError) {mod.module_eval {attr_accessor(name.to_sym)}}
+ assert_not_pinneddown(name)
+ end
+
+ def test_gc_attrset
+ assert_separately(['-r-test-/symbol', '-r-ext-/symbol/noninterned_name', '-'], "#{<<-'begin;'}\n#{<<-"end;"}")
+ bug = '[ruby-core:62226] [Bug #9787]'
+ include Test_Symbol::NonInterned
+ names = Array.new(1000) {noninterned_name("gc")}
+ names.each {|n| n.to_sym}
+ GC.start(immediate_sweep: false)
+ names.each do |n|
+ eval(":#{n}=")
+ assert_nothing_raised(TypeError, bug) {eval("proc{self.#{n} = nil}")}
+ end
+ begin;
+ end;
+ end
+
+ def test_execopt_key
+ name = noninterned_name.intern
+ assert_raise(ArgumentError) {
+ system(".", name => nil)
+ }
+ assert_not_pinneddown(name)
+ end
+
+ def test_execopt_redirect_value
+ name = noninterned_name.intern
+ assert_raise(ArgumentError) {
+ system(".", [] => name)
+ }
+ assert_not_pinneddown(name)
+ end
+
+ def test_execopt_redirect_path
+ name = noninterned_name.intern
+ assert_raise(TypeError) {
+ system(".", [] => [name, 0])
+ }
+ assert_not_pinneddown(name)
+ end
+
+ def test_execopt_redirect_symbol
+ name = noninterned_name.intern
+ assert_raise(ArgumentError) {
+ system(".", in: name)
+ }
+ assert_not_pinneddown(name)
+ end
+
+ def assert_no_immortal_symbol_created(name)
+ name = noninterned_name(name)
+ yield(name)
+ assert_not_pinneddown(name)
+ end
+
+ def assert_no_immortal_symbol_in_method_missing(name)
+ assert_no_immortal_symbol_created("send should not leak - #{name}") do |name|
+ assert_raise(NoMethodError) {yield(name)}
+ end
+ end
+
+ def test_send_leak_string
+ assert_no_immortal_symbol_in_method_missing("str") do |name|
+ 42.send(name)
+ end
+ end
+
+ def test_send_leak_symbol
+ assert_no_immortal_symbol_in_method_missing("sym") do |name|
+ 42.send(name.to_sym)
+ end
+ end
+
+ def test_send_leak_string_custom_method_missing
+ x = Object.new
+ def x.method_missing(*); super; end
+ assert_no_immortal_symbol_in_method_missing("str mm") do |name|
+ x.send(name)
+ end
+ end
+
+ def test_send_leak_symbol_custom_method_missing
+ x = Object.new
+ def x.method_missing(*); super; end
+ assert_no_immortal_symbol_in_method_missing("sym mm") do |name|
+ x.send(name.to_sym)
+ end
+ end
+
+ def test_send_leak_string_no_optimization
+ assert_no_immortal_symbol_in_method_missing("str slow") do |name|
+ 42.method(:send).call(name)
+ end
+ end
+
+ def test_send_leak_symbol_no_optimization
+ assert_no_immortal_symbol_in_method_missing("sym slow") do |name|
+ 42.method(:send).call(name.to_sym)
+ end
+ end
+
+ def test_send_leak_string_custom_method_missing_no_optimization
+ x = Object.new
+ def x.method_missing(*); super; end
+ assert_no_immortal_symbol_in_method_missing("str mm slow") do |name|
+ x.method(:send).call(name)
+ end
+ end
+
+ def test_send_leak_symbol_custom_method_missing_no_optimization
+ x = Object.new
+ def x.method_missing(*); super; end
+ assert_no_immortal_symbol_in_method_missing("sym mm slow") do |name|
+ x.method(:send).call(name.to_sym)
+ end
+ end
+
+ def test_kwarg_symbol_leak_no_rest
+ foo = -> (arg: 42) {}
+ assert_no_immortal_symbol_created("kwarg no rest") do |name|
+ assert_raise(ArgumentError) { foo.call(name.to_sym => 42) }
+ end
+ end
+
+ def test_kwarg_symbol_leak_with_rest
+ foo = -> (arg: 2, **options) {}
+ assert_no_immortal_symbol_created("kwarg with rest") do |name|
+ foo.call(name.to_sym => 42)
+ end
+ end
+
+ def test_kwarg_symbol_leak_just_rest
+ foo = -> (**options) {}
+ assert_no_immortal_symbol_created("kwarg just rest") do |name|
+ foo.call(name.to_sym => 42)
+ end
+ end
+
+ def test_iv_get
+ obj = Object.new
+ assert_no_immortal_symbol_created("rb_iv_get") do |name|
+ Bug::Symbol.iv_get(obj, name)
+ end
+ end
+ end
+end
diff --git a/test/-ext-/symbol/test_type.rb b/test/-ext-/symbol/test_type.rb
new file mode 100644
index 0000000000..ed019062fa
--- /dev/null
+++ b/test/-ext-/symbol/test_type.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/symbol"
+
+module Test_Symbol
+ class TestType < Test::Unit::TestCase
+ def test_id2str_fstring_bug9171
+ fstr = eval("# encoding: us-ascii
+ 'foobar'.freeze")
+ assert_same fstr, Bug::Symbol.id2str(:foobar)
+
+ fstr = eval("# encoding: us-ascii
+ '>'.freeze")
+ assert_same fstr, Bug::Symbol.id2str(:>)
+ end
+
+ def assert_symtype(sym, pred, msg = nil)
+ assert_send([Bug::Symbol, pred, sym], msg)
+ end
+
+ def assert_not_symtype(sym, pred, msg = nil)
+ assert_not_send([Bug::Symbol, pred, sym], msg)
+ end
+
+ def test_const
+ assert_symtype("Foo", :const?)
+ assert_not_symtype("F!", :const?)
+ assert_not_symtype("foo", :const?)
+ assert_not_symtype("@foo", :const?)
+ assert_not_symtype("@@foo", :const?)
+ assert_not_symtype("$foo", :const?)
+ assert_not_symtype("foo=", :const?)
+ assert_not_symtype("[foo]", :const?)
+ assert_not_symtype("xFoo", :const?)
+ end
+
+ def test_local
+ assert_symtype("foo", :local?)
+ assert_symtype("fooBar", :local?)
+ assert_symtype("foo_bar", :local?)
+ assert_not_symtype("foo!", :local?)
+ assert_not_symtype("foo?", :local?)
+ assert_not_symtype("Foo", :local?)
+ assert_not_symtype("@foo", :local?)
+ assert_not_symtype("@@foo", :local?)
+ assert_not_symtype("$foo", :local?)
+ assert_not_symtype("foo=", :local?)
+ assert_not_symtype("[foo]", :local?)
+ end
+
+ def test_global
+ assert_symtype("$foo", :global?)
+ assert_symtype("$$", :global?)
+ assert_not_symtype("$()", :global?)
+ assert_not_symtype("$", :global?)
+ assert_not_symtype("foo", :global?)
+ assert_not_symtype("Foo", :global?)
+ assert_not_symtype("@foo", :global?)
+ assert_not_symtype("@@foo", :global?)
+ assert_not_symtype("foo=", :global?)
+ assert_not_symtype("[foo]", :global?)
+ end
+
+ def test_instance
+ assert_symtype("@foo", :instance?)
+ assert_not_symtype("@", :instance?)
+ assert_not_symtype("@1", :instance?)
+ assert_not_symtype("@@", :instance?)
+ assert_not_symtype("foo", :instance?)
+ assert_not_symtype("Foo", :instance?)
+ assert_not_symtype("@@foo", :instance?)
+ assert_not_symtype("$foo", :instance?)
+ assert_not_symtype("foo=", :instance?)
+ assert_not_symtype("[foo]", :instance?)
+ end
+
+ def test_class
+ assert_symtype("@@foo", :class?)
+ assert_not_symtype("@@", :class?)
+ assert_not_symtype("@", :class?)
+ assert_not_symtype("@@1", :class?)
+ assert_not_symtype("foo", :class?)
+ assert_not_symtype("Foo", :class?)
+ assert_not_symtype("@foo", :class?)
+ assert_not_symtype("$foo", :class?)
+ assert_not_symtype("foo=", :class?)
+ assert_not_symtype("[foo]", :class?)
+ end
+
+ def test_attrset
+ assert_symtype("foo=", :attrset?)
+ assert_symtype("Foo=", :attrset?)
+ assert_symtype("@foo=", :attrset?)
+ assert_symtype("@@foo=", :attrset?)
+ assert_symtype("$foo=", :attrset?)
+ assert_not_symtype("0=", :attrset?)
+ assert_not_symtype("@=", :attrset?)
+ assert_not_symtype("@@=", :attrset?)
+ assert_not_symtype("foo", :attrset?)
+ assert_not_symtype("Foo", :attrset?)
+ assert_not_symtype("@foo", :attrset?)
+ assert_not_symtype("@@foo", :attrset?)
+ assert_not_symtype("$foo", :attrset?)
+ assert_not_symtype("[foo]", :attrset?)
+ assert_not_symtype("[foo]=", :attrset?)
+ assert_equal(:"foo=", Bug::Symbol.attrset("foo"))
+ assert_symtype(Bug::Symbol.attrset("foo"), :attrset?)
+ assert_equal(:"Foo=", Bug::Symbol.attrset("Foo"))
+ assert_symtype(Bug::Symbol.attrset("Foo"), :attrset?)
+ assert_equal(:"@foo=", Bug::Symbol.attrset("@foo"))
+ assert_symtype(Bug::Symbol.attrset("@foo"), :attrset?)
+ assert_equal(:"@@foo=", Bug::Symbol.attrset("@@foo"))
+ assert_symtype(Bug::Symbol.attrset("@@foo"), :attrset?)
+ assert_equal(:"$foo=", Bug::Symbol.attrset("$foo"))
+ assert_symtype(Bug::Symbol.attrset("$foo"), :attrset?)
+ assert_equal(:"[foo]=", Bug::Symbol.attrset("[foo]"))
+ assert_equal(:[]=, Bug::Symbol.attrset(:[]))
+ assert_symtype(Bug::Symbol.attrset("foo?="), :attrset?)
+ assert_equal(:"foo?=", Bug::Symbol.attrset(:foo?))
+ assert_symtype(Bug::Symbol.attrset("foo!="), :attrset?)
+ assert_equal(:"foo!=", Bug::Symbol.attrset(:foo!))
+ end
+
+ def test_check_id_invalid_type
+ cx = EnvUtil.labeled_class("X\u{1f431}")
+ 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}")
+ 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
new file mode 100644
index 0000000000..7f30feb944
--- /dev/null
+++ b/test/-ext-/test_abi.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+return unless RUBY_PATCHLEVEL < 0
+
+class TestABI < Test::Unit::TestCase
+ def test_require_lib_with_incorrect_abi_on_dev_ruby
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [], <<~RUBY
+ err = assert_raise(LoadError) { require "-test-/abi" }
+ assert_match(/incompatible ABI version/, err.message)
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
+ RUBY
+ end
+
+ def test_disable_abi_check_using_environment_variable
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [{ "RUBY_ABI_CHECK" => "0" }], <<~RUBY
+ assert_nothing_raised { require "-test-/abi" }
+ RUBY
+ end
+
+ def test_enable_abi_check_using_environment_variable
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [{ "RUBY_ABI_CHECK" => "1" }], <<~RUBY
+ err = assert_raise(LoadError) { require "-test-/abi" }
+ assert_match(/incompatible ABI version/, err.message)
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
+ RUBY
+ end
+
+ def test_require_lib_with_incorrect_abi_on_release_ruby
+ omit "ABI is enforced" if abi_checking_supported?
+
+ assert_separately [], <<~RUBY
+ assert_nothing_raised { require "-test-/abi" }
+ RUBY
+ end
+
+ private
+
+ def abi_checking_supported?
+ !(RUBY_PLATFORM =~ /mswin|mingw/)
+ end
+end
diff --git a/test/-ext-/test_bug-14834.rb b/test/-ext-/test_bug-14834.rb
new file mode 100644
index 0000000000..a3623b8adc
--- /dev/null
+++ b/test/-ext-/test_bug-14834.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class Test_BUG_14834 < Test::Unit::TestCase
+ def test
+ assert_ruby_status [], <<~'end;', '[ruby-core:87449] [Bug #14834]'
+ require '-test-/bug_14834'
+ Bug.bug_14834 do
+ [123].group_by {}
+ end
+ end;
+ end
+end
diff --git a/test/-ext-/test_bug-3571.rb b/test/-ext-/test_bug-3571.rb
new file mode 100644
index 0000000000..5952ce2a33
--- /dev/null
+++ b/test/-ext-/test_bug-3571.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class Test_BUG_3571 < Test::Unit::TestCase
+ def test_block_call_id
+ bug3571 = '[ruby-dev:41852]'
+ src = <<SRC
+begin
+ Bug.start
+rescue NotImplementedError => e
+ STDERR.puts e.message, e.backtrace[$0.size..-1]
+end
+SRC
+ out = [
+ "start() function is unimplemented on this machine",
+ "-:2:in 'Bug.start'",
+ "-:2:in '<main>'",
+ ]
+ assert_in_out_err(%w"-r-test-/bug_3571", src, [], out, bug3571)
+ end
+end
diff --git a/test/-ext-/test_bug-5832.rb b/test/-ext-/test_bug-5832.rb
new file mode 100644
index 0000000000..986a3706d8
--- /dev/null
+++ b/test/-ext-/test_bug-5832.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: false
+require '-test-/bug_5832'
+
+class Test_BUG_5832 < Test::Unit::TestCase
+ def test_block_passing
+ bug5832 = '[ruby-dev:45071]'
+
+ c = Class.new do
+ define_method(:call_invoke_block_from_c) do
+ Bug.funcall_callback(self)
+ end
+
+ def callback
+ yield if block_given?
+ end
+ end
+
+ assert_nothing_raised(RuntimeError, bug5832) do
+ c.new.call_invoke_block_from_c { raise 'unreachable' }
+ end
+ end
+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-/test_enumerator_kw.rb b/test/-ext-/test_enumerator_kw.rb
new file mode 100644
index 0000000000..fb47c2bcf8
--- /dev/null
+++ b/test/-ext-/test_enumerator_kw.rb
@@ -0,0 +1,11 @@
+require 'test/unit'
+require '-test-/enumerator_kw'
+
+class TestEnumeratorKw < Test::Unit::TestCase
+ def test_enumerator_kw
+ o = Object.new
+ o.extend Bug::EnumeratorKw
+ assert_equal([nil, [], {:a=>1}, o], o.m(a: 1) { |*a| a })
+ assert_equal([nil, [[], {:a=>1}, o], nil, o], o.m(a: 1).each { |*a| a })
+ end
+end
diff --git a/test/-ext-/test_notimplement.rb b/test/-ext-/test_notimplement.rb
new file mode 100644
index 0000000000..038b507b73
--- /dev/null
+++ b/test/-ext-/test_notimplement.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: false
+require '-test-/notimplement'
+
+class Test_NotImplement < Test::Unit::TestCase
+ def test_funcall_notimplement
+ bug3662 = '[ruby-dev:41953]'
+ assert_raise(NotImplementedError, bug3662) {
+ Bug.funcall(:notimplement)
+ }
+ assert_raise(NotImplementedError) {
+ Bug::NotImplement.new.notimplement
+ }
+ end
+
+ def test_respond_to
+ assert_include(Bug.methods(false), :notimplement)
+ assert_include(Bug::NotImplement.instance_methods(false), :notimplement)
+ assert_not_respond_to(Bug, :notimplement)
+ assert_not_respond_to(Bug::NotImplement.new, :notimplement)
+ end
+
+ def test_method_inspect_notimplement
+ assert_match(/not-implemented/, Bug.method(:notimplement).inspect)
+ assert_match(/not-implemented/, Bug::NotImplement.instance_method(:notimplement).inspect)
+ end
+
+ def test_not_method_defined
+ assert !Bug::NotImplement.method_defined?(:notimplement)
+ assert !Bug::NotImplement.method_defined?(:notimplement, true)
+ assert !Bug::NotImplement.method_defined?(:notimplement, false)
+ end
+
+ def test_not_private_method_defined
+ assert !Bug::NotImplement.private_method_defined?(:notimplement)
+ assert !Bug::NotImplement.private_method_defined?(:notimplement, true)
+ assert !Bug::NotImplement.private_method_defined?(:notimplement, false)
+ end
+
+ def test_not_protected_method_defined
+ assert !Bug::NotImplement.protected_method_defined?(:notimplement)
+ assert !Bug::NotImplement.protected_method_defined?(:notimplement, true)
+ assert !Bug::NotImplement.protected_method_defined?(:notimplement, false)
+ end
+end
diff --git a/test/-ext-/test_printf.rb b/test/-ext-/test_printf.rb
new file mode 100644
index 0000000000..c2b50285b9
--- /dev/null
+++ b/test/-ext-/test_printf.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/printf"
+require_relative '../ruby/allpairs'
+
+class Test_SPrintf < Test::Unit::TestCase
+ def to_s
+ "#{self.class}:#{object_id}"
+ end
+
+ def inspect
+ "<#{self.class}:#{object_id}>"
+ end
+
+ def test_to_str
+ assert_equal("<#{self.class}:#{object_id}>", Bug::Printf.s(self))
+ end
+
+ def test_inspect
+ assert_equal("{<#{self.class}:#{object_id}>}", Bug::Printf.v(self))
+ end
+
+ def test_quote
+ assert_equal('[\n]', Bug::Printf.q("\n"))
+ assert_equal('[aaa]', Bug::Printf.q('aaa'))
+ assert_equal('[a a]', Bug::Printf.q('a a'))
+ assert_equal('[]', Bug::Printf.q(''))
+ assert_equal('[]', Bug::Printf.q(:''))
+ end
+
+ def test_encoding
+ def self.to_s
+ "\u{3042 3044 3046 3048 304a}"
+ end
+ assert_equal("<\u{3042 3044 3046 3048 304a}>", Bug::Printf.s(self))
+ end
+
+ VS = [
+ #-0x1000000000000000000000000000000000000000000000002,
+ #-0x1000000000000000000000000000000000000000000000001,
+ #-0x1000000000000000000000000000000000000000000000000,
+ #-0xffffffffffffffffffffffffffffffffffffffffffffffff,
+ #-0x1000000000000000000000002,
+ #-0x1000000000000000000000001,
+ #-0x1000000000000000000000000,
+ #-0xffffffffffffffffffffffff,
+ -0x10000000000000002,
+ -0x10000000000000001,
+ -0x10000000000000000,
+ -0xffffffffffffffff,
+ -0x4000000000000002,
+ -0x4000000000000001,
+ -0x4000000000000000,
+ -0x3fffffffffffffff,
+ -0x100000002,
+ -0x100000001,
+ -0x100000000,
+ -0xffffffff,
+ #-0xc717a08d, # 0xc717a08d * 0x524b2245 = 0x4000000000000001
+ -0x80000002,
+ -0x80000001,
+ -0x80000000,
+ -0x7fffffff,
+ #-0x524b2245,
+ -0x40000002,
+ -0x40000001,
+ -0x40000000,
+ -0x3fffffff,
+ #-0x10002,
+ #-0x10001,
+ #-0x10000,
+ #-0xffff,
+ #-0x8101, # 0x8101 * 0x7f01 = 0x40000001
+ #-0x8002,
+ #-0x8001,
+ #-0x8000,
+ #-0x7fff,
+ #-0x7f01,
+ #-65,
+ #-64,
+ #-63,
+ #-62,
+ #-33,
+ #-32,
+ #-31,
+ #-30,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ #30,
+ #31,
+ #32,
+ #33,
+ #62,
+ #63,
+ #64,
+ #65,
+ #0x7f01,
+ #0x7ffe,
+ #0x7fff,
+ #0x8000,
+ #0x8001,
+ #0x8101,
+ #0xfffe,
+ #0xffff,
+ #0x10000,
+ #0x10001,
+ 0x3ffffffe,
+ 0x3fffffff,
+ 0x40000000,
+ 0x40000001,
+ #0x524b2245,
+ 0x7ffffffe,
+ 0x7fffffff,
+ 0x80000000,
+ 0x80000001,
+ #0xc717a08d,
+ 0xfffffffe,
+ 0xffffffff,
+ 0x100000000,
+ 0x100000001,
+ 0x3ffffffffffffffe,
+ 0x3fffffffffffffff,
+ 0x4000000000000000,
+ 0x4000000000000001,
+ 0xfffffffffffffffe,
+ 0xffffffffffffffff,
+ 0x10000000000000000,
+ 0x10000000000000001,
+ #0xffffffffffffffffffffffff,
+ #0x1000000000000000000000000,
+ #0x1000000000000000000000001,
+ #0xffffffffffffffffffffffffffffffffffffffffffffffff,
+ #0x1000000000000000000000000000000000000000000000000,
+ #0x1000000000000000000000000000000000000000000000001
+ ]
+ VS.reverse!
+
+ FLAGS = [[nil, ' '], [nil, '#'], [nil, '+'], [nil, '-'], [nil, '0']]
+
+ def self.assertions_format_integer(format, type, **opts)
+ proc {
+ VS.each {|v|
+ begin
+ r = Bug::Printf.(type, v, **opts)
+ rescue RangeError
+ else
+ e = sprintf format, v
+ assert_equal([e, format], r, "rb_sprintf(#{format.dump}, #{v})")
+ end
+ }
+ }
+ end
+
+ AllPairs.each(%w[d],
+ # octal and hexadecimal deal with negative values differently
+ [nil, 0, 5, 20],
+ [nil, true, 0], # 8, 20
+ *FLAGS) {
+ |type, width, prec, sp, hs, pl, mi, zr|
+ precision = ".#{prec unless prec == true}" if prec
+ format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}"
+ define_method("test_format_integer(#{format})",
+ assertions_format_integer(format, type,
+ space: sp, hash: hs,
+ plus: pl, minus: mi,
+ zero: zr, width: width,
+ prec: prec))
+ }
+
+ def test_string_prec
+ assert_equal("a", Bug::Printf.("s", "a", prec: 3)[0])
+ assert_equal(" a", Bug::Printf.("s", "a", width: 3, prec: 3)[0])
+ assert_equal("a ", Bug::Printf.("s", "a", minus: true, width: 3, prec: 3)[0])
+ end
+
+ def test_snprintf_count
+ assert_equal(3, Bug::Printf.sncount("foo"))
+ end
+end
diff --git a/test/-ext-/test_random.rb b/test/-ext-/test_random.rb
new file mode 100644
index 0000000000..e5cebcc871
--- /dev/null
+++ b/test/-ext-/test_random.rb
@@ -0,0 +1,45 @@
+require 'test/unit'
+
+module TestRandomExt
+ def setup
+ super
+ assert_nothing_raised(LoadError) {require '-test-/random'}
+ end
+
+ class TestLoop < Test::Unit::TestCase
+ include TestRandomExt
+
+ def test_bytes
+ rnd = Bug::Random::Loop.new(1)
+ assert_equal("\1", rnd.bytes(1))
+ end
+
+ def test_rand
+ rnd = Bug::Random::Loop.new(1)
+ assert_equal(1, rnd.rand(10))
+ end
+
+ def test_real
+ assert_equal(0.25, Bug::Random::Loop.new(1<<14).rand)
+ assert_equal(0.50, Bug::Random::Loop.new(2<<14).rand)
+ assert_equal(0.75, Bug::Random::Loop.new(3<<14).rand)
+ assert_equal(1.00, Bug::Random::Loop.new(4<<14).rand)
+ end
+ end
+
+ class TestVersionZero < Test::Unit::TestCase
+ include TestRandomExt
+
+ def test_bad_version
+ assert_raise(TypeError) {Bug::Random::VersionZero.new}
+ end
+ end
+
+ class TestVersionMax < Test::Unit::TestCase
+ include TestRandomExt
+
+ def test_bad_version
+ assert_raise(TypeError) {Bug::Random::VersionMax.new}
+ end
+ end
+end
diff --git a/test/-ext-/test_recursion.rb b/test/-ext-/test_recursion.rb
new file mode 100644
index 0000000000..db7a853988
--- /dev/null
+++ b/test/-ext-/test_recursion.rb
@@ -0,0 +1,36 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestRecursion < Test::Unit::TestCase
+ require '-test-/recursion'
+
+ def setup
+ @obj = Struct.new(:visited).new(false)
+ @obj.extend(Bug::Recursive)
+ end
+
+ def test_recursive
+ def @obj.doit
+ self.visited = true
+ exec_recursive(:doit)
+ raise "recursive"
+ end
+ assert_raise_with_message(RuntimeError, "recursive") {
+ @obj.exec_recursive(:doit)
+ }
+ assert(@obj.visited, "obj.hash was not called")
+ end
+
+ def test_recursive_outer
+ def @obj.doit
+ self.visited = true
+ exec_recursive_outer(:doit)
+ raise "recursive_outer should short circuit intermediate calls"
+ end
+ assert_nothing_raised {
+ @obj.exec_recursive_outer(:doit)
+ }
+ assert(@obj.visited, "obj.hash was not called")
+ end
+end
diff --git a/test/-ext-/test_scan_args.rb b/test/-ext-/test_scan_args.rb
new file mode 100644
index 0000000000..92d696d69e
--- /dev/null
+++ b/test/-ext-/test_scan_args.rb
@@ -0,0 +1,259 @@
+require 'test/unit'
+require '-test-/scan_args'
+
+class TestScanArgs < Test::Unit::TestCase
+ def test_lead
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead()}
+ assert_equal([1, "a"], Bug::ScanArgs.lead("a"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead("a", "b")}
+ end
+
+ def test_opt
+ assert_equal([0, nil], Bug::ScanArgs.opt())
+ assert_equal([1, "a"], Bug::ScanArgs.opt("a"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt("a", "b")}
+ end
+
+ def test_lead_opt
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt()}
+ assert_equal([1, "a", nil], Bug::ScanArgs.lead_opt("a"))
+ assert_equal([2, "a", "b"], Bug::ScanArgs.lead_opt("a", "b"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt("a", "b", "c")}
+ end
+
+ def test_var
+ assert_equal([0, []], Bug::ScanArgs.var())
+ assert_equal([3, ["a", "b", "c"]], Bug::ScanArgs.var("a", "b", "c"))
+ end
+
+ def test_lead_var
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var()}
+ assert_equal([3, "a", ["b", "c"]], Bug::ScanArgs.lead_var("a", "b", "c"))
+ end
+
+ def test_opt_var
+ assert_equal([0, nil, []], Bug::ScanArgs.opt_var())
+ assert_equal([3, "a", ["b", "c"]], Bug::ScanArgs.opt_var("a", "b", "c"))
+ end
+
+ def test_lead_opt_var
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var()}
+ assert_equal([3, "a", "b", ["c"]], Bug::ScanArgs.lead_opt_var("a", "b", "c"))
+ end
+
+ def test_opt_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail()}
+ assert_equal([2, "a", "b"], Bug::ScanArgs.opt_trail("a", "b"))
+ assert_equal([1, nil, "a"], Bug::ScanArgs.opt_trail("a"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail("a", "b", "c")}
+ end
+
+ def test_lead_opt_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail()}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail("a")}
+ assert_equal([2, "a", nil, "b"], Bug::ScanArgs.lead_opt_trail("a", "b"))
+ assert_equal([3, "a", "b", "c"], Bug::ScanArgs.lead_opt_trail("a", "b", "c"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail("a", "b", "c", "d")}
+ end
+
+ def test_var_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.var_trail()}
+ assert_equal([1, [], "a"], Bug::ScanArgs.var_trail("a"))
+ assert_equal([2, ["a"], "b"], Bug::ScanArgs.var_trail("a", "b"))
+ end
+
+ def test_lead_var_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail("a")}
+ assert_equal([2, "a", [], "b"], Bug::ScanArgs.lead_var_trail("a", "b"))
+ assert_equal([3, "a", ["b"], "c"], Bug::ScanArgs.lead_var_trail("a", "b", "c"))
+ end
+
+ def test_opt_var_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_var_trail()}
+ assert_equal([1, nil, [], "a"], Bug::ScanArgs.opt_var_trail("a"))
+ assert_equal([2, "a", [], "b"], Bug::ScanArgs.opt_var_trail("a", "b"))
+ assert_equal([3, "a", ["b"], "c"], Bug::ScanArgs.opt_var_trail("a", "b", "c"))
+ end
+
+ def test_lead_opt_var_trail
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail("a")}
+ assert_equal([2, "a", nil, [], "b"], Bug::ScanArgs.lead_opt_var_trail("a", "b"))
+ assert_equal([3, "a", "b", [], "c"], Bug::ScanArgs.lead_opt_var_trail("a", "b", "c"))
+ assert_equal([4, "a", "b", ["c"], "d"], Bug::ScanArgs.lead_opt_var_trail("a", "b", "c", "d"))
+ end
+
+ def test_hash
+ assert_equal([0, nil], Bug::ScanArgs.hash())
+ assert_raise(ArgumentError) {Bug::ScanArgs.hash("a")}
+ assert_equal([0, {a: 0}], Bug::ScanArgs.hash(a: 0))
+ end
+
+ def test_lead_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash()}
+ assert_equal([1, "a", nil], Bug::ScanArgs.lead_hash("a"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash("a", "b")}
+ assert_equal([1, "a", {b: 1}], Bug::ScanArgs.lead_hash("a", b: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash(b: 1)}
+ assert_equal([1, {"a"=>0, b: 1}, nil], Bug::ScanArgs.lead_hash({"a"=>0, b: 1}, **{}))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash(1, {"a"=>0, b: 1}, **{})}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash(**{})}
+ end
+
+ def test_opt_hash
+ assert_equal([0, nil, nil], Bug::ScanArgs.opt_hash())
+ assert_equal([1, "a", nil], Bug::ScanArgs.opt_hash("a"))
+ assert_equal([0, nil, {b: 1}], Bug::ScanArgs.opt_hash(b: 1))
+ assert_equal([1, "a", {b: 1}], Bug::ScanArgs.opt_hash("a", b: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_hash("a", "b")}
+ assert_equal([0, nil, {"a"=>0, b: 1}], Bug::ScanArgs.opt_hash("a"=>0, b: 1))
+ assert_equal([1, {"a"=>0, b: 1}, nil], Bug::ScanArgs.opt_hash({"a"=>0, b: 1}, **{}))
+ end
+
+ def test_lead_opt_hash
+ assert_equal([1, "a", nil, nil], Bug::ScanArgs.lead_opt_hash("a"))
+ assert_equal([2, "a", "b", nil], Bug::ScanArgs.lead_opt_hash("a", "b"))
+ assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.lead_opt_hash("a", c: 1))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.lead_opt_hash("a", "b", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_hash(c: 1)}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_hash("a", "b", "c")}
+ assert_equal([1, "a", nil, {"b"=>0, c: 1}], Bug::ScanArgs.lead_opt_hash("a", "b"=>0, c: 1))
+ end
+
+ def test_var_hash
+ assert_equal([0, [], nil], Bug::ScanArgs.var_hash())
+ assert_equal([1, ["a"], nil], Bug::ScanArgs.var_hash("a"))
+ assert_equal([1, ["a"], {b: 1}], Bug::ScanArgs.var_hash("a", b: 1))
+ assert_equal([0, [], {b: 1}], Bug::ScanArgs.var_hash(b: 1))
+ assert_equal([0, [], {"a"=>0, b: 1}], Bug::ScanArgs.var_hash("a"=>0, b: 1))
+ end
+
+ def test_lead_var_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_hash()}
+ assert_equal([1, "a", [], nil], Bug::ScanArgs.lead_var_hash("a"))
+ assert_equal([2, "a", ["b"], nil], Bug::ScanArgs.lead_var_hash("a", "b"))
+ assert_equal([2, "a", ["b"], {c: 1}], Bug::ScanArgs.lead_var_hash("a", "b", c: 1))
+ assert_equal([1, "a", [], {c: 1}], Bug::ScanArgs.lead_var_hash("a", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_hash(c: 1)}
+ assert_equal([3, "a", ["b", "c"], nil], Bug::ScanArgs.lead_var_hash("a", "b", "c"))
+ assert_equal([1, "a", [], {"b"=>0, c: 1}], Bug::ScanArgs.lead_var_hash("a", "b"=>0, c: 1))
+ end
+
+ def test_opt_var_hash
+ assert_equal([0, nil, [], nil], Bug::ScanArgs.opt_var_hash())
+ assert_equal([1, "a", [], nil], Bug::ScanArgs.opt_var_hash("a"))
+ assert_equal([2, "a", ["b"], nil], Bug::ScanArgs.opt_var_hash("a", "b"))
+ assert_equal([2, "a", ["b"], {c: 1}], Bug::ScanArgs.opt_var_hash("a", "b", c: 1))
+ assert_equal([1, "a", [], {c: 1}], Bug::ScanArgs.opt_var_hash("a", c: 1))
+ assert_equal([0, nil, [], {c: 1}], Bug::ScanArgs.opt_var_hash(c: 1))
+ assert_equal([3, "a", ["b", "c"], nil], Bug::ScanArgs.opt_var_hash("a", "b", "c"))
+ assert_equal([1, "a", [], {"b"=>0, c: 1}], Bug::ScanArgs.opt_var_hash("a", "b"=>0, c: 1))
+ end
+
+ def test_lead_opt_var_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_hash()}
+ assert_equal([1, "a", nil, [], nil], Bug::ScanArgs.lead_opt_var_hash("a"))
+ assert_equal([2, "a", "b", [], nil], Bug::ScanArgs.lead_opt_var_hash("a", "b"))
+ assert_equal([2, "a", "b", [], {c: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", c: 1))
+ assert_equal([1, "a", nil, [], {c: 1}], Bug::ScanArgs.lead_opt_var_hash("a", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_hash(c: 1)}
+ assert_equal([3, "a", "b", ["c"], nil], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c"))
+ assert_equal([3, "a", "b", ["c"], {d: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c", d: 1))
+ assert_equal([2, "a", "b", [], {"c"=>0, d: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c"=>0, d: 1))
+ end
+
+ def test_opt_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail_hash()}
+ assert_equal([1, nil, "a", nil], Bug::ScanArgs.opt_trail_hash("a"))
+ assert_equal([2, "a", "b", nil], Bug::ScanArgs.opt_trail_hash("a", "b"))
+ assert_equal([1, nil, "a", {c: 1}], Bug::ScanArgs.opt_trail_hash("a", c: 1))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.opt_trail_hash("a", "b", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail_hash(c: 1)}
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail_hash("a", "b", "c")}
+ assert_equal([1, nil, "a", {"b"=>0, c: 1}], Bug::ScanArgs.opt_trail_hash("a", "b"=>0, c: 1))
+ end
+
+ def test_lead_opt_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash()}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash("a")}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash(c: 1)}
+ assert_equal([2, "a", nil, "b", nil], Bug::ScanArgs.lead_opt_trail_hash("a", "b"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash("a", c: 1)}
+ assert_equal([2, "a", nil, "b", {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", c: 1))
+ assert_equal([3, "a", "b", "c", nil], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c"))
+ assert_equal([3, "a", "b", "c", {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c", "d")}
+ assert_equal([2, "a", nil, "b", {"c"=>0, c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c"=>0, c: 1))
+ end
+
+ def test_var_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.var_trail_hash()}
+ assert_equal([1, [], "a", nil], Bug::ScanArgs.var_trail_hash("a"))
+ assert_equal([2, ["a"], "b", nil], Bug::ScanArgs.var_trail_hash("a", "b"))
+ assert_equal([1, [], "a", {c: 1}], Bug::ScanArgs.var_trail_hash("a", c: 1))
+ assert_equal([2, ["a"], "b", {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", c: 1))
+ assert_raise(ArgumentError) {Bug::ScanArgs.var_trail_hash(c: 1)}
+ assert_equal([3, ["a", "b"], "c", nil], Bug::ScanArgs.var_trail_hash("a", "b", "c"))
+ assert_equal([3, ["a", "b"], "c", {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", "c", c: 1))
+ assert_equal([2, ["a"], "b", {"c"=>0, c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", "c"=>0, c: 1))
+ end
+
+ def test_lead_var_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash()}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash("a")}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash(c: 1)}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash("a", c: 1)}
+ assert_equal([2, "a", [], "b", nil], Bug::ScanArgs.lead_var_trail_hash("a", "b"))
+ assert_equal([2, "a", [], "b", {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", c: 1))
+ assert_equal([3, "a", ["b"], "c", nil], Bug::ScanArgs.lead_var_trail_hash("a", "b", "c"))
+ assert_equal([3, "a", ["b"], "c", {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", "c", c: 1))
+ assert_equal([2, "a", [], "b", {"c"=>0, c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", c: 1, "c"=>0))
+ end
+
+ def test_opt_var_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_var_trail_hash()}
+ assert_equal([1, nil, [], "a", nil], Bug::ScanArgs.opt_var_trail_hash("a"))
+ assert_raise(ArgumentError) {Bug::ScanArgs.opt_var_trail_hash(c: 1)}
+ assert_equal([1, nil, [], "a", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", c: 1))
+ assert_equal([2, "a", [], "b", nil], Bug::ScanArgs.opt_var_trail_hash("a", "b"))
+ assert_equal([2, "a", [], "b", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", c: 1))
+ assert_equal([3, "a", ["b"], "c", nil], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c"))
+ assert_equal([3, "a", ["b"], "c", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c", c: 1))
+ assert_equal([2, "a", [], "b", {"c"=>0, c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c"=>0, c: 1))
+ end
+
+ def test_lead_opt_var_trail_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail_hash()}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail_hash("a")}
+ assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail_hash("a", b: 1)}
+ assert_equal([2, "a", nil, [], "b", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b"))
+ assert_equal([2, "a", nil, [], "b", {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", c: 1))
+ assert_equal([3, "a", "b", [], "c", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c"))
+ assert_equal([3, "a", "b", [], "c", {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", c: 1))
+ assert_equal([4, "a", "b", ["c"], "d", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", "d"))
+ assert_equal([3, "a", "b", [], "c", {"d"=>0, c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", "d"=>0, c: 1))
+ end
+
+ def test_k_lead_opt_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.k_lead_opt_hash}
+ assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", c: 1))
+ assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", {c: 1}))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b", c: 1))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b", {c: 1}))
+ assert_raise(ArgumentError) {Bug::ScanArgs.k_lead_opt_hash(c: 1)}
+ assert_equal([1, "a", nil, {"b"=>0, c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b"=>0, c: 1))
+ end
+
+ def test_n_lead_opt_hash
+ assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash}
+ assert_equal([1, "a", nil, nil], Bug::ScanArgs.n_lead_opt_hash("a"))
+ assert_equal([2, "a", "b", nil], Bug::ScanArgs.n_lead_opt_hash("a", "b"))
+ assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", c: 1))
+ assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", {c: 1}))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b", c: 1))
+ assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b", {c: 1}))
+ assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash(c: 1)}
+ assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash({c: 1})}
+ assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash("a", "b", "c")}
+ assert_equal([1, "a", nil, {"b"=>0, c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b"=>0, c: 1))
+ 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
new file mode 100644
index 0000000000..ba41069304
--- /dev/null
+++ b/test/-ext-/thread/test_instrumentation_api.rb
@@ -0,0 +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'
+
+ cleanup_threads
+ end
+
+ def teardown
+ return if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
+ Bug::ThreadInstrumentation.unregister_callback
+ cleanup_threads
+ end
+
+ THREADS_COUNT = 3
+
+ 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_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 = full_timeline = nil
+ IO.popen("-") do |read_pipe|
+ if read_pipe
+ thread_statuses = Marshal.load(read_pipe)
+ full_timeline = Marshal.load(read_pipe)
+ else
+ threads = threaded_cpu_bound_work.each(&:join)
+ Marshal.dump(threads.map(&:status), 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
+ 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
+ assert Bug::ThreadInstrumentation::register_and_unregister_callbacks
+ end
+
+ private
+
+ def fib(n = 30)
+ return n if n <= 1
+ fib(n-1) + fib(n-2)
+ end
+
+ 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 threaded_cpu_bound_work(duration = 0.5)
+ THREADS_COUNT.times.map do
+ Thread.new do
+ cpu_bound_work(duration)
+ end
+ end
+ end
+
+ 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-/time/test_new.rb b/test/-ext-/time/test_new.rb
new file mode 100644
index 0000000000..8283bd6828
--- /dev/null
+++ b/test/-ext-/time/test_new.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/time"
+
+class Bug::Time::Test_New < Test::Unit::TestCase
+ def test_nano_new
+ assert_equal(Time.at(1447087832, 476451.125), Bug::Time.nano_new(1447087832, 476451125))
+ assert_not_equal(Time.at(1447087832, 476451.325), Bug::Time.nano_new(1447087832, 476451125))
+ assert_equal(false, Bug::Time.nano_new(1447087832, 476451125).utc?)
+ end
+
+ def assert_time_equal(a, b, msg=nil)
+ assert_equal(a, b, msg)
+ assert_equal(a.gmtoff, b.gmtoff, msg)
+ assert_equal(a.utc?, b.utc?, msg)
+ end
+
+ def test_timespec_new
+ assert_time_equal(Time.at(1447087832, 476451.125).localtime(32400),
+ Bug::Time.timespec_new(1447087832, 476451125, 32400))
+ assert_not_equal(Time.at(1447087832, 476451.128).localtime(32400),
+ Bug::Time.timespec_new(1447087832, 476451125, 32400))
+ assert_equal(false, Bug::Time.timespec_new(1447087832, 476451125, 0).utc?)
+ assert_equal(true, Bug::Time.timespec_new(1447087832, 476451125, 0x7ffffffe).utc?)
+ assert_equal(false, Bug::Time.timespec_new(1447087832, 476451125, 0x7fffffff).utc?)
+ # Cannot compare Time.now.gmtoff with
+ # Bug::Time.timespec_new(1447087832, 476451125, 0x7fffffff).gmtoff, because
+ # it depends on whether the current time is in summer time (daylight-saving time) or not.
+ t = Time.now
+ assert_equal(t.gmtoff, Bug::Time.timespec_new(t.tv_sec, t.tv_nsec, 0x7fffffff).gmtoff)
+ assert_time_equal(Time.at(1447087832, 476451.125).localtime(86399),
+ Bug::Time.timespec_new(1447087832, 476451125, 86399))
+ assert_time_equal(Time.at(1447087832, 476451.125).localtime(-86399),
+ Bug::Time.timespec_new(1447087832, 476451125, -86399))
+ assert_raise(ArgumentError){Bug::Time.timespec_new(1447087832, 476451125, 86400)}
+ assert_raise(ArgumentError){Bug::Time.timespec_new(1447087832, 476451125,-86400)}
+ end
+
+ def test_timespec_now
+ t0 = Time.now.to_r
+ t = Bug::Time.timespec_now
+ assert_in_delta 3, t0, t
+ end
+end
diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb
new file mode 100644
index 0000000000..603fd01fd5
--- /dev/null
+++ b/test/-ext-/tracepoint/test_tracepoint.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: false
+require 'test/unit'
+require '-test-/tracepoint'
+
+class TestTracepointObj < Test::Unit::TestCase
+ def test_not_available_from_ruby
+ assert_raise ArgumentError do
+ TracePoint.trace(:obj_new){}
+ end
+ end
+
+ 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'
+ _="foobar"
+ nil
+ }
+ EOS
+
+ newobj_count, free_count, gc_start_count, gc_end_mark_count, gc_end_sweep_count, *newobjs = *result
+ assert_equal 1, newobj_count
+ assert_equal 1, newobjs.size
+ assert_equal 'foobar', newobjs[0]
+ assert_operator free_count, :>=, 0
+ assert_operator gc_start_count, :==, gc_end_mark_count
+ assert_operator gc_start_count, :>=, gc_end_sweep_count
+ end
+
+ def test_tracks_objspace_count
+ stat1 = {}
+ stat2 = {}
+ GC.disable
+ GC.stat(stat1)
+ result = Bug.tracepoint_track_objspace_events{
+ GC.enable
+ 1_000_000.times{''}
+ GC.disable
+ }
+ GC.stat(stat2)
+ GC.enable
+
+ newobj_count, free_count, gc_start_count, gc_end_mark_count, gc_end_sweep_count, = *result
+
+ assert_operator stat2[:total_allocated_objects] - stat1[:total_allocated_objects], :>=, newobj_count
+ assert_operator 1_000_000, :<=, newobj_count
+
+ assert_operator stat2[:total_freed_objects] + stat2[:heap_final_slots] - stat1[:total_freed_objects], :>=, free_count
+ assert_operator stat2[:count] - stat1[:count], :==, gc_start_count
+
+ assert_operator gc_start_count, :==, gc_end_mark_count
+ assert_operator gc_start_count, :>=, gc_end_sweep_count
+ assert_operator stat2[:count] - stat1[:count] - 1, :<=, gc_end_sweep_count
+ end
+
+ def test_tracepoint_specify_normal_and_internal_events
+ assert_raise(TypeError){ Bug.tracepoint_specify_normal_and_internal_events }
+ end
+
+ def test_after_gc_start_hook_with_GC_stress
+ bug8492 = '[ruby-dev:47400] [Bug #8492]: infinite after_gc_start_hook reentrance'
+ assert_nothing_raised(Timeout::Error, bug8492) do
+ assert_in_out_err(%w[-r-test-/tracepoint], <<-'end;', /\A[1-9]/, timeout: 2)
+ 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
+ end
+ run(hook)
+ puts count
+ end;
+ end
+ end
+
+ def test_teardown_with_active_GC_end_hook
+ assert_ruby_status([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start')
+ end
+
+end
diff --git a/test/-ext-/typeddata/test_typeddata.rb b/test/-ext-/typeddata/test_typeddata.rb
new file mode 100644
index 0000000000..e32b030a35
--- /dev/null
+++ b/test/-ext-/typeddata/test_typeddata.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: false
+require 'test/unit'
+require "-test-/typeddata"
+
+class Test_TypedData < Test::Unit::TestCase
+ def test_wrong_argtype
+ assert_raise_with_message(TypeError, "wrong argument type false (expected typed_data)") {Bug::TypedData.check(false)}
+
+ assert_raise_with_message(TypeError, "wrong argument type true (expected typed_data)") {Bug::TypedData.check(true)}
+
+ assert_raise_with_message(TypeError, "wrong argument type Symbol (expected typed_data)") {Bug::TypedData.check(:e)}
+
+ assert_raise_with_message(TypeError, "wrong argument type Integer (expected typed_data)") {Bug::TypedData.check(0)}
+
+ assert_raise_with_message(TypeError, "wrong argument type String (expected typed_data)") {Bug::TypedData.check("a")}
+
+ obj = eval("class C\u{1f5ff}; self; end").new
+ assert_raise_with_message(TypeError, /C\u{1f5ff}/) {Bug::TypedData.check(obj)}
+ end
+
+ def test_deferred_free
+ assert_ruby_status([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ require "-test-/typeddata"
+ begin;
+ n = 1 << 20
+ Bug::TypedData.make(n)
+ end;
+ end
+end
diff --git a/test/-ext-/vm/test_at_exit.rb b/test/-ext-/vm/test_at_exit.rb
new file mode 100644
index 0000000000..61ad831792
--- /dev/null
+++ b/test/-ext-/vm/test_at_exit.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: false
+class TestVM < Test::Unit::TestCase
+
+ # [Bug #12095]
+ def test_at_exit
+
+ assert_in_out_err([], <<-"end;", %w[begin end]) # do
+ require '-test-/vm/at_exit'
+ Bug::VM.register_at_exit(false)
+ 1000.times do
+ Bug::VM.register_at_exit(nil)
+ ["x"]*1000
+ end
+ GC.start
+ Bug::VM.register_at_exit(true)
+ end;
+ end
+end
+
diff --git a/test/-ext-/wait/test_wait.rb b/test/-ext-/wait/test_wait.rb
new file mode 100644
index 0000000000..8e53f067cf
--- /dev/null
+++ b/test/-ext-/wait/test_wait.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestWait < Test::Unit::TestCase
+ require '-test-/wait'
+
+ def test_wait_for_valid_fd
+ IO.pipe do |r,w|
+ rc = IO.io_wait(w, IO::WRITABLE, nil)
+ assert_equal IO::WRITABLE, rc
+ end
+ end
+
+ def test_wait_for_invalid_fd
+ assert_separately [], <<~'RUBY'
+ require '-test-/wait'
+
+ r, w = IO.pipe
+ r.close
+
+ IO.for_fd(w.fileno).close
+
+ assert_raise(Errno::EBADF) do
+ IO.io_wait(w, IO::WRITABLE, nil)
+ end
+ RUBY
+ end
+
+ def test_wait_for_closed_pipe
+ IO.pipe do |r,w|
+ w.close
+ rc = IO.io_wait(r, IO::READABLE, nil)
+ assert_equal IO::READABLE, rc
+ end
+ end
+end
diff --git a/test/-ext-/win32/test_console_attr.rb b/test/-ext-/win32/test_console_attr.rb
new file mode 100644
index 0000000000..fe72d988d5
--- /dev/null
+++ b/test/-ext-/win32/test_console_attr.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: false
+if /mswin|mingw/ =~ RUBY_PLATFORM and STDOUT.tty?
+ require '-test-/win32/console'
+ require 'io/console'
+ require 'test/unit'
+
+ class Test_Win32Console < Test::Unit::TestCase
+ REVERSE_VIDEO = Bug::Win32::REVERSE_VIDEO
+
+ def reverse_video(fore, back = 0x0)
+ info = Bug::Win32.console_info(STDOUT)
+ if (info.attr & REVERSE_VIDEO) == 0
+ (fore << 4) | back
+ else
+ (back << 4) | fore | REVERSE_VIDEO
+ end
+ end
+
+ def reset
+ Bug::Win32.console_attribute(STDOUT, 7)
+ end
+
+ alias setup reset
+ alias teardown reset
+
+ def test_default
+ info = Bug::Win32.console_info(STDOUT)
+ assert_equal(7, info.attr);
+ end
+
+ def test_reverse
+ print "\e[7m"
+ info = Bug::Win32.console_info(STDOUT)
+ assert_equal(reverse_video(0x7), info.attr);
+ end
+
+ def test_bold
+ print "\e[1m"
+ info = Bug::Win32.console_info(STDOUT)
+ assert_equal(0x8, info.attr&0x8);
+ end
+
+ def test_bold_reverse
+ print "\e[1;7m"
+ info = Bug::Win32.console_info(STDOUT)
+ assert_equal(reverse_video(0xf), info.attr);
+ end
+
+ def test_reverse_bold
+ print "\e[7;1m"
+ info = Bug::Win32.console_info(STDOUT)
+ assert_equal(reverse_video(0xf), info.attr);
+ end
+ end
+end
diff --git a/test/-ext-/win32/test_dln.rb b/test/-ext-/win32/test_dln.rb
new file mode 100644
index 0000000000..e8f68ac4d4
--- /dev/null
+++ b/test/-ext-/win32/test_dln.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tmpdir'
+require 'rbconfig'
+
+module Bug
+ module Win32
+ class TestDln < Test::Unit::TestCase
+ def test_check_imported
+ bug = '[Bug #6303]'
+ so = ::File.expand_path("../ext/-test-/win32/dln/dlntest.dll", ::EnvUtil.rubybin)
+ assert_send([::File, :file?, so])
+ path = ::ENV['PATH']
+ path = ::File.dirname(so) + ::File::PATH_SEPARATOR + path
+ assert_in_out_err([{'PATH'=>path}, '-r-test-/win32/dln', '-eexit'], '', [], [], bug, timeout: 10)
+ end
+
+ def test_nonascii_load
+ bug9699 = '[ruby-core:61845] [Bug #9699]'
+ so = "-test-/dln/empty." + ::RbConfig::CONFIG["DLEXT"]
+ so = $:.find {|d| d = ::File.join(d, so); break d if ::File.exist?(d)}
+ assert_not_nil(so)
+ ::Dir.mkdir(dir = ::File.join(testdir = ::Dir.mktmpdir("test"), "\u{30c6 30b9 30c8}"))
+ ::File.copy_stream(so, ::File.join(dir, ::File.basename(so)))
+ assert_separately(['-', bug9699, testdir, ::File.basename(so)], <<-'end;')
+ bug, dir, so = *ARGV
+ assert_nothing_raised(LoadError, bug) do
+ require ::File.join(dir, "\u{30c6 30b9 30c8}", so)
+ end
+ end;
+ ensure
+ ::File.unlink(::File.join(dir, ::File.basename(so))) rescue nil
+ ::Dir.rmdir(dir) rescue nil
+ ::Dir.rmdir(testdir) rescue nil
+ end
+
+ end
+ end
+end if /mswin|mingw/ =~ RUBY_PLATFORM
diff --git a/test/-ext-/win32/test_fd_setsize.rb b/test/-ext-/win32/test_fd_setsize.rb
new file mode 100644
index 0000000000..4c64376f15
--- /dev/null
+++ b/test/-ext-/win32/test_fd_setsize.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+module Bug
+ module Win32
+ class TestFdSetSize < Test::Unit::TestCase
+ def test_select_with_unmatched_fd_setsize
+ bug6532 = '[ruby-core:44588]'
+ assert_in_out_err([], <<-INPUT, %w(:ok), [], bug6532)
+ require '-test-/win32/fd_setsize'
+ Bug::Win32.test_select
+ p :ok
+ INPUT
+ end
+
+ def test_fdset_with_unmatched_fd_setsize
+ bug6532 = '[ruby-core:44588]'
+ assert_in_out_err([], <<-INPUT, %w(:ok), [], bug6532)
+ require '-test-/win32/fd_setsize'
+ p :ok if Bug::Win32.test_fdset
+ INPUT
+ end
+ end
+ end
+end if /mswin|mingw/ =~ RUBY_PLATFORM