diff options
Diffstat (limited to 'test/-ext-')
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 |
