summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/enc/test_case_comprehensive.rb2
-rw-r--r--test/ruby/enc/test_case_mapping.rb10
-rw-r--r--test/ruby/enc/test_case_options.rb12
-rw-r--r--test/ruby/enc/test_cesu8.rb4
-rw-r--r--test/ruby/enc/test_emoji_breaks.rb5
-rw-r--r--test/ruby/enc/test_grapheme_breaks.rb2
-rw-r--r--test/ruby/enc/test_regex_casefold.rb2
-rw-r--r--test/ruby/rjit/test_assembler.rb368
-rw-r--r--test/ruby/test_allocation.rb656
-rw-r--r--test/ruby/test_argf.rb121
-rw-r--r--test/ruby/test_arity.rb1
-rw-r--r--test/ruby/test_array.rb154
-rw-r--r--test/ruby/test_ast.rb709
-rw-r--r--test/ruby/test_autoload.rb61
-rw-r--r--test/ruby/test_backtrace.rb60
-rw-r--r--test/ruby/test_beginendblock.rb14
-rw-r--r--test/ruby/test_bignum.rb9
-rw-r--r--test/ruby/test_call.rb1221
-rw-r--r--test/ruby/test_class.rb52
-rw-r--r--test/ruby/test_clone.rb53
-rw-r--r--test/ruby/test_comparable.rb10
-rw-r--r--test/ruby/test_compile_prism.rb2671
-rw-r--r--test/ruby/test_complex.rb207
-rw-r--r--test/ruby/test_continuation.rb4
-rw-r--r--test/ruby/test_data.rb283
-rw-r--r--test/ruby/test_default_gems.rb21
-rw-r--r--test/ruby/test_defined.rb26
-rw-r--r--test/ruby/test_dir.rb198
-rw-r--r--test/ruby/test_dir_m17n.rb46
-rw-r--r--test/ruby/test_dup.rb110
-rw-r--r--test/ruby/test_econv.rb2
-rw-r--r--test/ruby/test_encoding.rb35
-rw-r--r--test/ruby/test_enum.rb14
-rw-r--r--test/ruby/test_enumerator.rb137
-rw-r--r--test/ruby/test_env.rb60
-rw-r--r--test/ruby/test_eval.rb7
-rw-r--r--test/ruby/test_exception.rb208
-rw-r--r--test/ruby/test_fiber.rb18
-rw-r--r--test/ruby/test_file.rb290
-rw-r--r--test/ruby/test_file_exhaustive.rb15
-rw-r--r--test/ruby/test_float.rb20
-rw-r--r--test/ruby/test_frozen.rb30
-rw-r--r--test/ruby/test_gc.rb441
-rw-r--r--test/ruby/test_gc_compact.rb324
-rw-r--r--test/ruby/test_hash.rb759
-rw-r--r--test/ruby/test_integer.rb117
-rw-r--r--test/ruby/test_integer_comb.rb23
-rw-r--r--test/ruby/test_io.rb244
-rw-r--r--test/ruby/test_io_buffer.rb267
-rw-r--r--test/ruby/test_io_m17n.rb114
-rw-r--r--test/ruby/test_io_timeout.rb58
-rw-r--r--test/ruby/test_iseq.rb117
-rw-r--r--test/ruby/test_keyword.rb341
-rw-r--r--test/ruby/test_lambda.rb26
-rw-r--r--test/ruby/test_lazy_enumerator.rb25
-rw-r--r--test/ruby/test_literal.rb22
-rw-r--r--test/ruby/test_m17n.rb72
-rw-r--r--test/ruby/test_marshal.rb42
-rw-r--r--test/ruby/test_math.rb10
-rw-r--r--test/ruby/test_method.rb363
-rw-r--r--test/ruby/test_mjit.rb1273
-rw-r--r--test/ruby/test_mjit_debug.rb17
-rw-r--r--test/ruby/test_module.rb95
-rw-r--r--test/ruby/test_nomethod_error.rb2
-rw-r--r--test/ruby/test_numeric.rb3
-rw-r--r--test/ruby/test_object.rb84
-rw-r--r--test/ruby/test_objectspace.rb8
-rw-r--r--test/ruby/test_optimization.rb88
-rw-r--r--test/ruby/test_pack.rb110
-rw-r--r--test/ruby/test_parse.rb503
-rw-r--r--test/ruby/test_pattern_matching.rb60
-rw-r--r--test/ruby/test_proc.rb83
-rw-r--r--test/ruby/test_process.rb379
-rw-r--r--test/ruby/test_rand.rb8
-rw-r--r--test/ruby/test_random_formatter.rb55
-rw-r--r--test/ruby/test_range.rb372
-rw-r--r--test/ruby/test_rational.rb2
-rw-r--r--test/ruby/test_refinement.rb135
-rw-r--r--test/ruby/test_regexp.rb683
-rw-r--r--test/ruby/test_require.rb78
-rw-r--r--test/ruby/test_require_lib.rb31
-rw-r--r--test/ruby/test_rubyoptions.rb388
-rw-r--r--test/ruby/test_rubyvm.rb2
-rw-r--r--test/ruby/test_rubyvm_mjit.rb91
-rw-r--r--test/ruby/test_settracefunc.rb429
-rw-r--r--test/ruby/test_shapes.rb1040
-rw-r--r--test/ruby/test_signal.rb43
-rw-r--r--test/ruby/test_sprintf.rb17
-rw-r--r--test/ruby/test_stack.rb1
-rw-r--r--test/ruby/test_string.rb785
-rw-r--r--test/ruby/test_string_memory.rb55
-rw-r--r--test/ruby/test_struct.rb22
-rw-r--r--test/ruby/test_super.rb12
-rw-r--r--test/ruby/test_symbol.rb22
-rw-r--r--test/ruby/test_syntax.rb421
-rw-r--r--test/ruby/test_system.rb13
-rw-r--r--test/ruby/test_thread.rb79
-rw-r--r--test/ruby/test_thread_cv.rb6
-rw-r--r--test/ruby/test_thread_queue.rb89
-rw-r--r--test/ruby/test_time.rb133
-rw-r--r--test/ruby/test_time_tz.rb36
-rw-r--r--test/ruby/test_transcode.rb514
-rw-r--r--test/ruby/test_variable.rb130
-rw-r--r--test/ruby/test_vm_dump.rb7
-rw-r--r--test/ruby/test_weakkeymap.rb145
-rw-r--r--test/ruby/test_weakmap.rb91
-rw-r--r--test/ruby/test_whileuntil.rb18
-rw-r--r--test/ruby/test_yjit.rb1104
-rw-r--r--test/ruby/test_yjit_exit_locations.rb96
109 files changed, 17437 insertions, 3714 deletions
diff --git a/test/ruby/enc/test_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb
index bc57d57ee4..de18ac865c 100644
--- a/test/ruby/enc/test_case_comprehensive.rb
+++ b/test/ruby/enc/test_case_comprehensive.rb
@@ -37,7 +37,7 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC
end
def self.read_data_file(filename)
- IO.foreach(expand_filename(filename), encoding: Encoding::ASCII_8BIT) do |line|
+ File.foreach(expand_filename(filename), encoding: Encoding::ASCII_8BIT) do |line|
if $. == 1
if filename == 'UnicodeData'
elsif line.start_with?("# #{filename}-#{UNICODE_VERSION}.txt")
diff --git a/test/ruby/enc/test_case_mapping.rb b/test/ruby/enc/test_case_mapping.rb
index 31acdc4331..a7d1ed0d16 100644
--- a/test/ruby/enc/test_case_mapping.rb
+++ b/test/ruby/enc/test_case_mapping.rb
@@ -47,7 +47,7 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase
# different properties; careful: roundtrip isn't always guaranteed
def check_swapcase_properties(expected, start, *flags)
assert_equal expected, start.swapcase(*flags)
- temp = start
+ temp = +start
assert_equal expected, temp.swapcase!(*flags)
assert_equal start, start.swapcase(*flags).swapcase(*flags)
assert_equal expected, expected.swapcase(*flags).swapcase(*flags)
@@ -61,10 +61,10 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase
end
def test_invalid
- assert_raise(ArgumentError, "Should not be possible to upcase invalid string.") { "\xEB".force_encoding('UTF-8').upcase }
- assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".force_encoding('UTF-8').downcase }
- assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".force_encoding('UTF-8').capitalize }
- assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".force_encoding('UTF-8').swapcase }
+ assert_raise(ArgumentError, "Should not be possible to upcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').upcase }
+ assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').downcase }
+ assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".dup.force_encoding('UTF-8').capitalize }
+ assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').swapcase }
end
def test_general
diff --git a/test/ruby/enc/test_case_options.rb b/test/ruby/enc/test_case_options.rb
index e9bf50fcfc..e9c81d804e 100644
--- a/test/ruby/enc/test_case_options.rb
+++ b/test/ruby/enc/test_case_options.rb
@@ -19,7 +19,7 @@ class TestCaseOptions < Test::Unit::TestCase
def assert_raise_both_types(*options)
assert_raise_functional_operations 'a', *options
- assert_raise_bang_operations 'a', *options
+ assert_raise_bang_operations(+'a', *options)
assert_raise_functional_operations :a, *options
end
@@ -51,7 +51,7 @@ class TestCaseOptions < Test::Unit::TestCase
def assert_okay_both_types(*options)
assert_okay_functional_operations 'a', *options
- assert_okay_bang_operations 'a', *options
+ assert_okay_bang_operations(+'a', *options)
assert_okay_functional_operations :a, *options
end
@@ -69,10 +69,10 @@ class TestCaseOptions < Test::Unit::TestCase
assert_raise(ArgumentError) { 'a'.upcase :fold }
assert_raise(ArgumentError) { 'a'.capitalize :fold }
assert_raise(ArgumentError) { 'a'.swapcase :fold }
- assert_nothing_raised { 'a'.downcase! :fold }
- assert_raise(ArgumentError) { 'a'.upcase! :fold }
- assert_raise(ArgumentError) { 'a'.capitalize! :fold }
- assert_raise(ArgumentError) { 'a'.swapcase! :fold }
+ assert_nothing_raised { 'a'.dup.downcase! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.upcase! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.capitalize! :fold }
+ assert_raise(ArgumentError) { 'a'.dup.swapcase! :fold }
assert_nothing_raised { :a.downcase :fold }
assert_raise(ArgumentError) { :a.upcase :fold }
assert_raise(ArgumentError) { :a.capitalize :fold }
diff --git a/test/ruby/enc/test_cesu8.rb b/test/ruby/enc/test_cesu8.rb
index d9debe76cd..68a08389ea 100644
--- a/test/ruby/enc/test_cesu8.rb
+++ b/test/ruby/enc/test_cesu8.rb
@@ -106,4 +106,8 @@ EOT
assert_equal chr, ord.chr("cesu-8")
end
end
+
+ def test_cesu8_left_adjust_char_head
+ assert_equal("", "\u{10000}".encode("cesu-8").chop)
+ end
end
diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb
index c96d6088f5..bb5114680e 100644
--- a/test/ruby/enc/test_emoji_breaks.rb
+++ b/test/ruby/enc/test_emoji_breaks.rb
@@ -75,7 +75,7 @@ class TestEmojiBreaks < Test::Unit::TestCase
EMOJI_DATA_FILES.each do |file|
version_mismatch = true
file_tests = []
- IO.foreach(file.fullname, encoding: Encoding::UTF_8) do |line|
+ File.foreach(file.fullname, encoding: Encoding::UTF_8) do |line|
line.chomp!
if $.==1
if line=="# #{file.basename}-#{file.version}.txt"
@@ -84,7 +84,8 @@ class TestEmojiBreaks < Test::Unit::TestCase
raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt"
end
end
- version_mismatch = false if line =~ /^# Version: #{file.version}/
+ version_mismatch = false if line =~ /^# Version: #{file.version}/ # 13.0 and older
+ version_mismatch = false if line =~ /^# Used with Emoji Version #{EMOJI_VERSION}/ # 14.0 and newer
next if line.match?(/\A(#|\z)/)
if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028)
range_start = $1.to_i(16)
diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb
index f4e3c93b47..7e6d722d40 100644
--- a/test/ruby/enc/test_grapheme_breaks.rb
+++ b/test/ruby/enc/test_grapheme_breaks.rb
@@ -44,7 +44,7 @@ class TestGraphemeBreaksFromFile < Test::Unit::TestCase
if file_available?
def read_data
tests = []
- IO.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line|
+ File.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line|
if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt")
raise "File Version Mismatch"
end
diff --git a/test/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb
index eaabbc58a2..b5d5c6e337 100644
--- a/test/ruby/enc/test_regex_casefold.rb
+++ b/test/ruby/enc/test_regex_casefold.rb
@@ -19,7 +19,7 @@ class TestCaseFold < Test::Unit::TestCase
end
def read_tests
- IO.readlines("#{UNICODE_DATA_PATH}/CaseFolding.txt", encoding: Encoding::ASCII_8BIT)
+ File.readlines("#{UNICODE_DATA_PATH}/CaseFolding.txt", encoding: Encoding::ASCII_8BIT)
.collect.with_index { |linedata, linenumber| [linenumber.to_i+1, linedata.chomp] }
.reject { |number, data| data =~ /^(#|$)/ }
.collect do |linenumber, linedata|
diff --git a/test/ruby/rjit/test_assembler.rb b/test/ruby/rjit/test_assembler.rb
new file mode 100644
index 0000000000..fbf780d6c3
--- /dev/null
+++ b/test/ruby/rjit/test_assembler.rb
@@ -0,0 +1,368 @@
+require 'test/unit'
+require_relative '../../lib/jit_support'
+
+return unless JITSupport.rjit_supported?
+return unless RubyVM::RJIT.enabled?
+return unless RubyVM::RJIT::C.HAVE_LIBCAPSTONE
+
+require 'stringio'
+require 'ruby_vm/rjit/assembler'
+
+module RubyVM::RJIT
+ class TestAssembler < Test::Unit::TestCase
+ MEM_SIZE = 16 * 1024
+
+ def setup
+ @mem_block ||= C.mmap(MEM_SIZE)
+ @cb = CodeBlock.new(mem_block: @mem_block, mem_size: MEM_SIZE)
+ end
+
+ def test_add
+ asm = Assembler.new
+ asm.add([:rcx], 1) # ADD r/m64, imm8 (Mod 00: [reg])
+ asm.add(:rax, 0x7f) # ADD r/m64, imm8 (Mod 11: reg)
+ asm.add(:rbx, 0x7fffffff) # ADD r/m64 imm32 (Mod 11: reg)
+ asm.add(:rsi, :rdi) # ADD r/m64, r64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: add qword ptr [rcx], 1
+ 0x4: add rax, 0x7f
+ 0x8: add rbx, 0x7fffffff
+ 0xf: add rsi, rdi
+ EOS
+ end
+
+ def test_and
+ asm = Assembler.new
+ asm.and(:rax, 0) # AND r/m64, imm8 (Mod 11: reg)
+ asm.and(:rbx, 0x7fffffff) # AND r/m64, imm32 (Mod 11: reg)
+ asm.and(:rcx, [:rdi, 8]) # AND r64, r/m64 (Mod 01: [reg]+disp8)
+ assert_compile(asm, <<~EOS)
+ 0x0: and rax, 0
+ 0x4: and rbx, 0x7fffffff
+ 0xb: and rcx, qword ptr [rdi + 8]
+ EOS
+ end
+
+ def test_call
+ asm = Assembler.new
+ asm.call(rel32(0xff)) # CALL rel32
+ asm.call(:rax) # CALL r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: call 0xff
+ 0x5: call rax
+ EOS
+ end
+
+ def test_cmove
+ asm = Assembler.new
+ asm.cmove(:rax, :rcx) # CMOVE r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmove rax, rcx
+ EOS
+ end
+
+ def test_cmovg
+ asm = Assembler.new
+ asm.cmovg(:rbx, :rdi) # CMOVG r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmovg rbx, rdi
+ EOS
+ end
+
+ def test_cmovge
+ asm = Assembler.new
+ asm.cmovge(:rsp, :rbp) # CMOVGE r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmovge rsp, rbp
+ EOS
+ end
+
+ def test_cmovl
+ asm = Assembler.new
+ asm.cmovl(:rdx, :rsp) # CMOVL r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmovl rdx, rsp
+ EOS
+ end
+
+ def test_cmovle
+ asm = Assembler.new
+ asm.cmovle(:rax, :rax) # CMOVLE r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmovle rax, rax
+ EOS
+ end
+
+ def test_cmovne
+ asm = Assembler.new
+ asm.cmovne(:rax, :rbx) # CMOVNE r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS) # cmovne == cmovnz
+ 0x0: cmovne rax, rbx
+ EOS
+ end
+
+ def test_cmovnz
+ asm = Assembler.new
+ asm.cmovnz(:rax, :rbx) # CMOVNZ r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS) # cmovne == cmovnz
+ 0x0: cmovne rax, rbx
+ EOS
+ end
+
+ def test_cmovz
+ asm = Assembler.new
+ asm.cmovz(:rax, :rbx) # CMOVZ r64, r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS) # cmove == cmovz
+ 0x0: cmove rax, rbx
+ EOS
+ end
+
+ def test_cmp
+ asm = Assembler.new
+ asm.cmp(BytePtr[:rax, 8], 8) # CMP r/m8, imm8 (Mod 01: [reg]+disp8)
+ asm.cmp(DwordPtr[:rax, 8], 0x100) # CMP r/m32, imm32 (Mod 01: [reg]+disp8)
+ asm.cmp([:rax, 8], 8) # CMP r/m64, imm8 (Mod 01: [reg]+disp8)
+ asm.cmp([:rbx, 8], 0x100) # CMP r/m64, imm32 (Mod 01: [reg]+disp8)
+ asm.cmp([:rax, 0x100], 8) # CMP r/m64, imm8 (Mod 10: [reg]+disp32)
+ asm.cmp(:rax, 8) # CMP r/m64, imm8 (Mod 11: reg)
+ asm.cmp(:rax, 0x100) # CMP r/m64, imm32 (Mod 11: reg)
+ asm.cmp([:rax, 8], :rbx) # CMP r/m64, r64 (Mod 01: [reg]+disp8)
+ asm.cmp([:rax, -0x100], :rbx) # CMP r/m64, r64 (Mod 10: [reg]+disp32)
+ asm.cmp(:rax, :rbx) # CMP r/m64, r64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: cmp byte ptr [rax + 8], 8
+ 0x4: cmp dword ptr [rax + 8], 0x100
+ 0xb: cmp qword ptr [rax + 8], 8
+ 0x10: cmp qword ptr [rbx + 8], 0x100
+ 0x18: cmp qword ptr [rax + 0x100], 8
+ 0x20: cmp rax, 8
+ 0x24: cmp rax, 0x100
+ 0x2b: cmp qword ptr [rax + 8], rbx
+ 0x2f: cmp qword ptr [rax - 0x100], rbx
+ 0x36: cmp rax, rbx
+ EOS
+ end
+
+ def test_jbe
+ asm = Assembler.new
+ asm.jbe(rel32(0xff)) # JBE rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: jbe 0xff
+ EOS
+ end
+
+ def test_je
+ asm = Assembler.new
+ asm.je(rel32(0xff)) # JE rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: je 0xff
+ EOS
+ end
+
+ def test_jl
+ asm = Assembler.new
+ asm.jl(rel32(0xff)) # JL rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: jl 0xff
+ EOS
+ end
+
+ def test_jmp
+ asm = Assembler.new
+ label = asm.new_label('label')
+ asm.jmp(label) # JZ rel8
+ asm.write_label(label)
+ asm.jmp(rel32(0xff)) # JMP rel32
+ asm.jmp([:rax, 8]) # JMP r/m64 (Mod 01: [reg]+disp8)
+ asm.jmp(:rax) # JMP r/m64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: jmp 2
+ 0x2: jmp 0xff
+ 0x7: jmp qword ptr [rax + 8]
+ 0xa: jmp rax
+ EOS
+ end
+
+ def test_jne
+ asm = Assembler.new
+ asm.jne(rel32(0xff)) # JNE rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: jne 0xff
+ EOS
+ end
+
+ def test_jnz
+ asm = Assembler.new
+ asm.jnz(rel32(0xff)) # JNZ rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: jne 0xff
+ EOS
+ end
+
+ def test_jo
+ asm = Assembler.new
+ asm.jo(rel32(0xff)) # JO rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: jo 0xff
+ EOS
+ end
+
+ def test_jz
+ asm = Assembler.new
+ asm.jz(rel32(0xff)) # JZ rel32
+ assert_compile(asm, <<~EOS)
+ 0x0: je 0xff
+ EOS
+ end
+
+ def test_lea
+ asm = Assembler.new
+ asm.lea(:rax, [:rax, 8]) # LEA r64,m (Mod 01: [reg]+disp8)
+ asm.lea(:rax, [:rax, 0xffff]) # LEA r64,m (Mod 10: [reg]+disp32)
+ assert_compile(asm, <<~EOS)
+ 0x0: lea rax, [rax + 8]
+ 0x4: lea rax, [rax + 0xffff]
+ EOS
+ end
+
+ def test_mov
+ asm = Assembler.new
+ asm.mov(:eax, DwordPtr[:rbx, 8]) # MOV r32 r/m32 (Mod 01: [reg]+disp8)
+ asm.mov(:eax, 0x100) # MOV r32, imm32 (Mod 11: reg)
+ asm.mov(:rax, [:rbx]) # MOV r64, r/m64 (Mod 00: [reg])
+ asm.mov(:rax, [:rbx, 8]) # MOV r64, r/m64 (Mod 01: [reg]+disp8)
+ asm.mov(:rax, [:rbx, 0x100]) # MOV r64, r/m64 (Mod 10: [reg]+disp32)
+ asm.mov(:rax, :rbx) # MOV r64, r/m64 (Mod 11: reg)
+ asm.mov(:rax, 0x100) # MOV r/m64, imm32 (Mod 11: reg)
+ asm.mov(:rax, 0x100000000) # MOV r64, imm64
+ asm.mov(DwordPtr[:rax, 8], 0x100) # MOV r/m32, imm32 (Mod 01: [reg]+disp8)
+ asm.mov([:rax], 0x100) # MOV r/m64, imm32 (Mod 00: [reg])
+ asm.mov([:rax], :rbx) # MOV r/m64, r64 (Mod 00: [reg])
+ asm.mov([:rax, 8], 0x100) # MOV r/m64, imm32 (Mod 01: [reg]+disp8)
+ asm.mov([:rax, 8], :rbx) # MOV r/m64, r64 (Mod 01: [reg]+disp8)
+ asm.mov([:rax, 0x100], 0x100) # MOV r/m64, imm32 (Mod 10: [reg]+disp32)
+ asm.mov([:rax, 0x100], :rbx) # MOV r/m64, r64 (Mod 10: [reg]+disp32)
+ assert_compile(asm, <<~EOS)
+ 0x0: mov eax, dword ptr [rbx + 8]
+ 0x3: mov eax, 0x100
+ 0x8: mov rax, qword ptr [rbx]
+ 0xb: mov rax, qword ptr [rbx + 8]
+ 0xf: mov rax, qword ptr [rbx + 0x100]
+ 0x16: mov rax, rbx
+ 0x19: mov rax, 0x100
+ 0x20: movabs rax, 0x100000000
+ 0x2a: mov dword ptr [rax + 8], 0x100
+ 0x31: mov qword ptr [rax], 0x100
+ 0x38: mov qword ptr [rax], rbx
+ 0x3b: mov qword ptr [rax + 8], 0x100
+ 0x43: mov qword ptr [rax + 8], rbx
+ 0x47: mov qword ptr [rax + 0x100], 0x100
+ 0x52: mov qword ptr [rax + 0x100], rbx
+ EOS
+ end
+
+ def test_or
+ asm = Assembler.new
+ asm.or(:rax, 0) # OR r/m64, imm8 (Mod 11: reg)
+ asm.or(:rax, 0xffff) # OR r/m64, imm32 (Mod 11: reg)
+ asm.or(:rax, [:rbx, 8]) # OR r64, r/m64 (Mod 01: [reg]+disp8)
+ assert_compile(asm, <<~EOS)
+ 0x0: or rax, 0
+ 0x4: or rax, 0xffff
+ 0xb: or rax, qword ptr [rbx + 8]
+ EOS
+ end
+
+ def test_push
+ asm = Assembler.new
+ asm.push(:rax) # PUSH r64
+ assert_compile(asm, <<~EOS)
+ 0x0: push rax
+ EOS
+ end
+
+ def test_pop
+ asm = Assembler.new
+ asm.pop(:rax) # POP r64
+ assert_compile(asm, <<~EOS)
+ 0x0: pop rax
+ EOS
+ end
+
+ def test_ret
+ asm = Assembler.new
+ asm.ret # RET
+ assert_compile(asm, "0x0: ret \n")
+ end
+
+ def test_sar
+ asm = Assembler.new
+ asm.sar(:rax, 0) # SAR r/m64, imm8 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: sar rax, 0
+ EOS
+ end
+
+ def test_sub
+ asm = Assembler.new
+ asm.sub(:rax, 8) # SUB r/m64, imm8 (Mod 11: reg)
+ asm.sub(:rax, :rbx) # SUB r/m64, r64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: sub rax, 8
+ 0x4: sub rax, rbx
+ EOS
+ end
+
+ def test_test
+ asm = Assembler.new
+ asm.test(BytePtr[:rax, 8], 16) # TEST r/m8*, imm8 (Mod 01: [reg]+disp8)
+ asm.test([:rax, 8], 8) # TEST r/m64, imm32 (Mod 01: [reg]+disp8)
+ asm.test([:rax, 0xffff], 0xffff) # TEST r/m64, imm32 (Mod 10: [reg]+disp32)
+ asm.test(:rax, 0xffff) # TEST r/m64, imm32 (Mod 11: reg)
+ asm.test(:eax, :ebx) # TEST r/m32, r32 (Mod 11: reg)
+ asm.test(:rax, :rbx) # TEST r/m64, r64 (Mod 11: reg)
+ assert_compile(asm, <<~EOS)
+ 0x0: test byte ptr [rax + 8], 0x10
+ 0x4: test qword ptr [rax + 8], 8
+ 0xc: test qword ptr [rax + 0xffff], 0xffff
+ 0x17: test rax, 0xffff
+ 0x1e: test eax, ebx
+ 0x20: test rax, rbx
+ EOS
+ end
+
+ def test_xor
+ asm = Assembler.new
+ asm.xor(:rax, :rbx)
+ assert_compile(asm, <<~EOS)
+ 0x0: xor rax, rbx
+ EOS
+ end
+
+ private
+
+ def rel32(offset)
+ @cb.write_addr + 0xff
+ end
+
+ def assert_compile(asm, expected)
+ actual = compile(asm)
+ assert_equal expected, actual, "---\n#{actual}---"
+ end
+
+ def compile(asm)
+ start_addr = @cb.write_addr
+ @cb.write(asm)
+ end_addr = @cb.write_addr
+
+ io = StringIO.new
+ @cb.dump_disasm(start_addr, end_addr, io:, color: false, test: true)
+ io.seek(0)
+ disasm = io.read
+
+ disasm.gsub!(/^ /, '')
+ disasm.sub!(/\n\z/, '')
+ disasm
+ end
+ end
+end
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
new file mode 100644
index 0000000000..48348c0fbd
--- /dev/null
+++ b/test/ruby/test_allocation.rb
@@ -0,0 +1,656 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestAllocation < Test::Unit::TestCase
+ def check_allocations(checks)
+ assert_separately([], <<~RUBY)
+ $allocations = [0, 0]
+ $counts = {}
+ failures = []
+
+ def self.num_allocations
+ ObjectSpace.count_objects($counts)
+ arrays = $counts[:T_ARRAY]
+ hashes = $counts[:T_HASH]
+ yield
+ ObjectSpace.count_objects($counts)
+ arrays -= $counts[:T_ARRAY]
+ hashes -= $counts[:T_HASH]
+ $allocations[0] = -arrays
+ $allocations[1] = -hashes
+ end
+
+ define_singleton_method(:check_allocations) do |num_arrays, num_hashes, check_code|
+ instance_eval <<~RB
+ empty_array = empty_array = []
+ empty_hash = empty_hash = {}
+ array1 = array1 = [1]
+ hash1 = hash1 = {a: 2}
+ nill = nill = nil
+ block = block = lambda{}
+
+ num_allocations do
+ \#{check_code}
+ end
+ RB
+
+ if num_arrays != $allocations[0]
+ failures << "Expected \#{num_arrays} array allocations for \#{check_code.inspect}, but \#{$allocations[0]} arrays allocated"
+ end
+ if num_hashes != $allocations[1]
+ failures << "Expected \#{num_hashes} hash allocations for \#{check_code.inspect}, but \#{$allocations[1]} hashes allocated"
+ end
+ end
+
+ GC.start
+ GC.disable
+
+ #{checks}
+
+ unless failures.empty?
+ assert_equal(true, false, failures.join("\n"))
+ end
+ RUBY
+ end
+
+ class Literal < self
+ def test_array_literal
+ check_allocations(<<~RUBY)
+ check_allocations(1, 0, "[]")
+ check_allocations(1, 0, "[1]")
+ check_allocations(1, 0, "[*empty_array]")
+ check_allocations(1, 0, "[*empty_array, 1, *empty_array]")
+ check_allocations(1, 0, "[*empty_array, *empty_array]")
+ check_allocations(1, 0, "[#{'1,'*100000}]")
+ RUBY
+ end
+
+ def test_hash_literal
+ check_allocations(<<~RUBY)
+ check_allocations(0, 1, "{}")
+ check_allocations(0, 1, "{a: 1}")
+ check_allocations(0, 1, "{**empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, a: 1, **empty_hash}")
+ check_allocations(0, 1, "{**empty_hash, **empty_hash}")
+ check_allocations(0, 1, "{#{100000.times.map{|i| "a#{i}: 1"}.join(',')}}")
+ RUBY
+ end
+ end
+
+ class MethodCall < self
+ def block
+ ''
+ end
+
+ def test_no_parameters
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.none(#{only_block}); end
+
+ check_allocations(0, 0, "none(#{only_block})")
+ check_allocations(0, 0, "none(*empty_array#{block})")
+ check_allocations(0, 0, "none(**empty_hash#{block})")
+ check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "none(*empty_array, *empty_array#{block})")
+ check_allocations(0, 1, "none(**empty_hash, **empty_hash#{block})")
+ check_allocations(1, 1, "none(*empty_array, *empty_array, **empty_hash, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_parameter
+ check_allocations(<<~RUBY)
+ def self.required(x#{block}); end
+
+ check_allocations(0, 0, "required(1#{block})")
+ check_allocations(0, 0, "required(1, *empty_array#{block})")
+ check_allocations(0, 0, "required(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required(*array1#{block})")
+ check_allocations(0, 1, "required(**hash1#{block})")
+
+ check_allocations(1, 0, "required(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "required(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required(*array1, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_optional_parameter
+ check_allocations(<<~RUBY)
+ def self.optional(x=nil#{block}); end
+
+ check_allocations(0, 0, "optional(1#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array#{block})")
+ check_allocations(0, 0, "optional(1, **empty_hash#{block})")
+ check_allocations(0, 0, "optional(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "optional(*array1#{block})")
+ check_allocations(0, 1, "optional(**hash1#{block})")
+
+ check_allocations(1, 0, "optional(*array1, *empty_array#{block})")
+ check_allocations(0, 1, "optional(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "optional(*array1, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat(*x#{block}); end
+
+ check_allocations(1, 0, "splat(1#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*array1#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(1, *array1#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat(*array1#{block})")
+ check_allocations(1, 1, "splat(**hash1#{block})")
+
+ check_allocations(1, 0, "splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_and_positional_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.req_splat(x, *y#{block}); end
+
+ check_allocations(1, 0, "req_splat(1#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(1, *array1#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1#{block})")
+ check_allocations(1, 1, "req_splat(**hash1#{block})")
+
+ check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_post_parameters
+ check_allocations(<<~RUBY)
+ def self.splat_post(*x, y#{block}); end
+
+ check_allocations(1, 0, "splat_post(1#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(1, *array1#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(1, *array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1#{block})")
+ check_allocations(1, 1, "splat_post(**hash1#{block})")
+
+ check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword(a: nil#{block}); end
+
+ check_allocations(0, 0, "keyword(a: 2#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "keyword(**nil#{block})")
+ check_allocations(0, 0, "keyword(**empty_hash#{block})")
+ check_allocations(0, 0, "keyword(**hash1#{block})")
+ check_allocations(0, 0, "keyword(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "keyword(*empty_array#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "keyword(*empty_array#{block})")
+ check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_splat(**kw#{block}); end
+
+ check_allocations(0, 1, "keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_keyword_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(**nil#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, **hash1, **empty_hash#{block})")
+ RUBY
+ end
+
+ def test_required_positional_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword(b, a: nil#{block}); end
+
+ check_allocations(0, 0, "required_and_keyword(1, a: 2#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, **nil#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, **hash1#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})")
+ check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "required_and_keyword(*array1, **nill#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "required_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "required_and_keyword(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword(*b, a: nil#{block}); end
+
+ check_allocations(1, 0, "splat_and_keyword(1, a: 2#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, **nil#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, a: 2#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nill#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "splat_and_keyword(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_required_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.required_and_keyword_splat(b, **kw#{block}); end
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, **nil#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ # Currently allocates 1 array unnecessarily due to splatarray true
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "required_and_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_positional_splat_and_keyword_splat_parameter
+ check_allocations(<<~RUBY)
+ def self.splat_and_keyword_splat(*b, **kw#{block}); end
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nill#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_anonymous_splat_and_anonymous_keyword_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
+ check_allocations(<<~RUBY)
+ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end; def self.t(*, **#{block}); end
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.argument_forwarding(...); end
+
+ check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})")
+ RUBY
+ end
+
+ def test_nested_argument_forwarding
+ check_allocations(<<~RUBY)
+ def self.argument_forwarding(...); t(...) end; def self.t(...) end
+
+ check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, **nil#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
+ check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})")
+ RUBY
+ end
+
+ class WithBlock < self
+ def block
+ ', &block'
+ end
+ end
+ end
+end
diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb
index dbffd24370..55a06296aa 100644
--- a/test/ruby/test_argf.rb
+++ b/test/ruby/test_argf.rb
@@ -9,39 +9,23 @@ class TestArgf < Test::Unit::TestCase
def setup
@tmpdir = Dir.mktmpdir
@tmp_count = 0
- @t1 = make_tempfile0("argf-foo")
- @t1.binmode
- @t1.puts "1"
- @t1.puts "2"
- @t1.close
- @t2 = make_tempfile0("argf-bar")
- @t2.binmode
- @t2.puts "3"
- @t2.puts "4"
- @t2.close
- @t3 = make_tempfile0("argf-baz")
- @t3.binmode
- @t3.puts "5"
- @t3.puts "6"
- @t3.close
+ @t1 = make_tempfile("argf-foo", %w"1 2", binmode: true)
+ @t2 = make_tempfile("argf-bar", %w"3 4", binmode: true)
+ @t3 = make_tempfile("argf-baz", %w"5 6", binmode: true)
end
def teardown
FileUtils.rmtree(@tmpdir)
end
- def make_tempfile0(basename)
+ def make_tempfile(basename = "argf-qux", data = %w[foo bar baz], binmode: false)
@tmp_count += 1
- open("#{@tmpdir}/#{basename}-#{@tmp_count}", "w")
- end
-
- def make_tempfile(basename = "argf-qux")
- t = make_tempfile0(basename)
- t.puts "foo"
- t.puts "bar"
- t.puts "baz"
- t.close
- t
+ path = "#{@tmpdir}/#{basename}-#{@tmp_count}"
+ File.open(path, "w") do |f|
+ f.binmode if binmode
+ f.puts(*data)
+ f
+ end
end
def ruby(*args, external_encoding: Encoding::UTF_8)
@@ -143,6 +127,17 @@ class TestArgf < Test::Unit::TestCase
};
end
+ def test_lineno_after_shebang
+ expected = %w"1 1 1 2 2 2 3 3 1 4 4 2"
+ assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected)
+ #!/usr/bin/env ruby
+ {#
+ ARGF.each do |line|
+ puts [$., ARGF.lineno, ARGF.file.lineno]
+ end
+ };
+ end
+
def test_new_lineno_each
f = ARGF.class.new(@t1.path, @t2.path, @t3.path)
result = []
@@ -560,15 +555,11 @@ class TestArgf < Test::Unit::TestCase
end
end
- t1 = open("#{@tmpdir}/argf-hoge", "w")
- t1.binmode
- t1.puts "foo"
- t1.close
- t2 = open("#{@tmpdir}/argf-moge", "w")
- t2.binmode
- t2.puts "bar"
- t2.close
- ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1.path, t2.path) do |f|
+ t1 = "#{@tmpdir}/argf-hoge"
+ t2 = "#{@tmpdir}/argf-moge"
+ File.binwrite(t1, "foo\n")
+ File.binwrite(t2, "bar\n")
+ ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1, t2) do |f|
assert_equal(%w(false), f.read.split(/\n/))
end
end
@@ -582,7 +573,7 @@ class TestArgf < Test::Unit::TestCase
def test_read2
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
ARGF.read(8, s)
p s
};
@@ -593,7 +584,7 @@ class TestArgf < Test::Unit::TestCase
def test_read2_with_not_empty_buffer
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = "0123456789"
+ s = +"0123456789"
ARGF.read(8, s)
p s
};
@@ -606,7 +597,7 @@ class TestArgf < Test::Unit::TestCase
{#
nil while ARGF.gets
p ARGF.read
- p ARGF.read(0, "")
+ p ARGF.read(0, +"")
};
assert_equal("nil\n\"\"\n", f.read)
end
@@ -615,13 +606,13 @@ class TestArgf < Test::Unit::TestCase
def test_readpartial
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
begin
loop do
s << ARGF.readpartial(1)
- t = ""; ARGF.readpartial(1, t); s << t
+ t = +""; ARGF.readpartial(1, t); s << t
# not empty buffer
- u = "abcdef"; ARGF.readpartial(1, u); s << u
+ u = +"abcdef"; ARGF.readpartial(1, u); s << u
end
rescue EOFError
puts s
@@ -634,11 +625,11 @@ class TestArgf < Test::Unit::TestCase
def test_readpartial2
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
{#
- s = ""
+ s = +""
begin
loop do
s << ARGF.readpartial(1)
- t = ""; ARGF.readpartial(1, t); s << t
+ t = +""; ARGF.readpartial(1, t); s << t
end
rescue EOFError
$stdout.binmode
@@ -669,7 +660,7 @@ class TestArgf < Test::Unit::TestCase
def test_getc
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
while c = ARGF.getc
s << c
end
@@ -695,7 +686,7 @@ class TestArgf < Test::Unit::TestCase
def test_readchar
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
begin
while c = ARGF.readchar
s << c
@@ -773,7 +764,7 @@ class TestArgf < Test::Unit::TestCase
def test_each_char
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
{#
- s = ""
+ s = +""
ARGF.each_char {|c| s << c }
puts s
};
@@ -843,7 +834,7 @@ class TestArgf < Test::Unit::TestCase
def test_binmode
bug5268 = '[ruby-core:39234]'
- open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"}
+ File.binwrite(@t3.path, "5\r\n6\r\n")
ruby('-e', "ARGF.binmode; STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
f.binmode
assert_equal("1\n2\n3\n4\n5\r\n6\r\n", f.read, bug5268)
@@ -852,7 +843,7 @@ class TestArgf < Test::Unit::TestCase
def test_textmode
bug5268 = '[ruby-core:39234]'
- open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"}
+ File.binwrite(@t3.path, "5\r\n6\r\n")
ruby('-e', "STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
f.binmode
assert_equal("1\n2\n3\n4\n5\n6\n", f.read, bug5268)
@@ -1062,7 +1053,7 @@ class TestArgf < Test::Unit::TestCase
ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
{#
$stdout.sync = true
- :wait_readable == ARGF.read_nonblock(1, "", exception: false) or
+ :wait_readable == ARGF.read_nonblock(1, +"", exception: false) or
abort "did not return :wait_readable"
begin
@@ -1075,7 +1066,7 @@ class TestArgf < Test::Unit::TestCase
IO.select([ARGF]) == [[ARGF], [], []] or
abort 'did not awaken for readability (before byte)'
- buf = ''
+ buf = +''
buf.object_id == ARGF.read_nonblock(1, buf).object_id or
abort "read destination buffer failed"
print buf
@@ -1129,4 +1120,34 @@ class TestArgf < Test::Unit::TestCase
argf.close
end
end
+
+ def test_putc
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.putc('x')", t.path) do |f|
+ end
+ assert_equal("xxbar\n", File.read(t.path))
+ end
+
+ def test_puts
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ err = "#{@tmpdir}/errout"
+ ruby('-pi-', '-W2', '-e', "print ARGF.puts('foo')", t.path, {err: err}) do |f|
+ end
+ assert_equal("foo\nbar\n", File.read(t.path))
+ assert_empty File.read(err)
+ end
+
+ def test_print
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.print('foo')", t.path) do |f|
+ end
+ assert_equal("foobar\n", File.read(t.path))
+ end
+
+ def test_printf
+ t = make_tempfile("argf-#{__method__}", 'bar')
+ ruby('-pi-', '-e', "print ARGF.printf('%s', 'foo')", t.path) do |f|
+ end
+ assert_equal("foobar\n", File.read(t.path))
+ end
end
diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb
index d26338e0aa..bd26d5f0f5 100644
--- a/test/ruby/test_arity.rb
+++ b/test/ruby/test_arity.rb
@@ -65,6 +65,5 @@ class TestArity < Test::Unit::TestCase
assert_arity(%w[1 2]) { "".sub!(//) }
assert_arity(%w[0 1..2]) { "".sub!{} }
assert_arity(%w[0 1+]) { exec }
- assert_arity(%w[0 1+]) { Struct.new }
end
end
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index e376d76a16..9560fca958 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -529,14 +529,19 @@ class TestArray < Test::Unit::TestCase
end
def test_assoc
+ def (a4 = Object.new).to_ary
+ %w( pork porcine )
+ end
+
a1 = @cls[*%w( cat feline )]
a2 = @cls[*%w( dog canine )]
a3 = @cls[*%w( mule asinine )]
- a = @cls[ a1, a2, a3 ]
+ a = @cls[ a1, a2, a3, a4 ]
assert_equal(a1, a.assoc('cat'))
assert_equal(a3, a.assoc('mule'))
+ assert_equal(%w( pork porcine ), a.assoc("pork"))
assert_equal(nil, a.assoc('asinine'))
assert_equal(nil, a.assoc('wombat'))
assert_equal(nil, a.assoc(1..2))
@@ -1294,6 +1299,12 @@ class TestArray < Test::Unit::TestCase
=end
end
+ def test_pack_with_buffer
+ n = [ 65, 66, 67 ]
+ str = "a" * 100
+ assert_equal("aaaABC", n.pack("@3ccc", buffer: str.dup), "[Bug #19116]")
+ end
+
def test_pop
a = @cls[ 'cat', 'dog' ]
assert_equal('dog', a.pop)
@@ -1323,13 +1334,17 @@ class TestArray < Test::Unit::TestCase
end
def test_rassoc
+ def (a4 = Object.new).to_ary
+ %w( pork porcine )
+ end
a1 = @cls[*%w( cat feline )]
a2 = @cls[*%w( dog canine )]
a3 = @cls[*%w( mule asinine )]
- a = @cls[ a1, a2, a3 ]
+ a = @cls[ a1, a2, a3, a4 ]
assert_equal(a1, a.rassoc('feline'))
assert_equal(a3, a.rassoc('asinine'))
+ assert_equal(%w( pork porcine ), a.rassoc("porcine"))
assert_equal(nil, a.rassoc('dog'))
assert_equal(nil, a.rassoc('mule'))
assert_equal(nil, a.rassoc(1..2))
@@ -1580,6 +1595,96 @@ class TestArray < Test::Unit::TestCase
assert_equal_instance(a.values_at(*idx), a.slice((3..90)%2))
idx = 90.step(3, -2).to_a
assert_equal_instance(a.values_at(*idx), a.slice((90 .. 3)% -2))
+
+ a = [0, 1, 2, 3, 4, 5]
+ assert_equal([2, 1, 0], a.slice((2..).step(-1)))
+ assert_equal([2, 0], a.slice((2..).step(-2)))
+ assert_equal([2], a.slice((2..).step(-3)))
+ assert_equal([2], a.slice((2..).step(-4)))
+
+ assert_equal([3, 2, 1, 0], a.slice((-3..).step(-1)))
+ assert_equal([3, 1], a.slice((-3..).step(-2)))
+ assert_equal([3, 0], a.slice((-3..).step(-3)))
+ assert_equal([3], a.slice((-3..).step(-4)))
+ assert_equal([3], a.slice((-3..).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1, 0], a.slice((..0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..0).step(-2)))
+ assert_equal([5, 2], a.slice((..0).step(-3)))
+ assert_equal([5, 1], a.slice((..0).step(-4)))
+ assert_equal([5, 0], a.slice((..0).step(-5)))
+ assert_equal([5], a.slice((..0).step(-6)))
+ assert_equal([5], a.slice((..0).step(-7)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((...0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((...0).step(-2)))
+ assert_equal([5, 2], a.slice((...0).step(-3)))
+ assert_equal([5, 1], a.slice((...0).step(-4)))
+ assert_equal([5], a.slice((...0).step(-5)))
+ assert_equal([5], a.slice((...0).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...1).step(-1)))
+ assert_equal([5, 3], a.slice((...1).step(-2)))
+ assert_equal([5, 2], a.slice((...1).step(-3)))
+ assert_equal([5], a.slice((...1).step(-4)))
+ assert_equal([5], a.slice((...1).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((..-5).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..-5).step(-2)))
+ assert_equal([5, 2], a.slice((..-5).step(-3)))
+ assert_equal([5, 1], a.slice((..-5).step(-4)))
+ assert_equal([5], a.slice((..-5).step(-5)))
+ assert_equal([5], a.slice((..-5).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...-5).step(-1)))
+ assert_equal([5, 3], a.slice((...-5).step(-2)))
+ assert_equal([5, 2], a.slice((...-5).step(-3)))
+ assert_equal([5], a.slice((...-5).step(-4)))
+ assert_equal([5], a.slice((...-5).step(-5)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..1).step(-1)))
+ assert_equal([4, 2], a.slice((4..1).step(-2)))
+ assert_equal([4, 1], a.slice((4..1).step(-3)))
+ assert_equal([4], a.slice((4..1).step(-4)))
+ assert_equal([4], a.slice((4..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...1).step(-1)))
+ assert_equal([4, 2], a.slice((4...1).step(-2)))
+ assert_equal([4], a.slice((4...1).step(-3)))
+ assert_equal([4], a.slice((4...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..1).step(-1)))
+ assert_equal([4, 2], a.slice((-2..1).step(-2)))
+ assert_equal([4, 1], a.slice((-2..1).step(-3)))
+ assert_equal([4], a.slice((-2..1).step(-4)))
+ assert_equal([4], a.slice((-2..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...1).step(-1)))
+ assert_equal([4, 2], a.slice((-2...1).step(-2)))
+ assert_equal([4], a.slice((-2...1).step(-3)))
+ assert_equal([4], a.slice((-2...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..-5).step(-1)))
+ assert_equal([4, 2], a.slice((4..-5).step(-2)))
+ assert_equal([4, 1], a.slice((4..-5).step(-3)))
+ assert_equal([4], a.slice((4..-5).step(-4)))
+ assert_equal([4], a.slice((4..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...-5).step(-1)))
+ assert_equal([4, 2], a.slice((4...-5).step(-2)))
+ assert_equal([4], a.slice((4...-5).step(-3)))
+ assert_equal([4], a.slice((4...-5).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2..-5).step(-2)))
+ assert_equal([4, 1], a.slice((-2..-5).step(-3)))
+ assert_equal([4], a.slice((-2..-5).step(-4)))
+ assert_equal([4], a.slice((-2..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2...-5).step(-2)))
+ assert_equal([4], a.slice((-2...-5).step(-3)))
+ assert_equal([4], a.slice((-2...-5).step(-4)))
end
def test_slice_out_of_range
@@ -1597,6 +1702,15 @@ class TestArray < Test::Unit::TestCase
assert_equal([100], a.slice(-1, 1_000_000_000))
end
+ def test_slice_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal([1, 2, 3, 4, 5], (0..10).to_a[1, 5]) }
+ EnvUtil.under_gc_compact_stress do
+ a = [0, 1, 2, 3, 4, 5]
+ assert_equal([2, 1, 0], a.slice((2..).step(-1)))
+ end
+ end
+
def test_slice!
a = @cls[1, 2, 3, 4, 5]
assert_equal(3, a.slice!(2))
@@ -2890,7 +3004,9 @@ class TestArray < Test::Unit::TestCase
assert_raise(RangeError) {
[*0..2].shuffle(random: gen)
}
+ end
+ def test_shuffle_random_clobbering
ary = (0...10000).to_a
gen = proc do
ary.replace([])
@@ -2900,7 +3016,9 @@ class TestArray < Test::Unit::TestCase
alias rand call
end
assert_raise(RuntimeError) {ary.shuffle!(random: gen)}
+ end
+ def test_shuffle_random_zero
zero = Object.new
def zero.to_int
0
@@ -2913,7 +3031,10 @@ class TestArray < Test::Unit::TestCase
end
ary = (0...10000).to_a
assert_equal(ary.rotate, ary.shuffle(random: gen_to_int))
+ end
+ def test_shuffle_random_invalid_generator
+ ary = (0...10).to_a
assert_raise(NoMethodError) {
ary.shuffle(random: Object.new)
}
@@ -2930,7 +3051,9 @@ class TestArray < Test::Unit::TestCase
assert_include([0, 1, 2], sample)
}
end
+ end
+ def test_sample_statistics
srand(0)
a = (1..18).to_a
(0..20).each do |n|
@@ -2947,9 +3070,13 @@ class TestArray < Test::Unit::TestCase
end
assert_operator(h.values.min * 2, :>=, h.values.max) if n != 0
end
+ end
+ def test_sample_invalid_argument
assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)}
+ end
+ def test_sample_random_srand0
gen = Random.new(0)
srand(0)
a = (1..18).to_a
@@ -2958,13 +3085,15 @@ class TestArray < Test::Unit::TestCase
assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}")
end
end
+ end
+ def test_sample_unknown_keyword
assert_raise_with_message(ArgumentError, /unknown keyword/) do
[0, 1, 2].sample(xawqij: "a")
end
end
- def test_sample_random
+ def test_sample_random_generator
ary = (0...10000).to_a
assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)}
gen0 = proc do |max|
@@ -3007,7 +3136,9 @@ class TestArray < Test::Unit::TestCase
assert_equal([5000, 0, 5001, 2, 5002, 4, 5003, 6, 5004, 8, 5005], ary.sample(11, random: gen0))
ary.sample(11, random: gen1) # implementation detail, may change in the future
assert_equal([], ary)
+ end
+ def test_sample_random_generator_half
half = Object.new
def half.to_int
5000
@@ -3020,7 +3151,10 @@ class TestArray < Test::Unit::TestCase
end
ary = (0...10000).to_a
assert_equal(5000, ary.sample(random: gen_to_int))
+ end
+ def test_sample_random_invalid_generator
+ ary = (0..10).to_a
assert_raise(NoMethodError) {
ary.sample(random: Object.new)
}
@@ -3220,6 +3354,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch {|x| (-1) * (2**100) })
+ assert_equal(4, a.bsearch {|x| (4 - x).to_r })
+
assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -3255,6 +3391,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch_index {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch_index {|x| (-1) * (2**100) })
+ assert_equal(1, a.bsearch_index {|x| (4 - x).to_r })
+
assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -3418,11 +3556,21 @@ class TestArray < Test::Unit::TestCase
assert_equal(10000, eval(lit).size)
end
+ def test_array_safely_modified_by_sort_block
+ var_0 = (1..70).to_a
+ var_0.sort! do |var_0_block_129, var_1_block_129|
+ var_0.pop
+ var_1_block_129 <=> var_0_block_129
+ end.shift(3)
+ assert_equal((1..67).to_a.reverse, var_0)
+ 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/ruby/test_ast.rb b/test/ruby/test_ast.rb
index cd96027654..29da607fc5 100644
--- a/test/ruby/test_ast.rb
+++ b/test/ruby/test_ast.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
require 'tempfile'
+require 'pp'
class RubyVM
module AbstractSyntaxTree
@@ -131,6 +132,34 @@ class TestAst < Test::Unit::TestCase
end
end
+ Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
+ define_method("test_all_tokens:#{path}") do
+ node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
+ tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
+ tokens_bytes = tokens.map { _1[2]}.join.bytes
+ source_bytes = File.read("#{SRCDIR}/#{path}").bytes
+
+ assert_equal(source_bytes, tokens_bytes)
+
+ (tokens.count - 1).times do |i|
+ token_0 = tokens[i]
+ token_1 = tokens[i + 1]
+ end_pos = token_0.last[2..3]
+ beg_pos = token_1.last[0..1]
+
+ if end_pos[0] == beg_pos[0]
+ # When both tokens are same line, column should be consecutives
+ assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}")
+ else
+ # Line should be next
+ assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}")
+ # It should be on the beginning of the line
+ assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}")
+ end
+ end
+ end
+ end
+
private def parse(src)
EnvUtil.suppress_warning {
RubyVM::AbstractSyntaxTree.parse(src)
@@ -185,6 +214,147 @@ class TestAst < Test::Unit::TestCase
end
end
+ def assert_parse(code, warning: '')
+ node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)}
+ assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code)
+ end
+
+ def assert_invalid_parse(msg, code)
+ assert_raise_with_message(SyntaxError, msg, code) do
+ RubyVM::AbstractSyntaxTree.parse(code)
+ end
+ end
+
+ def test_invalid_exit
+ [
+ "break",
+ "break true",
+ "next",
+ "next true",
+ "redo",
+ ].each do |code, *args|
+ msg = /Invalid #{code[/\A\w+/]}/
+ assert_parse("while false; #{code}; end")
+ assert_parse("until true; #{code}; end")
+ assert_parse("begin #{code}; end while false")
+ assert_parse("begin #{code}; end until true")
+ assert_parse("->{#{code}}")
+ assert_parse("->{class X; #{code}; end}")
+ assert_invalid_parse(msg, "#{code}")
+ assert_invalid_parse(msg, "def m; #{code}; end")
+ assert_invalid_parse(msg, "begin; #{code}; end")
+ assert_parse("END {#{code}}")
+
+ assert_parse("!defined?(#{code})")
+ assert_parse("def m; defined?(#{code}); end")
+ assert_parse("!begin; defined?(#{code}); end")
+
+ next if code.include?(" ")
+ assert_parse("!defined? #{code}")
+ assert_parse("def m; defined? #{code}; end")
+ assert_parse("!begin; defined? #{code}; end")
+ end
+ end
+
+ def test_invalid_retry
+ msg = /Invalid retry/
+ assert_invalid_parse(msg, "retry")
+ assert_invalid_parse(msg, "def m; retry; end")
+ assert_invalid_parse(msg, "begin retry; end")
+ assert_parse("begin rescue; retry; end")
+ assert_invalid_parse(msg, "begin rescue; else; retry; end")
+ assert_invalid_parse(msg, "begin rescue; ensure; retry; end")
+ assert_parse("nil rescue retry")
+ assert_invalid_parse(msg, "END {retry}")
+ assert_invalid_parse(msg, "begin rescue; END {retry}; end")
+
+ assert_parse("!defined?(retry)")
+ assert_parse("def m; defined?(retry); end")
+ assert_parse("!begin defined?(retry); end")
+ assert_parse("begin rescue; else; defined?(retry); end")
+ assert_parse("begin rescue; ensure; defined?(retry); end")
+ assert_parse("END {defined?(retry)}")
+ assert_parse("begin rescue; END {defined?(retry)}; end")
+ assert_parse("!defined? retry")
+
+ assert_parse("def m; defined? retry; end")
+ assert_parse("!begin defined? retry; end")
+ assert_parse("begin rescue; else; defined? retry; end")
+ assert_parse("begin rescue; ensure; defined? retry; end")
+ assert_parse("END {defined? retry}")
+ assert_parse("begin rescue; END {defined? retry}; end")
+
+ assert_parse("#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def foo
+ begin
+ yield
+ rescue StandardError => e
+ begin
+ puts "hi"
+ retry
+ rescue
+ retry unless e
+ raise e
+ else
+ retry
+ ensure
+ retry
+ end
+ end
+ end
+ end;
+ end
+
+ def test_invalid_yield
+ msg = /Invalid yield/
+ assert_invalid_parse(msg, "yield")
+ assert_invalid_parse(msg, "class C; yield; end")
+ assert_invalid_parse(msg, "BEGIN {yield}")
+ assert_invalid_parse(msg, "END {yield}")
+ assert_invalid_parse(msg, "-> {yield}")
+
+ assert_invalid_parse(msg, "yield true")
+ assert_invalid_parse(msg, "class C; yield true; end")
+ assert_invalid_parse(msg, "BEGIN {yield true}")
+ assert_invalid_parse(msg, "END {yield true}")
+ assert_invalid_parse(msg, "-> {yield true}")
+
+ assert_parse("!defined?(yield)")
+ assert_parse("class C; defined?(yield); end")
+ assert_parse("BEGIN {defined?(yield)}")
+ assert_parse("END {defined?(yield)}")
+
+ assert_parse("!defined?(yield true)")
+ assert_parse("class C; defined?(yield true); end")
+ assert_parse("BEGIN {defined?(yield true)}")
+ assert_parse("END {defined?(yield true)}")
+
+ assert_parse("!defined? yield")
+ assert_parse("class C; defined? yield; end")
+ assert_parse("BEGIN {defined? yield}")
+ assert_parse("END {defined? yield}")
+ end
+
+ def test_node_id_for_location
+ exception = begin
+ raise
+ rescue => e
+ e
+ end
+ loc = exception.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
+
+ assert_equal node.node_id, node_id
+ end
+
+ def test_node_id_for_backtrace_location_raises_argument_error
+ bug19262 = '[ruby-core:111435]'
+
+ assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) }
+ end
+
def test_of_proc_and_method
proc = Proc.new { 1 + 2 }
method = self.method(__method__)
@@ -419,7 +589,7 @@ class TestAst < Test::Unit::TestCase
assert_equal("foo", head)
assert_equal(:EVSTR, body.type)
body, = body.children
- assert_equal(:LIT, body.type)
+ assert_equal(:INTEGER, body.type)
assert_equal([1], body.children)
end
@@ -447,6 +617,30 @@ class TestAst < Test::Unit::TestCase
assert_not_equal(type1, type2)
end
+ def test_rest_arg
+ rest_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-4]
+ end
+
+ assert_equal(nil, rest_arg.call(''))
+ assert_equal(:r, rest_arg.call('*r'))
+ assert_equal(:r, rest_arg.call('a, *r'))
+ assert_equal(:*, rest_arg.call('*'))
+ assert_equal(:*, rest_arg.call('a, *'))
+ end
+
+ def test_block_arg
+ block_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-1]
+ end
+
+ assert_equal(nil, block_arg.call(''))
+ assert_equal(:block, block_arg.call('&block'))
+ assert_equal(:&, block_arg.call('&'))
+ end
+
def test_keyword_rest
kwrest = lambda do |arg_str|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
@@ -455,11 +649,21 @@ class TestAst < Test::Unit::TestCase
end
assert_equal(nil, kwrest.call(''))
- assert_equal([nil], kwrest.call('**'))
+ assert_equal([:**], kwrest.call('**'))
assert_equal(false, kwrest.call('**nil'))
assert_equal([:a], kwrest.call('**a'))
end
+ def test_argument_forwarding
+ forwarding = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1]
+ node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : []
+ end
+
+ assert_equal([:*, [:**], :&], forwarding.call('...'))
+ end
+
def test_ranges_numbered_parameter
helper = Helper.new(__FILE__, src: "1.times {_1}")
helper.validate_range
@@ -542,8 +746,509 @@ dummy
assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
end
+ def test_keep_script_lines_for_of_with_existing_SCRIPT_LINES__that_has__FILE__as_a_key
+ # This test confirms that the bug that previously occurred because of
+ # `AbstractSyntaxTree.of`s unnecessary dependence on SCRIPT_LINES__ does not reproduce.
+ # The bug occurred only if SCRIPT_LINES__ included __FILE__ as a key.
+ lines = [
+ "SCRIPT_LINES__ = {__FILE__ => []}",
+ "puts RubyVM::AbstractSyntaxTree.of(->{ 1 + 2 }, keep_script_lines: true).script_lines",
+ "p SCRIPT_LINES__"
+ ]
+ test_stdout = lines + ['{"-e"=>[]}']
+ assert_in_out_err(["-e", lines.join("\n")], "", test_stdout, [])
+ end
+
+ def test_source_with_multibyte_characters
+ ast = RubyVM::AbstractSyntaxTree.parse(%{a("\u00a7");b("\u00a9")}, keep_script_lines: true)
+ a_fcall, b_fcall = ast.children[2].children
+
+ assert_equal(%{a("\u00a7")}, a_fcall.source)
+ assert_equal(%{b("\u00a9")}, b_fcall.source)
+ end
+
+ def test_keep_tokens_for_parse
+ node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_tokens: true)
+ 1.times do
+ end
+ __END__
+ dummy
+ END
+
+ expected = [
+ [:tINTEGER, "1"],
+ [:".", "."],
+ [:tIDENTIFIER, "times"],
+ [:tSP, " "],
+ [:keyword_do, "do"],
+ [:tIGNORED_NL, "\n"],
+ [:keyword_end, "end"],
+ [:nl, "\n"],
+ ]
+ assert_equal(expected, node.all_tokens.map { [_2, _3]})
+ end
+
+ def test_keep_tokens_unexpected_backslash
+ assert_raise_with_message(SyntaxError, /unexpected backslash/) do
+ RubyVM::AbstractSyntaxTree.parse("\\", keep_tokens: true)
+ end
+ end
+
+ def test_encoding_with_keep_script_lines
+ # Stop a warning "possibly useless use of a literal in void context"
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+
+ enc = Encoding::EUC_JP
+ code = "__ENCODING__".encode(enc)
+
+ assert_equal(enc, eval(code))
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false)
+ assert_equal(enc, node.children[2].children[0])
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true)
+ assert_equal(enc, node.children[2].children[0])
+
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
def test_e_option
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
"", [":SCOPE"], [])
end
+
+ def test_error_tolerant
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
+ class A
+ def m
+ if;
+ a = 10
+ end
+ end
+ STR
+ assert_nil($!)
+
+ assert_equal(:SCOPE, node.type)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ def test_error_tolerant_end_is_short_for_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-2:4
+ mid: :m
+ body:
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_singleton_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def obj.m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:9-1:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_begin
+ assert_error_tolerant(<<~STR, <<~EXP)
+ begin
+ a = 1
+ STR
+ (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_if
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_unless
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_while
+ assert_error_tolerant(<<~STR, <<~EXP)
+ while true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_until
+ assert_error_tolerant(<<~STR, <<~EXP)
+ until true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_case
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ when 1
+ STR
+ (SCOPE@1:0-2:6
+ tbl: []
+ args: nil
+ body:
+ (CASE@1:0-2:6 (VCALL@1:5-1:6 :a)
+ (WHEN@2:0-2:6 (LIST@2:5-2:6 (INTEGER@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil)
+ nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case
+ when a == 1
+ STR
+ (SCOPE@1:0-2:11
+ tbl: []
+ args: nil
+ body:
+ (CASE2@1:0-2:11 nil
+ (WHEN@2:0-2:11
+ (LIST@2:5-2:11
+ (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :==
+ (LIST@2:10-2:11 (INTEGER@2:10-2:11 1) nil)) nil)
+ (BEGIN@2:11-2:11 nil) nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ in {a: String}
+ STR
+ (SCOPE@1:0-2:14
+ tbl: []
+ args: nil
+ body:
+ (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a)
+ (IN@2:0-2:14
+ (HSHPTN@2:4-2:13
+ const: nil
+ kw:
+ (HASH@2:4-2:13
+ (LIST@2:4-2:13 (SYM@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil))
+ kwrest: nil) (BEGIN@2:14-2:14 nil) nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_for
+ assert_error_tolerant(<<~STR, <<~EXP)
+ for i in ary
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: [:i]
+ args: nil
+ body:
+ (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary)
+ (SCOPE@1:0-2:3
+ tbl: [nil]
+ args:
+ (ARGS@1:4-1:5
+ pre_num: 1
+ pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil))
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :m))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_class
+ assert_error_tolerant(<<~STR, <<~EXP)
+ class C
+ STR
+ (SCOPE@1:0-1:7
+ tbl: []
+ args: nil
+ body:
+ (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil
+ (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_module
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module M
+ STR
+ (SCOPE@1:0-1:8
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M)
+ (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil)
+ (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_block
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m 1 do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (INTEGER@1:2-1:3 1) nil))
+ (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_LAMBDA
+ assert_error_tolerant(<<~STR, <<~EXP)
+ -> do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (LAMBDA@1:0-2:3
+ (SCOPE@1:2-2:3
+ tbl: []
+ args:
+ (ARGS@1:2-1:2
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_treat_end_as_keyword_based_on_indent
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module Z
+ class Foo
+ foo.
+ end
+
+ def bar
+ end
+ end
+ STR
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z)
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil)
+ (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil
+ (SCOPE@2:2-4:5
+ tbl: []
+ args: nil
+ body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5))))
+ (DEFN@6:2-7:5
+ mid: :bar
+ body:
+ (SCOPE@6:2-7:5
+ tbl: []
+ args:
+ (ARGS@6:9-6:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: nil))))))
+ EXP
+ end
+
+ def test_error_tolerant_expr_value_can_be_error
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ if
+ end
+ STR
+ (SCOPE@1:0-3:3
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-3:3
+ mid: :m
+ body:
+ (SCOPE@1:0-3:3
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil))))
+ EXP
+ end
+
+ def test_error_tolerant_unexpected_backslash
+ node = assert_error_tolerant("\\", <<~EXP, keep_tokens: true)
+ (SCOPE@1:0-1:1 tbl: [] args: nil body: (ERROR@1:0-1:1))
+ EXP
+ assert_equal([[0, :backslash, "\\", [1, 0, 1, 1]]], node.children.last.tokens)
+ end
+
+ def test_with_bom
+ assert_error_tolerant("\u{feff}nil", <<~EXP)
+ (SCOPE@1:0-1:3 tbl: [] args: nil body: (NIL@1:0-1:3))
+ EXP
+ end
+
+ def test_unused_block_local_variable
+ assert_warning('') do
+ RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}})
+ end
+ end
+
+ def assert_error_tolerant(src, expected, keep_tokens: false)
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true, keep_tokens: keep_tokens)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ assert_nil($!)
+ str = ""
+ PP.pp(node, str, 80)
+ assert_equal(expected, str)
+ node
+ end
end
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb
index f6183f5ee2..1eb3551e57 100644
--- a/test/ruby/test_autoload.rb
+++ b/test/ruby/test_autoload.rb
@@ -65,6 +65,24 @@ p Foo::Bar
}
end
+ def test_autoload_p_with_static_extensions
+ require 'rbconfig'
+ omit unless RbConfig::CONFIG['EXTSTATIC'] == 'static'
+ begin
+ require 'fcntl.so'
+ rescue LoadError
+ omit('fcntl not included in the build')
+ end
+
+ assert_separately(['--disable-all'], <<~RUBY)
+ autoload :Fcntl, 'fcntl.so'
+
+ assert_equal('fcntl.so', autoload?(:Fcntl))
+ assert(Object.const_defined?(:Fcntl))
+ assert_equal('constant', defined?(Fcntl), '[Bug #19115]')
+ RUBY
+ end
+
def test_autoload_with_unqualified_file_name # [ruby-core:69206]
Object.send(:remove_const, :A) if Object.const_defined?(:A)
@@ -150,6 +168,7 @@ p Foo::Bar
end
def test_nameerror_when_autoload_did_not_define_the_constant
+ verbose_bak, $VERBOSE = $VERBOSE, nil
Tempfile.create(['autoload', '.rb']) {|file|
file.puts ''
file.close
@@ -162,6 +181,8 @@ p Foo::Bar
remove_autoload_constant
end
}
+ ensure
+ $VERBOSE = verbose_bak
end
def test_override_autoload
@@ -253,6 +274,11 @@ p Foo::Bar
end
def test_bug_13526
+ # Skip this on macOS 10.13 because of the following error:
+ # http://rubyci.s3.amazonaws.com/osx1013/ruby-master/log/20231011T014505Z.fail.html.gz
+ require "rbconfig"
+ omit if RbConfig::CONFIG["target_os"] == "darwin17"
+
script = File.join(__dir__, 'bug-13526.rb')
assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]')
end
@@ -443,6 +469,23 @@ p Foo::Bar
end
end
+ def test_source_location_after_require
+ bug = "Bug18624"
+ Dir.mktmpdir('autoload') do |tmpdir|
+ path = "#{tmpdir}/test-#{bug}.rb"
+ File.write(path, "C::#{bug} = __FILE__\n")
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ class C; end
+ C.autoload(:Bug18624, #{path.dump})
+ require #{path.dump}
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ assert_equal #{path.dump}, C.const_get(#{bug.dump})
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ end;
+ end
+ end
+
def test_no_memory_leak
assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60)
begin;
@@ -462,6 +505,7 @@ p Foo::Bar
File.write(autoload_path, '')
assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ $VERBOSE = nil
path = #{File.realpath(autoload_path).inspect}
autoload :X, path
assert_equal(path, Object.autoload?(:X))
@@ -529,6 +573,7 @@ p Foo::Bar
t2 = Thread.new {Bar}
t1.join
+ GC.start # force GC.
t2.join
Object.send(:remove_const, :Foo)
@@ -539,4 +584,20 @@ p Foo::Bar
RUBY
end
end
+
+ def test_autoload_parent_namespace
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "some_const.rb")
+ File.write(autoload_path, 'class SomeConst; end')
+
+ assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ module SomeNamespace
+ autoload :SomeConst, #{File.realpath(autoload_path).inspect}
+ assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do
+ assert_not_nil SomeConst
+ end
+ end
+ RUBY
+ end
+ end
end
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
index bb0562c0bb..fca7b62030 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -155,6 +155,10 @@ class TestBacktrace < Test::Unit::TestCase
end
def test_each_backtrace_location
+ assert_nil(Thread.each_caller_location {})
+
+ assert_raise(LocalJumpError) {Thread.each_caller_location}
+
i = 0
cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1}
assert_equal(cl.to_s, ecl.to_s)
@@ -181,6 +185,10 @@ class TestBacktrace < Test::Unit::TestCase
assert_raise(StopIteration) {
ecl.next
}
+
+ ary = []
+ cl = caller_locations(1, 2); Thread.each_caller_location(1, 2) {|x| ary << x}
+ assert_equal(cl.map(&:to_s), ary.map(&:to_s))
end
def test_caller_locations_first_label
@@ -215,15 +223,15 @@ class TestBacktrace < Test::Unit::TestCase
@res = caller_locations(2, 1).inspect
end
@line = __LINE__ + 1
- 1.times.map { 1.times.map { foo } }
- assert_equal("[\"#{__FILE__}:#{@line}:in `times'\"]", @res)
+ [1].map.map { [1].map.map { foo } }
+ assert_equal("[\"#{__FILE__}:#{@line}:in 'Array#map'\"]", @res)
end
def test_caller_location_path_cfunc_iseq_no_pc
def self.foo
@res = caller_locations(2, 1)[0].path
end
- 1.times.map { 1.times.map { foo } }
+ [1].map.map { [1].map.map { foo } }
assert_equal(__FILE__, @res)
end
@@ -292,13 +300,13 @@ class TestBacktrace < Test::Unit::TestCase
end
def test_caller_locations_label
- assert_equal("#{__method__}", caller_locations(0, 1)[0].label)
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
loc, = tap {break caller_locations(0, 1)}
- assert_equal("block in #{__method__}", loc.label)
+ assert_equal("block in TestBacktrace##{__method__}", loc.label)
begin
raise
rescue
- assert_equal("rescue in #{__method__}", caller_locations(0, 1)[0].label)
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
end
end
@@ -378,17 +386,17 @@ class TestBacktrace < Test::Unit::TestCase
def test_core_backtrace_hash_merge
e = assert_raise(TypeError) do
- {**nil}
+ {**1}
end
assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
end
def test_notty_backtrace
- err = ["-:1:in `<main>': unhandled exception"]
+ err = ["-:1:in '<main>': unhandled exception"]
assert_in_out_err([], "raise", [], err)
- err = ["-:2:in `foo': foo! (RuntimeError)",
- "\tfrom -:4:in `<main>'"]
+ err = ["-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:4:in '<main>'"]
assert_in_out_err([], <<-"end;", [], err)
def foo
raise "foo!"
@@ -396,12 +404,11 @@ class TestBacktrace < Test::Unit::TestCase
foo
end;
- err = ["-:7:in `rescue in bar': bar! (RuntimeError)",
- "\tfrom -:4:in `bar'",
- "\tfrom -:9:in `<main>'",
- "-:2:in `foo': foo! (RuntimeError)",
- "\tfrom -:5:in `bar'",
- "\tfrom -:9:in `<main>'"]
+ err = ["-:7:in 'Object#bar': bar! (RuntimeError)",
+ "\tfrom -:9:in '<main>'",
+ "-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:5:in 'Object#bar'",
+ "\tfrom -:9:in '<main>'"]
assert_in_out_err([], <<-"end;", [], err)
def foo
raise "foo!"
@@ -416,7 +423,7 @@ class TestBacktrace < Test::Unit::TestCase
end
def test_caller_to_enum
- err = ["-:3:in `foo': unhandled exception", "\tfrom -:in `each'"]
+ err = ["-:3:in 'Object#foo': unhandled exception", "\tfrom -:in 'Enumerator#each'"]
assert_in_out_err([], <<-"end;", [], err, "[ruby-core:91911]")
def foo
return to_enum(__method__) unless block_given?
@@ -428,4 +435,23 @@ class TestBacktrace < Test::Unit::TestCase
enum.next
end;
end
+
+ def test_no_receiver_for_anonymous_class
+ err = ["-:2:in 'bar': unhandled exception", # Not '#<Class:0xXXX>.bar'
+ "\tfrom -:3:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ def foo.bar = raise
+ foo.bar
+ end;
+
+ err = ["-:3:in 'baz': unhandled exception", # Not '#<Class:0xXXX>::Bar.baz'
+ "\tfrom -:4:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ foo::Bar = Class.new
+ def (foo::Bar).baz = raise
+ foo::Bar.baz
+ end;
+ end
end
diff --git a/test/ruby/test_beginendblock.rb b/test/ruby/test_beginendblock.rb
index eb8394864f..3706efab52 100644
--- a/test/ruby/test_beginendblock.rb
+++ b/test/ruby/test_beginendblock.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
+EnvUtil.suppress_warning {require 'continuation'}
class TestBeginEndBlock < Test::Unit::TestCase
DIR = File.dirname(File.expand_path(__FILE__))
@@ -14,6 +15,11 @@ class TestBeginEndBlock < Test::Unit::TestCase
assert_in_out_err(["-p", "-eBEGIN{p :begin}", "-eEND{p :end}"], "foo\nbar\n", %w(:begin foo bar :end))
end
+ def test_endblock_variable
+ assert_in_out_err(["-n", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(:ok))
+ assert_in_out_err(["-p", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(foo :ok))
+ end
+
def test_begininmethod
assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do
eval("def foo; BEGIN {}; end")
@@ -40,9 +46,9 @@ class TestBeginEndBlock < Test::Unit::TestCase
end
def test_endblockwarn_in_eval
- assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['(eval):2: warning: END in method; use at_exit'])
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['test.rb:1: warning: END in method; use at_exit'])
begin;
- eval <<-EOE
+ eval <<-EOE, nil, "test.rb", 0
def end2
END {}
end
@@ -62,7 +68,7 @@ class TestBeginEndBlock < Test::Unit::TestCase
bug8501 = '[ruby-core:55365] [Bug #8501]'
args = ['-e', 'o = Object.new; def o.inspect; raise "[Bug #8501]"; end',
'-e', 'at_exit{o.nope}']
- status = assert_in_out_err(args, '', [], /undefined method `nope'/, bug8501)
+ status = assert_in_out_err(args, '', [], /undefined method 'nope'/, bug8501)
assert_not_predicate(status, :success?, bug8501)
end
@@ -126,6 +132,8 @@ class TestBeginEndBlock < Test::Unit::TestCase
end
def test_callcc_at_exit
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9110 = '[ruby-core:58329][Bug #9110]'
assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug9110)
begin;
diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb
index 53a2b20cc8..1f882c6cb9 100644
--- a/test/ruby/test_bignum.rb
+++ b/test/ruby/test_bignum.rb
@@ -203,6 +203,15 @@ class TestBignum < Test::Unit::TestCase
assert_equal(00_02, '00_02'.to_i)
end
+ def test_very_big_str_to_inum
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ digits = [["3", 700], ["0", 2700], ["1", 1], ["0", 26599]]
+ num = digits.inject(+"") {|s,(c,n)|s << c*n}.to_i
+ assert_equal digits.sum {|c,n|n}, num.to_s.size
+ end;
+ end
+
def test_to_s2
assert_raise(ArgumentError) { T31P.to_s(37) }
assert_equal("9" * 32768, (10**32768-1).to_s)
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index 67b3a936d4..a52b75c267 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
+require '-test-/iter'
class TestCall < Test::Unit::TestCase
def aaa(a, b=100, *rest)
@@ -47,12 +48,19 @@ class TestCall < Test::Unit::TestCase
assert_equal(5, o.y)
o&.z ||= 6
assert_equal(6, o.z)
+ o&.z &&= 7
+ assert_equal(7, o.z)
o = nil
assert_nil(o&.x)
assert_nothing_raised(NoMethodError) {o&.x = raise}
+ assert_nothing_raised(NoMethodError) {o&.x = raise; nil}
assert_nothing_raised(NoMethodError) {o&.x *= raise}
assert_nothing_raised(NoMethodError) {o&.x *= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise; nil}
end
def test_safe_call_evaluate_arguments_only_method_call_is_made
@@ -92,6 +100,209 @@ class TestCall < Test::Unit::TestCase
}
end
+ def test_frozen_splat_and_keywords
+ a = [1, 2].freeze
+ def self.f(*a); a end
+ assert_equal([1, 2, {kw: 3}], f(*a, kw: 3))
+ end
+
+ def test_call_bmethod_proc
+ pr = proc{|sym| sym}
+ define_singleton_method(:a, &pr)
+ ary = [10]
+ assert_equal(10, a(*ary))
+ end
+
+ def test_call_bmethod_proc_restarg
+ pr = proc{|*sym| sym}
+ define_singleton_method(:a, &pr)
+ ary = [10]
+ assert_equal([10], a(*ary))
+ assert_equal([10], a(10))
+ end
+
+ def test_call_op_asgn_keywords
+ h = Class.new do
+ attr_reader :get, :set
+ def v; yield; [*@get, *@set] end
+ def [](*a, **b, &c) @get = [a, b, c]; @set = []; 3 end
+ def []=(*a, **b, &c) @set = [a, b, c] end
+ end.new
+
+ a = []
+ kw = {}
+ b = lambda{}
+
+ # Prevent "assigned but unused variable" warnings
+ _ = [h, a, kw, b]
+
+ message = /keyword arg given in index/
+
+ # +=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] += 1}, message)
+ assert_syntax_error(%q{h[0, **kw] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1}, message)
+
+ # +=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1}, message)
+
+ # +=, without block, popped
+ assert_syntax_error(%q{h[**kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}, message)
+
+ # +=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1; nil}, message)
+
+ # &&=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] &&= 1}, message)
+ assert_syntax_error(%q{h[0, **kw] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}, message)
+
+ # &&=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1}, message)
+
+ # &&=, without block, popped
+ assert_syntax_error(%q{h[**kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}, message)
+
+ # &&=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1; nil}, message)
+
+ # ||=, without block, non-popped
+ assert_syntax_error(%q{h[**kw] ||= 1}, message)
+ assert_syntax_error(%q{h[0, **kw] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}, message)
+
+ # ||=, with block, non-popped
+ assert_syntax_error(%q{h[**kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1}, message)
+
+ # ||=, without block, popped
+ assert_syntax_error(%q{h[**kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}, message)
+
+ # ||=, with block, popped
+ assert_syntax_error(%q{h[**kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, **kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1; nil}, message)
+ assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1; nil}, message)
+
+ end
+
+ def test_kwsplat_block_order_op_asgn
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+
+ def o.[](...) 2 end
+ def o.[]=(...) end
+
+ message = /keyword arg given in index/
+
+ assert_syntax_error(%q{o[kw: 1] += 1}, message)
+ assert_syntax_error(%q{o[**o] += 1}, message)
+ assert_syntax_error(%q{o[**o, &o] += 1}, message)
+ assert_syntax_error(%q{o[*o, **o, &o] += 1}, message)
+ end
+
+ def test_call_op_asgn_keywords_mutable
+ h = Class.new do
+ attr_reader :get, :set
+ def v; yield; [*@get, *@set] end
+ def [](*a, **b)
+ @get = [a.dup, b.dup]
+ a << :splat_modified
+ b[:kw_splat_modified] = true
+ @set = []
+ 3
+ end
+ def []=(*a, **b) @set = [a, b] end
+ end.new
+
+ message = /keyword arg given in index/
+
+ a = []
+ kw = {}
+
+ # Prevent "assigned but unused variable" warnings
+ _ = [h, a, kw]
+
+ assert_syntax_error(%q{h[*a, 2, b: 5, **kw] += 1}, message)
+ end
+
def test_call_splat_order
bug12860 = '[ruby-core:77701] [Bug# 12860]'
ary = [1, 2]
@@ -108,4 +319,1014 @@ class TestCall < Test::Unit::TestCase
ary = [1, 2, b]
assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504)
end
+
+ def test_call_args_splat_with_nonhash_keyword_splat
+ o = Object.new
+ def o.to_hash; {a: 1} end
+ def self.f(*a, **kw)
+ kw
+ end
+ assert_equal Hash, f(*[], **o).class
+ end
+
+ def test_kwsplat_block_order
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+
+ def self.t(...) end
+
+ t(**o, &o)
+ assert_equal([:to_hash, :to_proc], ary)
+
+ ary.clear
+ t(*o, **o, &o)
+ assert_equal([:to_a, :to_hash, :to_proc], ary)
+ end
+
+ def test_kwsplat_block_order_super
+ def self.t(splat)
+ o = Object.new
+ ary = []
+ o.define_singleton_method(:to_a) {ary << :to_a; []}
+ o.define_singleton_method(:to_hash) {ary << :to_hash; {}}
+ o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}}
+ if splat
+ super(*o, **o, &o)
+ else
+ super(**o, &o)
+ end
+ ary
+ end
+ extend Module.new{def t(...) end}
+
+ assert_equal([:to_hash, :to_proc], t(false))
+ assert_equal([:to_a, :to_hash, :to_proc], t(true))
+ end
+
+ OVER_STACK_LEN = (ENV['RUBY_OVER_STACK_LEN'] || 150).to_i # Greater than VM_ARGC_STACK_MAX
+ OVER_STACK_ARGV = OVER_STACK_LEN.times.to_a.freeze
+
+ def test_call_cfunc_splat_large_array_bug_4040
+ a = OVER_STACK_ARGV
+
+ assert_equal(a, [].push(*a))
+ assert_equal(a, [].push(a[0], *a[1..]))
+ assert_equal(a, [].push(a[0], a[1], *a[2..]))
+ assert_equal(a, [].push(*a[0..1], *a[2..]))
+ assert_equal(a, [].push(*a[...-1], a[-1]))
+ assert_equal(a, [].push(a[0], *a[1...-1], a[-1]))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1]))
+ assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1]))
+ assert_equal(a, [].push(*a[...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1]))
+ assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1]))
+
+ kw = {x: 1}
+ a_kw = a + [kw]
+
+ assert_equal(a_kw, [].push(*a, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1..], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], **kw))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw))
+
+ assert_equal(a_kw, [].push(*a, x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1..], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1))
+
+ a_kw[-1][:y] = 2
+ kw = {y: 2}
+
+ assert_equal(a_kw, [].push(*a, x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1..], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1, **kw))
+ assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1, **kw))
+
+ kw = {}
+
+ assert_equal(a, [].push(*a, **kw))
+ assert_equal(a, [].push(a[0], *a[1..], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2..], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2..], **kw))
+ assert_equal(a, [].push(*a[...-1], a[-1], **kw))
+ assert_equal(a, [].push(a[0], *a[1...-1], a[-1], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1], **kw))
+ assert_equal(a, [].push(*a[...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw))
+ assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw))
+
+ a_kw = a + [Hash.ruby2_keywords_hash({})]
+ assert_equal(a, [].push(*a_kw))
+
+ # Single test with value that would cause SystemStackError.
+ # Not all tests use such a large array to reduce testing time.
+ assert_equal(1380888, [].push(*1380888.times.to_a).size)
+ end
+
+ def test_call_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, a(*OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], b(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], c(*OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], d(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_proc_large_array_splat_pass
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, l.call(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, proc{|*a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|_, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|_, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, proc{|*a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, **kw| a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1, **kw| a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_proc_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ l = instance_eval("proc{|#{args}| [#{args}]}")
+ assert_equal OVER_STACK_ARGV, l.(*OVER_STACK_ARGV)
+
+ l = instance_eval("proc{|#{args}, b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [nil], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}| [#{args1}]}")
+ assert_equal(OVER_STACK_ARGV[0...-1], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, *b| [#{args}, b]}")
+ assert_equal(OVER_STACK_ARGV + [[]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args1}, *b| [#{args1}, b]}")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c| [#{args}, b, c]}")
+ assert_equal(OVER_STACK_ARGV + [nil, []], l.(*OVER_STACK_ARGV))
+
+ l = instance_eval("proc{|#{args}, b, *c, d| [#{args}, b, c, d]}")
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], l.(*OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_lambda_large_array_splat_fail
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ l.call(*OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_lambda_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, ->(*a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(_, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(_, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b, *a){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, ->(*a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, **kw){a.length}.(*OVER_STACK_ARGV)
+ assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1, **kw){a.length}.(*OVER_STACK_ARGV)
+ end
+
+ def test_call_yield_block_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ [
+ proc{0} ,
+ proc{|a=1|a},
+ proc{|k: 1|0},
+ proc{|**kw| 0},
+ proc{|k: 1, **kw| 0},
+ proc{|a=1, k: 1| a},
+ proc{|a=1, **kw| a},
+ proc{|a=1, k: 1, **kw| a},
+ ].each do |l|
+ assert_equal 0, a(&l)
+ end
+
+ assert_equal OVER_STACK_LEN, a{|*a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b, *a| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, a{|*a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1, **kw| a.length}
+ end
+
+ def test_call_yield_large_array_splat_with_large_number_of_parameters
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ assert_equal OVER_STACK_ARGV, instance_eval("a{|#{args}| [#{args}]}", __FILE__, __LINE__)
+ assert_equal(OVER_STACK_ARGV + [nil], instance_eval("a{|#{args}, b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1], instance_eval("a{|#{args1}| [#{args1}]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [[]], instance_eval("a{|#{args}, *b| [#{args}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], instance_eval("a{|#{args1}, *b| [#{args1}, b]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, []], instance_eval("a{|#{args}, b, *c| [#{args}, b, c]}", __FILE__, __LINE__))
+ assert_equal(OVER_STACK_ARGV + [nil, [], nil], instance_eval("a{|#{args}, b, *c, d| [#{args}, b, c, d]}", __FILE__, __LINE__))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_yield_lambda_large_array_splat_fail
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+ [
+ ->{} ,
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ a(&l)
+ end
+ end
+ end
+
+ def test_call_yield_lambda_large_array_splat_pass
+ def self.a
+ yield(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, a(&->(*a){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(_, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(_, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b, *a){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, **kw){a.length})
+ assert_equal OVER_STACK_LEN, a(&->(*a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, **kw){a.length})
+ assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1, **kw){a.length})
+ end
+
+ def test_call_send_iseq_large_array_splat_fail
+ def self.a; end
+ def self.b(a=1); end
+ def self.c(k: 1); end
+ def self.d(**kw); end
+ def self.e(k: 1, **kw); end
+ def self.f(a=1, k: 1); end
+ def self.g(a=1, **kw); end
+ def self.h(a=1, k: 1, **kw); end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ send(meth, *OVER_STACK_ARGV)
+ end
+ end
+ end
+
+ def test_call_send_iseq_large_array_splat_pass
+ def self.a(*a); a.length end
+ assert_equal OVER_STACK_LEN, send(:a, *OVER_STACK_ARGV)
+
+ def self.b(_, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:b, *OVER_STACK_ARGV)
+
+ def self.c(_, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:c, *OVER_STACK_ARGV)
+
+ def self.d(b=1, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:d, *OVER_STACK_ARGV)
+
+ def self.e(b=1, *a, _); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:e, *OVER_STACK_ARGV)
+
+ def self.f(b, *a); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:f, *OVER_STACK_ARGV)
+
+ def self.g(*a, k: 1); a.length end
+ assert_equal OVER_STACK_LEN, send(:g, *OVER_STACK_ARGV)
+
+ def self.h(*a, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:h, *OVER_STACK_ARGV)
+
+ def self.i(*a, k: 1, **kw); a.length end
+ assert_equal OVER_STACK_LEN, send(:i, *OVER_STACK_ARGV)
+
+ def self.j(b=1, *a, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:j, *OVER_STACK_ARGV)
+
+ def self.k(b=1, *a, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:k, *OVER_STACK_ARGV)
+
+ def self.l(b=1, *a, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 1), send(:l, *OVER_STACK_ARGV)
+
+ def self.m(b=1, *a, _, k: 1); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:m, *OVER_STACK_ARGV)
+
+ def self.n(b=1, *a, _, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:n, *OVER_STACK_ARGV)
+
+ def self.o(b=1, *a, _, k: 1, **kw); a.length end
+ assert_equal (OVER_STACK_LEN - 2), send(:o, *OVER_STACK_ARGV)
+ end
+
+ def test_call_send_iseq_large_array_splat_with_large_number_of_parameters
+ args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',')
+ args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',')
+
+ singleton_class.class_eval("def a(#{args}); [#{args}] end")
+ assert_equal OVER_STACK_ARGV, send(:a, *OVER_STACK_ARGV)
+
+ singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [0], send(:b, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end")
+ assert_equal(OVER_STACK_ARGV + [[]], send(:c, *OVER_STACK_ARGV))
+
+ singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end")
+ assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], send(:d, *OVER_STACK_ARGV))
+ end if OVER_STACK_LEN < 200
+
+ def test_call_send_cfunc_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:object_id, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_send_cfunc_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, [].send(:push, *OVER_STACK_ARGV).length
+ end
+
+ def test_call_attr_reader_large_array_splat_fail
+ singleton_class.send(:attr_reader, :a)
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_attr_writer_large_array_splat_fail
+ singleton_class.send(:attr_writer, :a)
+ singleton_class.send(:alias_method, :a, :a=)
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aref_large_array_splat_fail
+ s = Struct.new(:a).new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ s.send(:a, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_struct_aset_large_array_splat_fail
+ s = Struct.new(:a) do
+ alias b a=
+ end.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ s.send(:b, *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_alias_large_array_splat
+ c = Class.new do
+ def a; end
+ def c(*a); a.length end
+ attr_accessor :e
+ end
+ sc = Class.new(c) do
+ alias b a
+ alias d c
+ alias f e
+ alias g e=
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.b(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.f(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do
+ obj.g(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.d(*OVER_STACK_ARGV)
+ end
+
+ def test_call_zsuper_large_array_splat
+ c = Class.new do
+ private
+ def a; end
+ def c(*a); a.length end
+ attr_reader :e
+ end
+ sc = Class.new(c) do
+ public :a
+ public :c
+ public :e
+ end
+
+ obj = sc.new
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.a(*OVER_STACK_ARGV)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ obj.e(*OVER_STACK_ARGV)
+ end
+
+ assert_equal OVER_STACK_LEN, obj.c(*OVER_STACK_ARGV)
+ end
+
+ class RefinedModuleLargeArrayTest
+ c = self
+ using(Module.new do
+ refine c do
+ def a; end
+ def c(*a) a.length end
+ attr_reader :e
+ end
+ end)
+
+ def b
+ a(*OVER_STACK_ARGV)
+ end
+
+ def d
+ c(*OVER_STACK_ARGV)
+ end
+
+ def f
+ e(*OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_refined_large_array_splat_fail
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.b
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do
+ RefinedModuleLargeArrayTest.new.f
+ end
+ end
+
+ def test_call_refined_large_array_splat_pass
+ assert_equal OVER_STACK_LEN, RefinedModuleLargeArrayTest.new.d
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_fail
+ def self.method_missing(_) end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_iseq_large_array_splat_pass
+ def self.method_missing(m, *a)
+ a.length
+ end
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_bmethod_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_bmethod_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV)
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_fail
+ define_singleton_method(:method_missing){|_|}
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ nonexistent_method(*OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send(:nonexistent_method, *OVER_STACK_ARGV)
+ end
+
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do
+ send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+ end
+
+ def test_call_method_missing_bmethod_large_array_splat_pass
+ define_singleton_method(:method_missing){|_, *a| a.length}
+ assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV)
+ assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV)
+ end
+
+ def test_call_symproc_large_array_splat_fail
+ define_singleton_method(:a){}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ instance_eval(":#{meth}.to_proc.(self, *OVER_STACK_ARGV)", __FILE__, __LINE__)
+ end
+ end
+ end
+
+ def test_call_symproc_large_array_splat_pass
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, :a.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :b.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :c.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :d.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :e.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :f.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, :g.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :h.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, :i.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :j.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :k.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), :l.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :m.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :n.to_proc.(self, *OVER_STACK_ARGV)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), :o.to_proc.(self, *OVER_STACK_ARGV)
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ def self.a; end
+ def self.b(a=1) end
+ def self.c(k: 1) end
+ def self.d(**kw) end
+ def self.e(k: 1, **kw) end
+ def self.f(a=1, k: 1) end
+ def self.g(a=1, **kw) end
+ def self.h(a=1, k: 1, **kw) end
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ def self.a(*a) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ def self.b(_, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ def self.c(_, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ def self.d(b=1, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ def self.e(b=1, *a, _) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ def self.f(b, *a) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ def self.g(*a, k: 1) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ def self.h(*a, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.i(*a, k: 1, **kw) a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ def self.j(b=1, *a, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ def self.k(b=1, *a, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ def self.l(b=1, *a, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ def self.m(b=1, *a, _, k: 1) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ def self.n(b=1, *a, _, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ def self.o(b=1, *a, _, k: 1, **kw) a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_fail
+ extend Bug::Iter::Yield
+ l = ->(*a){}
+
+ define_singleton_method(:a){||}
+ define_singleton_method(:b){|a=1|}
+ define_singleton_method(:c){|k: 1|}
+ define_singleton_method(:d){|**kw|}
+ define_singleton_method(:e){|k: 1, **kw|}
+ define_singleton_method(:f){|a=1, k: 1|}
+ define_singleton_method(:g){|a=1, **kw|}
+ define_singleton_method(:h){|a=1, k: 1, **kw|}
+
+ (:a..:h).each do |meth|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(meth, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_rb_call_bmethod_large_array_splat_pass
+ extend Bug::Iter::Yield
+ l = ->(*a){a.length}
+
+ define_singleton_method(:a){|*a| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:b){|_, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:c){|_, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:d){|b=1, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:e){|b=1, *a, _| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:f){|b, *a| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:g){|*a, k: 1| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:h){|*a, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:i){|*a, k: 1, **kw| a.length}
+ assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:j){|b=1, *a, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:k){|b=1, *a, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:m){|b=1, *a, _, k: 1| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:n){|b=1, *a, _, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l)
+
+ define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length}
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l)
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_fail
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+ [
+ ->(){},
+ ->(a=1){},
+ ->(k: 1){},
+ ->(**kw){},
+ ->(k: 1, **kw){},
+ ->(a=1, k: 1){},
+ ->(a=1, **kw){},
+ ->(a=1, k: 1, **kw){},
+ ].each do |l|
+ assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do
+ yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
+ end
+ end
+
+ def test_call_ifunc_iseq_large_array_splat_pass
+ extend Bug::Iter::Yield
+ def self.a(*a)
+ yield(*a)
+ end
+
+ l = ->(*a) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(_, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b, *a) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(*a, k: 1, **kw) do a.length end
+ assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+
+ l = ->(b=1, *a, _, k: 1, **kw) do a.length end
+ assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l)
+ end
end
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index 07c34ce9d5..710b8a6f7b 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -96,6 +96,13 @@ class TestClass < Test::Unit::TestCase
def test_superclass_of_basicobject
assert_equal(nil, BasicObject.superclass)
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ module Mod end
+ BasicObject.include(Mod)
+ assert_equal(nil, BasicObject.superclass)
+ end;
end
def test_module_function
@@ -355,6 +362,17 @@ class TestClass < Test::Unit::TestCase
assert_equal(42, PrivateClass.new.foo)
end
+ def test_private_const_access
+ assert_raise_with_message NameError, /uninitialized/ do
+ begin
+ eval('class ::TestClass::PrivateClass; end')
+ rescue NameError
+ end
+
+ Object.const_get "NOT_AVAILABLE_CONST_NAME_#{__LINE__}"
+ end
+ end
+
StrClone = String.clone
Class.new(StrClone)
@@ -759,6 +777,31 @@ class TestClass < Test::Unit::TestCase
end
end
+ def test_attached_object
+ c = Class.new
+ sc = c.singleton_class
+ obj = c.new
+
+ assert_equal(obj, obj.singleton_class.attached_object)
+ assert_equal(c, sc.attached_object)
+
+ assert_raise_with_message(TypeError, /is not a singleton class/) do
+ c.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'NilClass' is not a singleton class/) do
+ nil.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'FalseClass' is not a singleton class/) do
+ false.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /'TrueClass' is not a singleton class/) do
+ true.singleton_class.attached_object
+ end
+ end
+
def test_subclass_gc
c = Class.new
10_000.times do
@@ -784,4 +827,13 @@ PREP
3_000_000.times(&code)
CODE
end
+
+ def test_instance_freeze_dont_freeze_the_class_bug_19164
+ klass = Class.new
+ klass.prepend(Module.new)
+
+ klass.new.freeze
+ klass.define_method(:bar) {}
+ assert_equal klass, klass.remove_method(:bar), '[Bug #19164]'
+ end
end
diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb
index 321feb07c7..775c9ed848 100644
--- a/test/ruby/test_clone.rb
+++ b/test/ruby/test_clone.rb
@@ -27,6 +27,59 @@ class TestClone < Test::Unit::TestCase
assert_equal([M003, M002, M001], M003.ancestors)
end
+ def test_frozen_properties_retained_on_clone
+ obj = Object.new.freeze
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_predicate(cloned_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_clone
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ cloned_obj = obj.clone
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_clone
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ cloned_obj = obj.clone
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(cloned_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_and_ivars_retained_on_clone_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ assert_predicate(cloned_obj, :frozen?)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_proc_obj_id_flag_reset
+ # [Bug #20250]
+ proc = Proc.new { }
+ proc.object_id
+ proc.clone.object_id # Would crash with RUBY_DEBUG=1
+ end
+
def test_user_flags
assert_separately([], <<-EOS)
#
diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb
index b849217b7d..b689469d9e 100644
--- a/test/ruby/test_comparable.rb
+++ b/test/ruby/test_comparable.rb
@@ -85,7 +85,13 @@ class TestComparable < Test::Unit::TestCase
assert_equal(1, @o.clamp(1, 1))
assert_equal(@o, @o.clamp(0, 0))
- assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') {
+ assert_equal(@o, @o.clamp(nil, 2))
+ assert_equal(-2, @o.clamp(nil, -2))
+ assert_equal(@o, @o.clamp(-2, nil))
+ assert_equal(2, @o.clamp(2, nil))
+ assert_equal(@o, @o.clamp(nil, nil))
+
+ assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') {
@o.clamp(2, 1)
}
end
@@ -115,7 +121,7 @@ class TestComparable < Test::Unit::TestCase
assert_raise_with_message(*exc) {@o.clamp(-1...0)}
assert_raise_with_message(*exc) {@o.clamp(...2)}
- assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') {
+ assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') {
@o.clamp(2..1)
}
end
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
new file mode 100644
index 0000000000..d13b150f93
--- /dev/null
+++ b/test/ruby/test_compile_prism.rb
@@ -0,0 +1,2671 @@
+# frozen_string_literal: true
+
+# This file is organized to match itemization in https://github.com/ruby/prism/issues/1335
+module Prism
+ class TestCompilePrism < Test::Unit::TestCase
+ # Subclass is used for tests which need it
+ class Subclass; end
+ ############################################################################
+ # Literals #
+ ############################################################################
+
+ def test_FalseNode
+ assert_prism_eval("false")
+ end
+
+ def test_FloatNode
+ assert_prism_eval("1.2")
+ assert_prism_eval("1.2e3")
+ assert_prism_eval("+1.2e+3")
+ assert_prism_eval("-1.2e-3")
+ end
+
+ def test_ImaginaryNode
+ assert_prism_eval("1i")
+ assert_prism_eval("+1.0i")
+ assert_prism_eval("1ri")
+ end
+
+ def test_IntegerNode
+ assert_prism_eval("1")
+ assert_prism_eval("+1")
+ assert_prism_eval("-1")
+ assert_prism_eval("0x10")
+ assert_prism_eval("0b10")
+ assert_prism_eval("0o10")
+ assert_prism_eval("010")
+ assert_prism_eval("(0o00)")
+ end
+
+ def test_NilNode
+ assert_prism_eval("nil")
+ end
+
+ def test_RationalNode
+ assert_prism_eval("1.2r")
+ assert_prism_eval("+1.2r")
+ end
+
+ def test_SelfNode
+ assert_prism_eval("self")
+ end
+
+ def test_SourceEncodingNode
+ assert_prism_eval("__ENCODING__")
+ end
+
+ def test_SourceFileNode
+ assert_prism_eval("__FILE__")
+ end
+
+ def test_SourceLineNode
+ assert_prism_eval("__LINE__", raw: true)
+ end
+
+ def test_TrueNode
+ assert_prism_eval("true")
+ end
+
+ ############################################################################
+ # Reads #
+ ############################################################################
+
+ def test_BackReferenceReadNode
+ assert_prism_eval("$+")
+ end
+
+ def test_ClassVariableReadNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit; end")
+ end
+
+ def test_ConstantPathNode
+ assert_prism_eval("Prism::TestCompilePrism")
+ end
+
+ def test_ConstantReadNode
+ assert_prism_eval("Prism")
+ end
+
+ Z = 1
+
+ def test_DefinedNode
+ assert_prism_eval("defined? nil")
+ assert_prism_eval("defined? self")
+ assert_prism_eval("defined? true")
+ assert_prism_eval("defined? false")
+ assert_prism_eval("defined? 1")
+ assert_prism_eval("defined? 1i")
+ assert_prism_eval("defined? 1.0")
+ assert_prism_eval("defined? 1..2")
+ assert_prism_eval("defined? [A, B, C]")
+ assert_prism_eval("defined? [1, 2, 3]")
+ assert_prism_eval("defined?({ a: 1 })")
+ assert_prism_eval("defined? 'str'")
+ assert_prism_eval('defined?("#{expr}")')
+ assert_prism_eval("defined? :sym")
+ assert_prism_eval("defined? /foo/")
+ assert_prism_eval('defined?(/#{1}/)')
+ assert_prism_eval("defined? -> { 1 + 1 }")
+ assert_prism_eval("defined? a && b")
+ assert_prism_eval("defined? a || b")
+ assert_prism_eval("defined? __ENCODING__")
+ assert_prism_eval("defined? __FILE__")
+ assert_prism_eval("defined? __LINE__")
+
+ assert_prism_eval("defined? %[1,2,3]")
+ assert_prism_eval("defined? %q[1,2,3]")
+ assert_prism_eval("defined? %Q[1,2,3]")
+ assert_prism_eval("defined? %r[1,2,3]")
+ assert_prism_eval("defined? %i[1,2,3]")
+ assert_prism_eval("defined? %I[1,2,3]")
+ assert_prism_eval("defined? %w[1,2,3]")
+ assert_prism_eval("defined? %W[1,2,3]")
+ assert_prism_eval("defined? %s[1,2,3]")
+ assert_prism_eval("defined? %x[1,2,3]")
+
+ assert_prism_eval("defined? [*b]")
+ assert_prism_eval("defined? [[*1..2], 3, *4..5]")
+ assert_prism_eval("defined? [a: [:b, :c]]")
+ assert_prism_eval("defined? 1 in 1")
+
+ assert_prism_eval("defined? @a")
+ assert_prism_eval("defined? $a")
+ assert_prism_eval("defined? @@a")
+ assert_prism_eval("defined? A")
+ assert_prism_eval("defined? ::A")
+ assert_prism_eval("defined? A::B")
+ assert_prism_eval("defined? A::B::C")
+ assert_prism_eval("defined? #{self.class.name}::Z::A")
+ assert_prism_eval("defined? yield")
+ assert_prism_eval("defined? super")
+
+ assert_prism_eval("defined? X = 1")
+ assert_prism_eval("defined? X *= 1")
+ assert_prism_eval("defined? X /= 1")
+ assert_prism_eval("defined? X &= 1")
+ assert_prism_eval("defined? X ||= 1")
+
+ assert_prism_eval("defined? $1")
+ assert_prism_eval("defined? $2")
+ assert_prism_eval("defined? $`")
+ assert_prism_eval("defined? $'")
+ assert_prism_eval("defined? $+")
+
+ assert_prism_eval("defined? $X = 1")
+ assert_prism_eval("defined? $X *= 1")
+ assert_prism_eval("defined? $X /= 1")
+ assert_prism_eval("defined? $X &= 1")
+ assert_prism_eval("defined? $X ||= 1")
+
+ assert_prism_eval("defined? @@X = 1")
+ assert_prism_eval("defined? @@X *= 1")
+ assert_prism_eval("defined? @@X /= 1")
+ assert_prism_eval("defined? @@X &= 1")
+ assert_prism_eval("defined? @@X ||= 1")
+
+ assert_prism_eval("defined? @X = 1")
+ assert_prism_eval("defined? @X *= 1")
+ assert_prism_eval("defined? @X /= 1")
+ assert_prism_eval("defined? @X &= 1")
+ assert_prism_eval("defined? @X ||= 1")
+
+ assert_prism_eval("x = 1; defined? x = 1")
+ assert_prism_eval("x = 1; defined? x *= 1")
+ assert_prism_eval("x = 1; defined? x /= 1")
+ assert_prism_eval("x = 1; defined? x &= 1")
+ assert_prism_eval("x = 1; defined? x ||= 1")
+
+ assert_prism_eval("if defined? A; end")
+
+ assert_prism_eval("defined?(())")
+ assert_prism_eval("defined?(('1'))")
+
+ # method chain starting with self that's truthy
+ assert_prism_eval("defined?(self.itself.itself.itself)")
+
+ # method chain starting with self that's false (exception swallowed)
+ assert_prism_eval("defined?(self.itself.itself.neat)")
+
+ # single self with method, truthy
+ assert_prism_eval("defined?(self.itself)")
+
+ # single self with method, false
+ assert_prism_eval("defined?(self.neat!)")
+
+ # method chain implicit self that's truthy
+ assert_prism_eval("defined?(itself.itself.itself)")
+
+ # method chain implicit self that's false
+ assert_prism_eval("defined?(itself.neat.itself)")
+
+ ## single method implicit self that's truthy
+ assert_prism_eval("defined?(itself)")
+
+ ## single method implicit self that's false
+ assert_prism_eval("defined?(neatneat)")
+
+ assert_prism_eval("defined?(a(itself))")
+ assert_prism_eval("defined?(itself(itself))")
+
+ # Method chain on a constant
+ assert_prism_eval(<<~RUBY)
+ class PrismDefinedNode
+ def m1; end
+ end
+
+ defined?(PrismDefinedNode.new.m1)
+ RUBY
+
+ assert_prism_eval("defined?(next)")
+ assert_prism_eval("defined?(break)")
+ assert_prism_eval("defined?(redo)")
+ assert_prism_eval("defined?(retry)")
+
+ assert_prism_eval(<<~RUBY)
+ class PrismDefinedReturnNode
+ def self.m1; defined?(return) end
+ end
+
+ PrismDefinedReturnNode.m1
+ RUBY
+
+ assert_prism_eval("defined?(begin; 1; end)")
+
+ assert_prism_eval("defined?(defined?(a))")
+ assert_prism_eval('defined?(:"#{1}")')
+ assert_prism_eval("defined?(`echo #{1}`)")
+
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_and_write_node &&= 1)")
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_operator_write_node += 1)")
+ assert_prism_eval("defined?(PrismTestSubclass.test_call_or_write_node ||= 1)")
+ assert_prism_eval("defined?(Prism::CPAWN &&= 1)")
+ assert_prism_eval("defined?(Prism::CPOWN += 1)")
+ assert_prism_eval("defined?(Prism::CPOrWN ||= 1)")
+ assert_prism_eval("defined?(Prism::CPWN = 1)")
+ assert_prism_eval("defined?([0][0] &&= 1)")
+ assert_prism_eval("defined?([0][0] += 1)")
+ assert_prism_eval("defined?([0][0] ||= 1)")
+
+ assert_prism_eval("defined?(case :a; when :a; 1; else; 2; end)")
+ assert_prism_eval("defined?(case [1, 2, 3]; in [1, 2, 3]; 4; end)")
+ assert_prism_eval("defined?(class PrismClassA; end)")
+ assert_prism_eval("defined?(def prism_test_def_node; end)")
+ assert_prism_eval("defined?(for i in [1,2] do; i; end)")
+ assert_prism_eval("defined?(if true; 1; end)")
+ assert_prism_eval("defined?(/(?<foo>bar)/ =~ 'barbar')")
+ assert_prism_eval("defined?(1 => 1)")
+ assert_prism_eval("defined?(module M; end)")
+ assert_prism_eval("defined?(1.2r)")
+ assert_prism_eval("defined?(class << self; end)")
+ assert_prism_eval("defined?(while a != 1; end)")
+ assert_prism_eval("defined?(until a == 1; end)")
+ assert_prism_eval("defined?(unless true; 1; end)")
+ end
+
+ def test_GlobalVariableReadNode
+ assert_prism_eval("$pit = 1; $pit")
+ end
+
+ def test_InstanceVariableReadNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; @pit; end")
+ end
+
+ def test_LocalVariableReadNode
+ assert_prism_eval("pit = 1; pit")
+ end
+
+ def test_NumberedReferenceReadNode
+ assert_prism_eval("$1")
+ assert_prism_eval("$99999")
+ end
+
+ ############################################################################
+ # Writes #
+ ############################################################################
+
+ def test_ClassVariableAndWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit &&= 1; end")
+ end
+
+ def test_ClassVariableOperatorWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit += 1; end")
+ end
+
+ def test_ClassVariableOrWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit ||= 0; end")
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = nil; @@pit ||= 1; end")
+ end
+
+ def test_ClassVariableWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; end")
+ end
+
+ def test_ConstantAndWriteNode
+ assert_prism_eval("Constant = 1; Constant &&= 1")
+ end
+
+ def test_ConstantOperatorWriteNode
+ assert_prism_eval("Constant = 1; Constant += 1")
+ end
+
+ def test_ConstantOrWriteNode
+ assert_prism_eval("Constant = 1; Constant ||= 1")
+ end
+
+ def test_ConstantWriteNode
+ # We don't call assert_prism_eval directly in this case because we
+ # don't want to assign the constant multiple times if we run
+ # with `--repeat-count`
+ # Instead, we eval manually here, and remove the constant to
+ constant_name = "YCT"
+ source = "#{constant_name} = 1"
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+ assert_equal prism_eval, 1
+ Object.send(:remove_const, constant_name)
+ end
+
+ def test_ConstantPathWriteNode
+ assert_prism_eval("Prism::CPWN = 1")
+ assert_prism_eval("::CPWN = 1")
+ end
+
+ def test_ConstantPathAndWriteNode
+ assert_prism_eval("Prism::CPAWN = 1; Prism::CPAWN &&= 2")
+ assert_prism_eval("Prism::CPAWN &&= 1")
+ assert_prism_eval("::CPAWN = 1; ::CPAWN &&= 2")
+ end
+
+ def test_ConstantPathOrWriteNode
+ assert_prism_eval("Prism::CPOrWN = nil; Prism::CPOrWN ||= 1")
+ assert_prism_eval("Prism::CPOrWN ||= 1")
+ assert_prism_eval("::CPOrWN = nil; ::CPOrWN ||= 1")
+ end
+
+ def test_ConstantPathOperatorWriteNode
+ assert_prism_eval("Prism::CPOWN = 0; Prism::CPOWN += 1")
+ assert_prism_eval("::CPOWN = 0; ::CPOWN += 1")
+ end
+
+ def test_GlobalVariableAndWriteNode
+ assert_prism_eval("$pit = 0; $pit &&= 1")
+ end
+
+ def test_GlobalVariableOperatorWriteNode
+ assert_prism_eval("$pit = 0; $pit += 1")
+ end
+
+ def test_GlobalVariableOrWriteNode
+ assert_prism_eval("$pit ||= 1")
+ end
+
+ def test_GlobalVariableWriteNode
+ assert_prism_eval("$pit = 1")
+ end
+
+ def test_InstanceVariableAndWriteNode
+ assert_prism_eval("@pit = 0; @pit &&= 1")
+ end
+
+ def test_InstanceVariableOperatorWriteNode
+ assert_prism_eval("@pit = 0; @pit += 1")
+ end
+
+ def test_InstanceVariableOrWriteNode
+ assert_prism_eval("@pit ||= 1")
+ end
+
+ def test_InstanceVariableWriteNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; end")
+ end
+
+ def test_LocalVariableAndWriteNode
+ assert_prism_eval("pit = 0; pit &&= 1")
+ end
+
+ def test_LocalVariableOperatorWriteNode
+ assert_prism_eval("pit = 0; pit += 1")
+ end
+
+ def test_LocalVariableOrWriteNode
+ assert_prism_eval("pit ||= 1")
+ end
+
+ def test_LocalVariableWriteNode
+ assert_prism_eval("pit = 1")
+ assert_prism_eval(<<-CODE)
+ a = 0
+ [].each do
+ a = 1
+ end
+ a
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ a = 1
+ d = 1
+ [1].each do
+ b = 2
+ a = 2
+ [2].each do
+ c = 3
+ d = 4
+ a = 2
+ end
+ end
+ [a, d]
+ CODE
+ end
+
+ def test_MatchWriteNode
+ assert_prism_eval("/(?<foo>bar)(?<baz>bar>)/ =~ 'barbar'")
+ assert_prism_eval("/(?<foo>bar)/ =~ 'barbar'")
+ end
+
+ ############################################################################
+ # Multi-writes #
+ ############################################################################
+
+ def test_ClassVariableTargetNode
+ assert_prism_eval("class Prism::TestCompilePrism; @@pit, @@pit1 = 1; end")
+ end
+
+ def test_ConstantTargetNode
+ # We don't call assert_prism_eval directly in this case because we
+ # don't want to assign the constant multiple times if we run
+ # with `--repeat-count`
+ # Instead, we eval manually here, and remove the constant to
+ constant_names = ["YCT", "YCT2"]
+ source = "#{constant_names.join(",")} = 1"
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+ assert_equal prism_eval, 1
+ constant_names.map { |name|
+ Object.send(:remove_const, name)
+ }
+ end
+
+ def test_ConstantPathTargetNode
+ assert_separately([], <<~'RUBY')
+ verbose = $VERBOSE
+ # Create some temporary nested constants
+ Object.send(:const_set, "MyFoo", Object)
+ Object.const_get("MyFoo").send(:const_set, "Bar", Object)
+
+ constant_names = ["MyBar", "MyFoo::Bar", "MyFoo::Bar::Baz"]
+ source = "#{constant_names.join(",")} = Object"
+ iseq = RubyVM::InstructionSequence.compile_prism(source)
+ $VERBOSE = nil
+ prism_eval = iseq.eval
+ $VERBOSE = verbose
+ assert_equal prism_eval, Object
+ RUBY
+ end
+
+ def test_GlobalVariableTargetNode
+ assert_prism_eval("$pit, $pit1 = 1")
+ end
+
+ def test_InstanceVariableTargetNode
+ assert_prism_eval("class Prism::TestCompilePrism; @pit, @pit1 = 1; end")
+ end
+
+ def test_LocalVariableTargetNode
+ assert_prism_eval("pit, pit1 = 1")
+ assert_prism_eval(<<-CODE)
+ a = 1
+ [1].each do
+ c = 2
+ a, b = 2
+ end
+ a
+ CODE
+ end
+
+ def test_MultiTargetNode
+ assert_prism_eval("a, (b, c) = [1, 2, 3]")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; a")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; b")
+ assert_prism_eval("a, (b, c) = [1, 2, 3]; c")
+ assert_prism_eval("a, (b, c) = [1, [2, 3]]; c")
+ assert_prism_eval("a, (b, *c) = [1, [2, 3]]; c")
+ assert_prism_eval("a, (b, *c) = 1, [2, 3]; c")
+ assert_prism_eval("a, (b, *) = 1, [2, 3]; b")
+ assert_prism_eval("a, (b, *c, d) = 1, [2, 3, 4]; [a, b, c, d]")
+ assert_prism_eval("(a, (b, c, d, e), f, g), h = [1, [2, 3]], 4, 5, [6, 7]; c")
+ end
+
+ def test_MultiWriteNode
+ assert_prism_eval("foo, bar = [1, 2]")
+ assert_prism_eval("foo, = [1, 2]")
+ assert_prism_eval("foo, *, bar = [1, 2]")
+ assert_prism_eval("foo, bar = 1, 2")
+ assert_prism_eval("foo, *, bar = 1, 2")
+ assert_prism_eval("foo, *, bar = 1, 2, 3, 4")
+ assert_prism_eval("a, b, *, d = 1, 2, 3, 4")
+ assert_prism_eval("a, b, *, d = 1, 2")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; a")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; b")
+ assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; c")
+ assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; a")
+ assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; c")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; b")
+ assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; d")
+ assert_prism_eval("((a, *, b), *, (c, *, (d, *, e, f, g))), *, ((h, i, *, j), *, (k, l, m, *, n, o, p), q, r) = 1; a")
+ assert_prism_eval("*a = 1; a")
+ assert_prism_eval("_, {}[:foo] = 1")
+ assert_prism_eval("_, {}[:foo], _ = 1")
+ assert_prism_eval("_, {}[:foo], _ = 1")
+ assert_prism_eval("_,{}[:foo], _, {}[:bar] = 1")
+ assert_prism_eval("* = :foo")
+ assert_prism_eval("* = *[]")
+ assert_prism_eval("a, * = :foo")
+
+
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ foo.bar, foo.baz = 1
+ CODE
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ _, foo.bar, foo.baz = 1
+ CODE
+ assert_prism_eval(<<~CODE)
+ class Foo
+ def bar=(x); end
+ def baz=(c); end
+ end
+ foo = Foo.new
+ _, foo.bar, _, foo.baz = 1
+ CODE
+
+ # Test nested writes with method calls
+ assert_prism_eval(<<~RUBY)
+ class Foo
+ attr_accessor :bar
+ end
+
+ a = Foo.new
+
+ (a.bar, a.bar), b = [1], 2
+ RUBY
+ assert_prism_eval(<<~RUBY)
+ h = {}
+ (h[:foo], h[:bar]), a = [1], 2
+ RUBY
+ end
+
+ ############################################################################
+ # String-likes #
+ ############################################################################
+
+ def test_EmbeddedStatementsNode
+ assert_prism_eval('"foo #{to_s} baz"')
+ end
+
+ def test_EmbeddedVariableNode
+ assert_prism_eval('class Prism::TestCompilePrism; @pit = 1; "#@pit"; end')
+ assert_prism_eval('class Prism::TestCompilePrism; @@pit = 1; "#@@pit"; end')
+ assert_prism_eval('$pit = 1; "#$pit"')
+ end
+
+ def test_InterpolatedMatchLastLineNode
+ assert_prism_eval('$pit = ".oo"; if /"#{$pit}"/mix; end')
+ end
+
+ def test_InterpolatedRegularExpressionNode
+ assert_prism_eval('$pit = 1; /1 #$pit 1/')
+ assert_prism_eval('$pit = 1; /#$pit/i')
+ assert_prism_eval('/1 #{1 + 2} 1/')
+ assert_prism_eval('/1 #{"2"} #{1 + 2} 1/')
+ end
+
+ def test_InterpolatedStringNode
+ assert_prism_eval('$pit = 1; "1 #$pit 1"')
+ assert_prism_eval('"1 #{1 + 2} 1"')
+ assert_prism_eval('"Prism" "::" "TestCompilePrism"')
+ assert_prism_eval(<<-'RUBY')
+ # frozen_string_literal: true
+
+ !("a""b""#{1}").frozen?
+ RUBY
+ assert_prism_eval(<<-'RUBY')
+ # frozen_string_literal: true
+
+ !("a""#{1}""b").frozen?
+ RUBY
+
+ # Test encoding of interpolated strings
+ assert_prism_eval(<<~'RUBY')
+ "#{"foo"}s".encoding
+ RUBY
+ assert_prism_eval(<<~'RUBY')
+ a = "foo"
+ b = "#{a}" << "Bar"
+ [a, b, b.encoding]
+ RUBY
+ end
+
+ def test_concatenated_StringNode
+ assert_prism_eval('("a""b").frozen?')
+ assert_prism_eval(<<-CODE)
+ # frozen_string_literal: true
+
+ ("a""b").frozen?
+ CODE
+ end
+
+ def test_InterpolatedSymbolNode
+ assert_prism_eval('$pit = 1; :"1 #$pit 1"')
+ assert_prism_eval(':"1 #{1 + 2} 1"')
+ end
+
+ def test_InterpolatedXStringNode
+ assert_prism_eval(<<~RUBY)
+ def self.`(command) = command * 2
+ `echo \#{1}`
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ def self.`(command) = command * 2
+ `echo \#{"100"}`
+ RUBY
+ end
+
+ def test_MatchLastLineNode
+ assert_prism_eval("if /foo/; end")
+ assert_prism_eval("if /foo/i; end")
+ assert_prism_eval("if /foo/x; end")
+ assert_prism_eval("if /foo/m; end")
+ assert_prism_eval("if /foo/im; end")
+ assert_prism_eval("if /foo/mx; end")
+ assert_prism_eval("if /foo/xi; end")
+ assert_prism_eval("if /foo/ixm; end")
+ end
+
+ def test_RegularExpressionNode
+ assert_prism_eval('/pit/')
+ assert_prism_eval('/pit/i')
+ assert_prism_eval('/pit/x')
+ assert_prism_eval('/pit/m')
+ assert_prism_eval('/pit/im')
+ assert_prism_eval('/pit/mx')
+ assert_prism_eval('/pit/xi')
+ assert_prism_eval('/pit/ixm')
+
+ assert_prism_eval('/pit/u')
+ assert_prism_eval('/pit/e')
+ assert_prism_eval('/pit/s')
+ assert_prism_eval('/pit/n')
+
+ assert_prism_eval('/pit/me')
+ assert_prism_eval('/pit/ne')
+
+ assert_prism_eval('2.times.map { /#{1}/o }')
+ assert_prism_eval('2.times.map { foo = 1; /#{foo}/o }')
+ end
+
+ def test_StringNode
+ assert_prism_eval('"pit"')
+ assert_prism_eval('"a".frozen?')
+ end
+
+ def test_StringNode_frozen_string_literal_true
+ [
+ # Test that string literal is frozen
+ <<~RUBY,
+ # frozen_string_literal: true
+ "a".frozen?
+ RUBY
+ # Test that two string literals with the same contents are the same string
+ <<~RUBY,
+ # frozen_string_literal: true
+ "hello".equal?("hello")
+ RUBY
+ ].each do |src|
+ assert_prism_eval(src, raw: true)
+ end
+ end
+
+ def test_StringNode_frozen_string_literal_false
+ [
+ # Test that string literal is frozen
+ <<~RUBY,
+ # frozen_string_literal: false
+ !"a".frozen?
+ RUBY
+ # Test that two string literals with the same contents are the same string
+ <<~RUBY,
+ # frozen_string_literal: false
+ !"hello".equal?("hello")
+ RUBY
+ ].each do |src|
+ assert_prism_eval(src, raw: true)
+ end
+ end
+
+ def test_StringNode_frozen_string_literal_default
+ # Test that string literal is chilled
+ assert_prism_eval('"a".frozen?')
+
+ # Test that two identical chilled string literals aren't the same object
+ assert_prism_eval('!"hello".equal?("hello")')
+ end
+
+ def test_SymbolNode
+ assert_prism_eval(":pit")
+
+ # Test UTF-8 symbol in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ :"\u{e9}"
+ RUBY
+
+ # Test ASCII-8BIT symbol in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ :"\xff"
+ RUBY
+
+ # Test US-ASCII symbol in a ASCII-8BIT file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: ascii-8bit -*-
+ :a
+ RUBY
+ end
+
+ def test_XStringNode
+ assert_prism_eval(<<~RUBY)
+ class Prism::TestCompilePrism
+ def self.`(command) = command * 2
+ `pit`
+ end
+ RUBY
+ end
+
+ ############################################################################
+ # Structures #
+ ############################################################################
+
+ def test_ArrayNode
+ assert_prism_eval("[]")
+ assert_prism_eval("[1, 2, 3]")
+ assert_prism_eval("%i[foo bar baz]")
+ assert_prism_eval("%w[foo bar baz]")
+ assert_prism_eval("[*1..2]")
+ assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8]")
+ assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[0, *1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[-1, true, 0, *1..2, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]")
+ assert_prism_eval("[[*1..2], 3, *4..5]")
+
+ # Test keyword splat inside of array
+ assert_prism_eval("[**{x: 'hello'}]")
+
+ # Test UTF-8 string array literal in a US-ASCII file
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ # frozen_string_literal: true
+ %W"\u{1f44b} \u{1f409}"
+ RUBY
+ end
+
+ def test_AssocNode
+ assert_prism_eval("{ foo: :bar }")
+ end
+
+ def test_AssocSplatNode
+ assert_prism_eval("foo = { a: 1 }; { **foo }")
+ assert_prism_eval("foo = { a: 1 }; bar = foo; { **foo, b: 2, **bar, c: 3 }")
+ assert_prism_eval("foo = { a: 1 }; { b: 2, **foo, c: 3}")
+
+ # Test anonymous AssocSplatNode
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.bar(**) = Hash(**)
+
+ o.bar(hello: "world")
+ RUBY
+
+ # Test that AssocSplatNode is evaluated before BlockArgumentNode using
+ # the splatkw instruction
+ assert_prism_eval(<<~RUBY)
+ o = Struct.new(:ary) do
+ def to_hash
+ ary << :to_hash
+ {}
+ end
+
+ def to_proc
+ ary << :to_proc
+ -> {}
+ end
+
+ def t(...); end
+ end.new
+ o.ary = []
+
+ o.t(**o, &o)
+ o.ary
+ RUBY
+ end
+
+ def test_HashNode
+ assert_prism_eval("{}")
+ assert_prism_eval("{ a: :a }")
+ assert_prism_eval("{ a: :a, b: :b }")
+ assert_prism_eval("a = 1; { a: a }")
+ assert_prism_eval("a = 1; { a: }")
+ assert_prism_eval("{ to_s: }")
+ assert_prism_eval("{ Prism: }")
+ assert_prism_eval("[ Prism: [:b, :c]]")
+ assert_prism_eval("{ [] => 1}")
+ end
+
+ def test_ImplicitNode
+ assert_prism_eval("{ to_s: }")
+ end
+
+ def test_RangeNode
+ assert_prism_eval("1..2")
+ assert_prism_eval("1...2")
+ assert_prism_eval("..2")
+ assert_prism_eval("...2")
+ assert_prism_eval("1..")
+ assert_prism_eval("1...")
+ end
+
+ def test_SplatNode
+ assert_prism_eval("*b = []; b")
+ assert_prism_eval("*b = [1, 2, 3]; b")
+ assert_prism_eval("a, *b = [1, 2, 3]; a")
+ assert_prism_eval("a, *b = [1, 2, 3]; b")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; a")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; b")
+ assert_prism_eval("a, *b, c = [1, 2, 3]; c")
+ assert_prism_eval("*b, c = [1, 2, 3]; b")
+ assert_prism_eval("*b, c = [1, 2, 3]; c")
+ assert_prism_eval("a, *, c = [1, 2, 3]; a")
+ assert_prism_eval("a, *, c = [1, 2, 3]; c")
+
+ # Test anonymous splat node
+ assert_prism_eval(<<~RUBY)
+ def self.bar(*) = Array(*)
+
+ bar([1, 2, 3])
+ RUBY
+ end
+
+ ############################################################################
+ # Jumps #
+ ############################################################################
+
+ def test_AndNode
+ assert_prism_eval("true && 1")
+ assert_prism_eval("false && 1")
+ end
+
+ def test_CaseNode
+ assert_prism_eval("case :a; when :a; 1; else; 2; end")
+ assert_prism_eval("case :a; when :b; 1; else; 2; end")
+ assert_prism_eval("case :a; when :a; 1; else; 2; end")
+ assert_prism_eval("case :a; when :a; end")
+ assert_prism_eval("case :a; when :b, :c; end")
+ assert_prism_eval("case; when :a; end")
+ assert_prism_eval("case; when :a, :b; 1; else; 2 end")
+ assert_prism_eval("case :a; when :b; else; end")
+ assert_prism_eval("b = 1; case :a; when b; else; end")
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_case_node
+ case :a
+ when :b
+ else
+ return 2
+ end
+ 1
+ end
+ prism_test_case_node
+ CODE
+
+ # Test splat in when
+ assert_prism_eval(<<~RUBY)
+ ary = [1, 2]
+ case 1
+ when *ary
+ :ok
+ else
+ :ng
+ end
+ RUBY
+
+ # Test splat in when
+ assert_prism_eval(<<~RUBY)
+ ary = [1, 2]
+ case 1
+ when :foo, *ary
+ :ok
+ else
+ :ng
+ end
+ RUBY
+
+ # Test case without predicate
+ assert_prism_eval(<<~RUBY)
+ case
+ when 1 == 2
+ :ng
+ else
+ :ok
+ end
+ RUBY
+
+ # test splat with no predicate
+ assert_prism_eval(<<~RUBY)
+ case
+ when *[true]
+ :ok
+ else
+ :ng
+ end
+ RUBY
+ end
+
+ def test_ElseNode
+ assert_prism_eval("if false; 0; else; 1; end")
+ assert_prism_eval("if true; 0; else; 1; end")
+ assert_prism_eval("true ? 1 : 0")
+ assert_prism_eval("false ? 0 : 1")
+ end
+
+ def test_FlipFlopNode
+ assert_prism_eval("not (1 == 1) .. (2 == 2)")
+ assert_prism_eval("not (1 == 1) ... (2 == 2)")
+ end
+
+ def test_IfNode
+ assert_prism_eval("if true; 1; end")
+ assert_prism_eval("1 if true")
+ assert_prism_eval('a = b = 1; if a..b; end')
+ assert_prism_eval('if "a".."b"; end')
+ assert_prism_eval('if "a"..; end')
+ assert_prism_eval('if .."b"; end')
+ assert_prism_eval('if ..1; end')
+ assert_prism_eval('if 1..; end')
+ assert_prism_eval('if 1..2; end')
+ assert_prism_eval('if true or true; end');
+ end
+
+ def test_OrNode
+ assert_prism_eval("true || 1")
+ assert_prism_eval("false || 1")
+ end
+
+ def test_UnlessNode
+ assert_prism_eval("1 unless true")
+ assert_prism_eval("1 unless false")
+ assert_prism_eval("unless true; 1; end")
+ assert_prism_eval("unless false; 1; end")
+ end
+
+ def test_UntilNode
+ assert_prism_eval("a = 0; until a == 1; a = a + 1; end")
+
+ # Test UntilNode in rescue
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ o.instance_variable_set(:@ret, [])
+ def o.foo = @ret << @ret.length
+ def o.bar = @ret.length > 3
+ begin
+ raise
+ rescue
+ o.foo until o.bar
+ end
+ o.instance_variable_get(:@ret)
+ RUBY
+ end
+
+ def test_WhileNode
+ assert_prism_eval("a = 0; while a != 1; a = a + 1; end")
+
+ # Test WhileNode in rescue
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ o.instance_variable_set(:@ret, [])
+ def o.foo = @ret << @ret.length
+ def o.bar = @ret.length < 3
+ begin
+ raise
+ rescue
+ o.foo while o.bar
+ end
+ o.instance_variable_get(:@ret)
+ RUBY
+ end
+
+ def test_ForNode
+ assert_prism_eval("for i in [1,2] do; i; end")
+ assert_prism_eval("for @i in [1,2] do; @i; end")
+ assert_prism_eval("for $i in [1,2] do; $i; end")
+
+ assert_prism_eval("for foo, in [1,2,3] do end")
+
+ assert_prism_eval("for i, j in {a: 'b'} do; i; j; end")
+ end
+
+ ############################################################################
+ # Throws #
+ ############################################################################
+
+ def test_BeginNode
+ assert_prism_eval("begin; 1; end")
+ assert_prism_eval("begin; end; 1")
+ end
+
+ def test_BreakNode
+ assert_prism_eval("while true; break; end")
+ assert_prism_eval("while true; break 1; end")
+ assert_prism_eval("while true; break 1, 2; end")
+
+ assert_prism_eval("[].each { break }")
+ assert_prism_eval("[true].map { break }")
+ end
+
+ def test_ensure_in_methods
+ assert_prism_eval(<<-CODE)
+def self.m
+ a = []
+ensure
+ a << 5
+ return a
+end
+m
+ CODE
+ end
+
+ def test_break_runs_ensure
+ assert_prism_eval(<<-CODE)
+a = []
+while true
+ begin
+ break
+ ensure
+ a << 1
+ end
+end
+a
+ CODE
+ end
+
+ def test_EnsureNode
+ assert_prism_eval("begin; 1; ensure; 2; end")
+ assert_prism_eval("begin; 1; begin; 3; ensure; 4; end; ensure; 2; end")
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ ensure
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ ensure
+ a = 3
+ end
+ a
+ CODE
+
+ # Test that ensure block only evaluated once
+ assert_prism_eval(<<~RUBY)
+ res = []
+ begin
+ begin
+ raise
+ ensure
+ res << $!.to_s
+ end
+ rescue
+ res
+ end
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ a = 2
+ ensure
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ b = 2
+ ensure
+ c = 3
+ end
+ a + b + c
+ CODE
+ assert_prism_eval(<<~CODE)
+ foo = 1
+ begin
+ ensure
+ begin
+ ensure
+ foo.nil?
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ def test
+ ensure
+ {}.each do |key, value|
+ {}[key] = value
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ def test
+ a = 1
+ ensure
+ {}.each do |key, value|
+ {}[key] = a
+ end
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_ensure_node
+ begin
+ ensure
+ end
+ return
+ end
+ prism_test_ensure_node
+ CODE
+
+ # Test empty ensure block
+ assert_prism_eval(<<~RUBY)
+ res = []
+
+ begin
+ begin
+ raise
+ ensure
+ end
+ rescue
+ res << "rescue"
+ end
+
+ res
+ RUBY
+ end
+
+ def test_NextNode
+ assert_prism_eval("2.times do |i|; next if i == 1; end")
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ i = 0
+ while i < 5
+ i += 1
+ next if i == 3
+ res << i
+ end
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ (1..5).each do |i|
+ next if i.even?
+ res << i
+ end
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ (1..5).map do |i|
+ next i, :even if i.even?
+ i
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ res = []
+ i = 0
+ begin
+ i += 1
+ next if i == 3
+ res << i
+ end while i < 5
+ res
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ while false
+ begin
+ ensure
+ end
+ next
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ [].each do
+ begin
+ rescue
+ next
+ end
+ end
+ CODE
+ end
+
+ def test_RedoNode
+ assert_prism_eval(<<-CODE)
+ counter = 0
+
+ 5.times do |i|
+ counter += 1
+ if i == 2 && counter < 3
+ redo
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ for i in 1..5
+ if i == 3
+ i = 0
+ redo
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ i = 0
+ begin
+ i += 1
+ redo if i == 3
+ end while i < 5
+ CODE
+ end
+
+ def test_RescueNode
+ assert_prism_eval("begin; 1; rescue; 2; end")
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ rescue SyntaxError
+ 2
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ raise 'boom'
+ rescue StandardError
+ 2
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ a = 1
+ rescue StandardError => e
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ raise StandardError
+ rescue StandardError => e
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ begin
+ 1
+ rescue StandardError => e
+ e
+ rescue SyntaxError => f
+ f
+ else
+ 4
+ end
+ CODE
+ assert_prism_eval(<<-CODE)
+ begin
+ a = 2
+ rescue
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ a = 2
+ rescue
+ a = 3
+ end
+ a
+ CODE
+ assert_prism_eval(<<-CODE)
+ a = 1
+ begin
+ b = 2
+ raise "bang"
+ rescue
+ c = 3
+ end
+ a + b + c
+ CODE
+ assert_prism_eval("begin; rescue; end")
+
+ assert_prism_eval(<<~CODE)
+ begin
+ rescue
+ args.each do |key, value|
+ tmp[key] = 1
+ end
+ end
+ CODE
+ assert_prism_eval(<<~CODE)
+ 10.times do
+ begin
+ rescue
+ break
+ end
+ end
+ CODE
+
+ # Test RescueNode with ElseNode
+ assert_prism_eval(<<~RUBY)
+ calls = []
+ begin
+ begin
+ rescue RuntimeError
+ calls << 1
+ else
+ calls << 2
+ raise RuntimeError
+ end
+ rescue RuntimeError
+ end
+
+ calls
+ RUBY
+ end
+
+ def test_RescueModifierNode
+ assert_prism_eval("1.nil? rescue false")
+ assert_prism_eval("1.nil? rescue 1")
+ assert_prism_eval("raise 'bang' rescue nil")
+ assert_prism_eval("raise 'bang' rescue a = 1; a.nil?")
+ assert_prism_eval("a = 0 rescue (a += 1 && retry if a <= 1)")
+ end
+
+ def test_RetryNode
+ assert_prism_eval(<<~CODE)
+ a = 1
+ begin
+ a
+ raise "boom"
+ rescue
+ a += 1
+ retry unless a > 1
+ ensure
+ a = 3
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ begin
+ rescue
+ foo = 2
+ retry
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE)
+ begin
+ a = 2
+ rescue
+ retry
+ end
+ CODE
+ end
+
+ def test_ReturnNode
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ return 1
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ return 1, 2
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ [1].each do |e|
+ return true
+ end
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node
+ [1].map do |i|
+ return i if i == 1
+ 2
+ end
+ end
+ prism_test_return_node
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_return_node(*args, **kwargs)
+ return *args, *args, **kwargs
+ end
+ prism_test_return_node(1, foo: 0)
+ CODE
+ end
+
+ ############################################################################
+ # Scopes/statements #
+ ############################################################################
+
+ def test_BlockNode
+ assert_prism_eval("[1, 2, 3].each { |num| num }")
+
+ assert_prism_eval("[].tap { _1 }")
+
+ assert_prism_eval("[].each { |a,| }")
+ assert_prism_eval("[[1, 2, 3]].map { |_, _, a| a }")
+ assert_prism_eval("[[1, 2, 3]].map { |_, a| a }")
+
+ assert_prism_eval("[[]].map { |a| a }")
+ assert_prism_eval("[[]].map { |a| a }")
+ assert_prism_eval("[[]].map { |a, &block| a }")
+ assert_prism_eval("[[]].map { |a, &block| a }")
+ assert_prism_eval("[{}].map { |a,| }")
+ assert_prism_eval("[[]].map { |a,b=1| a }")
+ assert_prism_eval("[{}].map { |a,| }")
+ assert_prism_eval("[{}].map { |a| a }")
+
+ # Test blocks with MultiTargetNode
+ assert_prism_eval("[[1, 2]].each.map { |(a), (b)| [a, b] }")
+ end
+
+ def test_ClassNode
+ assert_prism_eval("class PrismClassA; end")
+ assert_prism_eval("class PrismClassA; end; class PrismClassB < PrismClassA; end")
+ assert_prism_eval("class PrismClassA; end; class PrismClassA::PrismClassC; end")
+ assert_prism_eval(<<-HERE
+ class PrismClassA; end
+ class PrismClassA::PrismClassC; end
+ class PrismClassB; end
+ class PrismClassB::PrismClassD < PrismClassA::PrismClassC; end
+ HERE
+ )
+ end
+
+ # Many of these tests are versions of tests at bootstraptest/test_method.rb
+ def test_DefNode
+ assert_prism_eval("def prism_test_def_node; end")
+ assert_prism_eval("a = Object.new; def a.prism_singleton; :ok; end; a.prism_singleton")
+ assert_prism_eval("def self.prism_test_def_node() 1 end; prism_test_def_node()")
+ assert_prism_eval("def self.prism_test_def_node(a,b) [a, b] end; prism_test_def_node(1,2)")
+ assert_prism_eval("def self.prism_test_def_node(a,x=7,y=1) x end; prism_test_def_node(7,1)")
+ assert_prism_eval("def self.prism_test_def_node(a = 1); x = 2; end; prism_test_def_node")
+
+ # rest argument
+ assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node().inspect")
+ assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node(1).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,*a) a end; prism_test_def_node(7,7,1,2).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y=7,*a) a end; prism_test_def_node(7).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,z=7,*a) a end; prism_test_def_node(7,7).inspect")
+ assert_prism_eval("def self.prism_test_def_node(x,y,z=7,zz=7,*a) a end; prism_test_def_node(7,7,7).inspect")
+
+ # keyword arguments
+ assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(a: 2)")
+ assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(b: 3)")
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(x = 1, y, a: 8, b: 2, c: 4)
+ a + b + c + x + y
+ end
+ prism_test_def_node(10, b: 3)
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a: [])
+ a
+ end
+ prism_test_def_node
+ CODE
+
+ # block arguments
+ assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node{}.class")
+ assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node().inspect")
+ assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) b end; prism_test_def_node(7,1).inspect")
+ assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) c end; prism_test_def_node(7,7,1).inspect")
+
+ # splat
+ assert_prism_eval("def self.prism_test_def_node(a) a end; prism_test_def_node(*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,a) a end; prism_test_def_node(7,*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,y,a) a end; prism_test_def_node(7,7,*[1])")
+ assert_prism_eval("def self.prism_test_def_node(x,y,a,b,c) a end; prism_test_def_node(7,7,*[1,7,7])")
+
+ # recursive call
+ assert_prism_eval("def self.prism_test_def_node(n) n == 0 ? 1 : prism_test_def_node(n-1) end; prism_test_def_node(5)")
+
+ # instance method
+ assert_prism_eval("class PrismTestDefNode; def prism_test_def_node() 1 end end; PrismTestDefNode.new.prism_test_def_node")
+ assert_prism_eval("class PrismTestDefNode; def prism_test_def_node(*a) a end end; PrismTestDefNode.new.prism_test_def_node(1).inspect")
+
+ # block argument
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(&block) prism_test_def_node2(&block) end
+ def self.prism_test_def_node2() yield 1 end
+ prism_test_def_node2 {|a| a }
+ CODE
+
+ # multi argument
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, d))
+ [a, b, c, d]
+ end
+ prism_test_def_node("a", ["b", "c", "d"])
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, c, *))
+ [a, b, c]
+ end
+ prism_test_def_node("a", ["b", "c"])
+ CODE
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (*, b, c))
+ [a, b, c]
+ end
+ prism_test_def_node("a", ["b", "c"])
+ CODE
+
+ # recursive multis
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, (d, *e, f)))
+ [a, b, c, d, d, e, f]
+ end
+ prism_test_def_node("a", ["b", "c", ["d", "e", "f"]])
+ CODE
+
+ # Many arguments
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_def_node(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m)
+ [a, b, c, d, e, f, g, h, i, j, k, l, m]
+ end
+ prism_test_def_node(
+ "a",
+ ["b", "c1", "c2", "d"],
+ "e",
+ "f1", "f2",
+ "g",
+ ["h", "i1", "i2", "j"],
+ k: "k",
+ l: "l",
+ m1: "m1",
+ m2: "m2"
+ )
+ CODE
+ end
+
+ def test_pow_parameters
+ assert_prism_eval("def self.m(a, **); end; method(:m).parameters")
+ end
+
+ def test_star_parameters
+ assert_prism_eval("def self.m(a, *, b); end; method(:m).parameters")
+ end
+
+ def test_repeated_block_params
+ assert_prism_eval("def self.x(&blk); blk; end; x { |_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters")
+ end
+
+ def test_repeated_proc_params
+ assert_prism_eval("proc {|_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters")
+ end
+
+ def test_forward_parameters_block
+ assert_prism_eval("def self.m(&); end; method(:m).parameters")
+ end
+
+ def test_forward_parameters
+ assert_prism_eval("def self.m(...); end; method(:m).parameters")
+ end
+
+ def test_repeated_block_underscore
+ assert_prism_eval("def self.m(_, **_, &_); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_kw_rest_underscore
+ assert_prism_eval("def self.m(_, **_); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_required_keyword_underscore
+ assert_prism_eval("def self.m(_, _, *_, _, _:); _; end; method(:m).parameters")
+ assert_prism_eval("def self.m(_, _, *_, _, _:, _: 2); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_required_post_underscore
+ assert_prism_eval("def self.m(_, _, *_, _); _; end; method(:m).parameters")
+ end
+
+ def test_repeated_splat_underscore
+ assert_prism_eval("def self.m(_, _, _ = 1, _ = 2, *_); end; method(:m).parameters")
+ end
+
+ def test_repeated_optional_underscore
+ assert_prism_eval("def self.m(a, _, _, _ = 1, _ = 2, b); end; method(:m).parameters")
+ end
+
+ def test_repeated_required_underscore
+ assert_prism_eval("def self.m(a, _, _, b); end; method(:m).parameters")
+ end
+
+ def test_locals_in_parameters
+ assert_prism_eval("def self.m(a = b = c = 1); [a, b, c]; end; self.m")
+ end
+
+ def test_trailing_comma_on_block
+ assert_prism_eval("def self.m; yield [:ok]; end; m {|v0,| v0 }")
+ end
+
+ def test_complex_default_params
+ assert_prism_eval("def self.foo(a:, b: '2'.to_i); [a, b]; end; foo(a: 1)")
+ assert_prism_eval("def self.foo(a:, b: 2, c: '3'.to_i); [a, b, c]; end; foo(a: 1)")
+ end
+
+ def test_numbered_params
+ assert_prism_eval("[1, 2, 3].then { _3 }")
+ assert_prism_eval("1.then { one = 1; one + _1 }")
+ end
+
+ def test_rescue_with_ensure
+ assert_prism_eval(<<-CODE)
+begin
+ begin
+ raise "a"
+ rescue
+ raise "b"
+ ensure
+ raise "c"
+ end
+rescue => e
+ e.message
+end
+ CODE
+ end
+
+ def test_required_kwarg_ordering
+ assert_prism_eval("def self.foo(a: 1, b:); [a, b]; end; foo(b: 2)")
+ end
+
+ def test_trailing_keyword_method_params
+ # foo(1, b: 2, c: 3) # argc -> 3
+ assert_prism_eval("def self.foo(a, b:, c:); [a, b, c]; end; foo(1, b: 2, c: 3)")
+ end
+
+ def test_keyword_method_params_only
+ # foo(a: 1, b: 2) # argc -> 2
+ assert_prism_eval("def self.foo(a:, b:); [a, b]; end; foo(a: 1, b: 2)")
+ end
+
+ def test_keyword_method_params_with_splat
+ # foo(a: 1, **b) # argc -> 1
+ assert_prism_eval("def self.foo(a:, b:); [a, b]; end; b = { b: 2 }; foo(a: 1, **b)")
+ end
+
+ def test_positional_and_splat_keyword_method_params
+ # foo(a, **b) # argc -> 2
+ assert_prism_eval("def self.foo(a, b); [a, b]; end; b = { b: 2 }; foo(1, **b)")
+ end
+
+ def test_positional_and_splat_method_params
+ # foo(a, *b, c, *d, e) # argc -> 2
+ assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, 5)")
+ end
+
+ def test_positional_with_splat_and_splat_keyword_method_params
+ # foo(a, *b, c, *d, **e) # argc -> 3
+ assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; e = { e: 5 }; foo(1, *b, 3, *d, **e)")
+ end
+
+ def test_positional_with_splat_and_keyword_method_params
+ # foo(a, *b, c, *d, e:) # argc -> 3
+ assert_prism_eval("def self.foo(a, b, c, d, e:); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, e: 5)")
+ end
+
+ def test_leading_splat_and_keyword_method_params
+ # foo(*a, b:) # argc -> 2
+ assert_prism_eval("def self.foo(a, b:); [a, b]; end; a = [1]; foo(*a, b: 2)")
+ end
+
+ def test_repeated_method_params
+ assert_prism_eval("def self.foo(_a, _a); _a; end; foo(1, 2)")
+ end
+
+ def test_splat_params_with_no_lefties
+ assert_prism_eval("def self.foo(v, (*)); v; end; foo(1, [2, 3, 4])")
+ end
+
+ def test_method_parameters
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(a, b=1, *c, d:, e: 2, **f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(d:, e: 2, **f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(**f, &g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_method_parameters(&g)
+ end
+
+ method(:prism_test_method_parameters).parameters
+ CODE
+ end
+
+ def test_LambdaNode
+ assert_prism_eval("-> { to_s }.call")
+ end
+
+ def test_LambdaNode_with_multiline_args
+ assert_prism_eval(<<-CODE)
+ -> (a,
+ b) {
+ a + b
+ }.call(1, 2)
+ CODE
+ end
+
+ def test_ModuleNode
+ assert_prism_eval("module M; end")
+ assert_prism_eval("module M::N; end")
+ assert_prism_eval("module ::O; end")
+ end
+
+ def test_ParenthesesNode
+ assert_prism_eval("()")
+ assert_prism_eval("(1)")
+ end
+
+ def test_PreExecutionNode
+ assert_prism_eval("BEGIN { a = 1 }; 2", raw: true)
+ assert_prism_eval("b = 2; BEGIN { a = 1 }; a + b", raw: true)
+ end
+
+ def test_PostExecutionNode
+ assert_prism_eval("END { 1 }")
+ assert_prism_eval("END { @b }; @b = 1")
+ assert_prism_eval("END { @b; 0 }; @b = 1")
+ assert_prism_eval("foo = 1; END { foo.nil? }")
+ assert_prism_eval("foo = 1; END { END { foo.nil? }}")
+ end
+
+ def test_ProgramNode
+ assert_prism_eval("")
+ assert_prism_eval("1")
+ end
+
+ def test_SingletonClassNode
+ assert_prism_eval("class << self; end")
+ end
+
+ def test_StatementsNode
+ assert_prism_eval("1")
+ end
+
+ def test_YieldNode
+ assert_prism_eval("def prism_test_yield_node; yield; end")
+ assert_prism_eval("def prism_test_yield_node; yield 1, 2; end")
+ assert_prism_eval("def prism_test_yield_node; yield **kw if condition; end")
+
+ # Test case where there's a call directly after the yield call
+ assert_prism_eval("def prism_test_yield_node; yield; 1; end")
+ assert_prism_eval("def prism_test_yield_node; yield 1, 2; 1; end")
+ end
+
+ ############################################################################
+ # Calls / arguments #
+ ############################################################################
+
+ def test_ArgumentsNode
+ # assert_prism_eval("[].push 1")
+ end
+
+ def test_BlockArgumentNode
+ assert_prism_eval("1.then(&:to_s)")
+
+ # Test anonymous block forwarding
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.foo(&) = yield
+ def o.bar(&) = foo(&)
+
+ o.bar { :ok }
+ RUBY
+ end
+
+ def test_BlockLocalVariableNode
+ assert_prism_eval(<<-CODE
+ pm_var = "outer scope variable"
+
+ 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var }
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ pm_var = "outer scope variable"
+
+ 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var }
+ pm_var
+ CODE
+ )
+ end
+
+ def test_CallNode
+ assert_prism_eval("to_s")
+
+ # with arguments
+ assert_prism_eval("eval '1'")
+
+ # with arguments and popped
+ assert_prism_eval("eval '1'; 1")
+
+ # With different types of calling arguments
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_call_node_double_splat(**); end
+ prism_test_call_node_double_splat(b: 1, **{})
+ CODE
+ assert_prism_eval(<<-CODE)
+ prism_test_call_node_double_splat(:b => 1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_test_call_node_splat(*); end
+ prism_test_call_node_splat(*[], 1)
+ CODE
+
+ assert_prism_eval("prism_test_call_node_splat(*[], 1, 2)")
+
+ assert_prism_eval(<<~RUBY)
+ def self.prism_test_call_node_splat_and_double_splat(a, b, **opts); end
+ prism_test_call_node_splat_and_double_splat(*[1], 2, **{})
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ class Foo
+ def []=(a, b)
+ 1234
+ end
+ end
+
+ def self.foo(i, j)
+ tbl = Foo.new
+ tbl[i] = j
+ end
+ foo(1, 2)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ class Foo
+ def i=(a)
+ 1234
+ end
+ end
+
+ def self.foo(j)
+ tbl = Foo.new
+ tbl.i = j
+ end
+ foo(1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ foo = Object.new
+ def foo.[]=(k,v); 42; end
+ foo.[]=(1,2)
+ CODE
+
+ # With splat inside of []=
+ assert_prism_eval(<<~RUBY)
+ obj = Object.new
+ def obj.[]=(a, b); 10; end
+ obj[*[1]] = 3
+ RUBY
+
+ assert_prism_eval(<<-CODE)
+ def self.prism_opt_var_trail_hash(a = nil, *b, c, **d); end
+ prism_opt_var_trail_hash("a")
+ prism_opt_var_trail_hash("a", c: 1)
+ prism_opt_var_trail_hash("a", "b")
+ prism_opt_var_trail_hash("a", "b", "c")
+ prism_opt_var_trail_hash("a", "b", "c", c: 1)
+ prism_opt_var_trail_hash("a", "b", "c", "c" => 0, c: 1)
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.foo(*args, **kwargs) = [args, kwargs]
+
+ [
+ foo(2 => 3),
+ foo([] => 42),
+ foo(a: 42, b: 61),
+ foo(1, 2, 3, a: 42, "b" => 61),
+ foo(:a => 42, :b => 61),
+ ]
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ class PrivateMethod
+ def initialize
+ self.instance_var
+ end
+ private
+ attr_accessor :instance_var
+ end
+ pm = PrivateMethod.new
+ pm.send(:instance_var)
+ CODE
+
+ # Testing safe navigation operator
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ if [][0]&.first
+ 1
+ end
+ end
+ test_prism_call_node
+ CODE
+
+ # Test opt_str_freeze instruction when calling #freeze on a string literal
+ assert_prism_eval(<<~RUBY)
+ "foo".freeze.equal?("foo".freeze)
+ RUBY
+ # Test encoding in opt_str_freeze
+ assert_prism_eval(<<~'RUBY', raw: true)
+ # -*- coding: us-ascii -*-
+ "\xff".freeze.encoding
+ RUBY
+
+ # Test opt_aref_with instruction when calling [] with a string
+ assert_prism_eval(<<~RUBY)
+ ObjectSpace.count_objects
+
+ h = {"abc" => 1}
+ before = ObjectSpace.count_objects[:T_STRING]
+ 5.times{ h["abc"] }
+ after = ObjectSpace.count_objects[:T_STRING]
+
+ before == after
+ RUBY
+
+ # Test opt_aset_with instruction when calling []= with a string key
+ assert_prism_eval(<<~RUBY)
+ ObjectSpace.count_objects
+
+ h = {"abc" => 1}
+ before = ObjectSpace.count_objects[:T_STRING]
+ 5.times{ h["abc"] = 2}
+ after = ObjectSpace.count_objects[:T_STRING]
+
+ before == after
+ RUBY
+ end
+
+ def test_CallAndWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_and_write_node; end;
+ PrismTestSubclass.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def PrismTestSubclass.test_call_and_write_node
+ "str"
+ end
+ def PrismTestSubclass.test_call_and_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_and_write_node; end;
+ self.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_and_write_node
+ "str"
+ end
+ def self.test_call_and_write_node=(val)
+ val
+ end
+ self.test_call_and_write_node &&= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node; end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node &&= 1
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ 2
+ end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node &&= 1
+ CODE
+ end
+
+ def test_CallOrWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_or_write_node; end;
+ def PrismTestSubclass.test_call_or_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def PrismTestSubclass.test_call_or_write_node
+ "str"
+ end
+ PrismTestSubclass.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_or_write_node; end;
+ def self.test_call_or_write_node=(val)
+ val
+ end
+ self.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE
+ def self.test_call_or_write_node
+ "str"
+ end
+ self.test_call_or_write_node ||= 1
+ CODE
+ )
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node
+ 2
+ end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node ||= 1
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def self.test_prism_call_node; end
+ def self.test_prism_call_node=(val)
+ val
+ end
+ self&.test_prism_call_node ||= 1
+ CODE
+ end
+
+ def test_CallOperatorWriteNode
+ assert_prism_eval(<<-CODE
+ class PrismTestSubclass; end
+ def PrismTestSubclass.test_call_operator_write_node
+ 2
+ end
+ def PrismTestSubclass.test_call_operator_write_node=(val)
+ val
+ end
+ PrismTestSubclass.test_call_operator_write_node += 1
+ CODE
+ )
+ end
+
+ def test_ForwardingArgumentsNode
+ assert_prism_eval(<<-CODE)
+ def prism_test_forwarding_arguments_node(...); end;
+ def prism_test_forwarding_arguments_node1(...)
+ prism_test_forwarding_arguments_node(...)
+ end
+ CODE
+
+ assert_prism_eval(<<-CODE)
+ def prism_test_forwarding_arguments_node(...); end;
+ def prism_test_forwarding_arguments_node1(a, ...)
+ prism_test_forwarding_arguments_node(1,2, 3, ...)
+ end
+ CODE
+
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.bar(a, b, c) = [a, b, c]
+ def o.foo(...) = 1.times { bar(...) }
+
+ o.foo(1, 2, 3)
+ RUBY
+ end
+
+ def test_ForwardingSuperNode
+ assert_prism_eval("class Forwarding; def to_s; super; end; end")
+ assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end")
+ assert_prism_eval(<<-CODE)
+ class A
+ def initialize(a, b)
+ end
+ end
+
+ class B < A
+ attr_reader :res
+ def initialize(a, b, *)
+ super
+ @res = [a, b]
+ end
+ end
+
+ B.new(1, 2).res
+ CODE
+ end
+
+ def test_KeywordHashNode
+ assert_prism_eval("[a: [:b, :c]]")
+ end
+
+ def test_SuperNode
+ assert_prism_eval("def to_s; super 1; end")
+ assert_prism_eval("def to_s; super(); end")
+ assert_prism_eval("def to_s; super('a', :b, [1,2,3]); end")
+ assert_prism_eval("def to_s; super(1, 2, 3, &:foo); end")
+ end
+
+ ############################################################################
+ # Methods / parameters #
+ ############################################################################
+
+ def test_AliasGlobalVariableNode
+ assert_prism_eval("alias $prism_foo $prism_bar")
+ end
+
+ def test_AliasMethodNode
+ assert_prism_eval("alias :prism_a :to_s")
+ end
+
+ def test_BlockParameterNode
+ assert_prism_eval("def prism_test_block_parameter_node(&bar) end")
+ assert_prism_eval("->(b, c=1, *d, e, &f){}")
+
+ # Test BlockParameterNode with no name
+ assert_prism_eval("->(&){}")
+ assert_prism_eval("def prism_test_block_parameter_node(&); end")
+ end
+
+ def test_BlockParametersNode
+ assert_prism_eval("Object.tap { || }")
+ assert_prism_eval("[1].map { |num| num }")
+ assert_prism_eval("[1].map { |a; b| b = 2; a + b}")
+
+ # Test block parameters with multiple _
+ assert_prism_eval(<<~RUBY)
+ [[1, 2, 3, 4, 5, 6]].map { |(_, _, _, _, _, _)| _ }
+ RUBY
+ end
+
+ def test_FowardingParameterNode
+ assert_prism_eval("def prism_test_forwarding_parameter_node(...); end")
+ end
+
+ def test_KeywordRestParameterNode
+ assert_prism_eval("def prism_test_keyword_rest_parameter_node(a, **b); end")
+ assert_prism_eval("Object.tap { |**| }")
+
+ # Test that KeywordRestParameterNode creates a copy
+ assert_prism_eval(<<~RUBY)
+ hash = {}
+ o = Object.new
+ def o.foo(**a) = a[:foo] = 1
+
+ o.foo(**hash)
+ hash
+ RUBY
+ end
+
+ def test_NoKeywordsParameterNode
+ assert_prism_eval("def prism_test_no_keywords(**nil); end")
+ assert_prism_eval("def prism_test_no_keywords(a, b = 2, **nil); end")
+ end
+
+ def test_OptionalParameterNode
+ assert_prism_eval("def prism_test_optional_param_node(bar = nil); end")
+ end
+
+ def test_OptionalKeywordParameterNode
+ assert_prism_eval("def prism_test_optional_keyword_param_node(bar: nil); end")
+
+ # Test with optional argument and method call in OptionalKeywordParameterNode
+ assert_prism_eval(<<~RUBY)
+ o = Object.new
+ def o.foo = 1
+ def o.bar(a = nil, b: foo) = b
+ o.bar
+ RUBY
+ end
+
+ def test_ParametersNode
+ assert_prism_eval("def prism_test_parameters_node(bar, baz); end")
+ assert_prism_eval("def prism_test_parameters_node(a, b = 2); end")
+ end
+
+ def test_RequiredParameterNode
+ assert_prism_eval("def prism_test_required_param_node(bar); end")
+ assert_prism_eval("def prism_test_required_param_node(foo, bar); end")
+ end
+
+ def test_RequiredKeywordParameterNode
+ assert_prism_eval("def prism_test_required_param_node(bar:); end")
+ assert_prism_eval("def prism_test_required_param_node(foo:, bar:); end")
+ assert_prism_eval("-> a, b = 1, c:, d:, &e { a }")
+ end
+
+ def test_RestParameterNode
+ assert_prism_eval("def prism_test_rest_parameter_node(*a); end")
+ end
+
+ def test_UndefNode
+ assert_prism_eval("def prism_undef_node_1; end; undef prism_undef_node_1")
+ assert_prism_eval(<<-HERE
+ def prism_undef_node_2
+ end
+ def prism_undef_node_3
+ end
+ undef prism_undef_node_2, prism_undef_node_3
+ HERE
+ )
+ assert_prism_eval(<<-HERE
+ def prism_undef_node_4
+ end
+ undef :'prism_undef_node_#{4}'
+ HERE
+ )
+ end
+
+ ############################################################################
+ # Pattern matching #
+ ############################################################################
+
+ def test_AlternationPatternNode
+ assert_prism_eval("1 in 1 | 2")
+ assert_prism_eval("1 in 2 | 1")
+ assert_prism_eval("1 in 2 | 3 | 4 | 1")
+ assert_prism_eval("1 in 2 | 3")
+ end
+
+ def test_ArrayPatternNode
+ assert_prism_eval("[] => []")
+
+ ["in", "=>"].each do |operator|
+ ["", "Array"].each do |constant|
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *foo]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *foo]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 2, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 1, 2, 3]")
+
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 2, 3]")
+ assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 1, 2, 3]")
+ end
+ end
+
+ assert_prism_eval("begin; Object.new => [1, 2, 3]; rescue NoMatchingPatternError; true; end")
+ assert_prism_eval("begin; [1, 2, 3] => Object[1, 2, 3]; rescue NoMatchingPatternError; true; end")
+ end
+
+ def test_CapturePatternNode
+ assert_prism_eval("[1] => [Integer => foo]")
+ end
+
+ def test_CaseMatchNode
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3]
+ in [1, 2, 3]
+ 4
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case { a: 5, b: 6 }
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3, 4]
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ else
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3, 4]
+ in [1, 2, 3]
+ 4
+ in { a: 5, b: 6 }
+ 7
+ else
+ 8
+ end
+ RUBY
+
+ assert_prism_eval(<<~RUBY)
+ case [1, 2, 3]
+ in [1, 2, 3] unless to_s
+ in [1, 2, 3] if to_s.nil?
+ in [1, 2, 3]
+ true
+ end
+ RUBY
+ end
+
+ def test_FindPatternNode
+ ["in", "=>"].each do |operator|
+ ["", "Array"].each do |constant|
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, 5, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *foo]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *foo]")
+
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *bar]")
+ assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *bar]")
+ end
+ end
+
+ assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [*, [*, [*, [*, [*]]]]]")
+ assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [1, [2, [3, [4, [5]]]]]")
+
+ assert_prism_eval("begin; Object.new => [*, 2, *]; rescue NoMatchingPatternError; true; end")
+ assert_prism_eval("begin; [1, 2, 3] => Object[*, 2, *]; rescue NoMatchingPatternError; true; end")
+ end
+
+ def test_HashPatternNode
+ assert_prism_eval("{} => {}")
+
+ [["{ ", " }"], ["Hash[", "]"]].each do |(prefix, suffix)|
+ assert_prism_eval("{} => #{prefix} **nil #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3 #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3 #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, ** #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, ** #{suffix}")
+
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, **foo #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **foo #{suffix}")
+
+ assert_prism_eval("{ a: 1 } => #{prefix} a: 1, **nil #{suffix}")
+ assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **nil #{suffix}")
+ end
+
+ assert_prism_eval("{ a: { b: { c: 1 } } } => { a: { b: { c: 1 } } }")
+ end
+
+ def test_MatchPredicateNode
+ assert_prism_eval("1 in 1")
+ assert_prism_eval("1.0 in 1.0")
+ assert_prism_eval("1i in 1i")
+ assert_prism_eval("1r in 1r")
+
+ assert_prism_eval("\"foo\" in \"foo\"")
+ assert_prism_eval("\"foo \#{1}\" in \"foo \#{1}\"")
+
+ assert_prism_eval("false in false")
+ assert_prism_eval("nil in nil")
+ assert_prism_eval("self in self")
+ assert_prism_eval("true in true")
+
+ assert_prism_eval("5 in 0..10")
+ assert_prism_eval("5 in 0...10")
+
+ assert_prism_eval("[\"5\"] in %w[5]")
+
+ assert_prism_eval("Prism in Prism")
+ assert_prism_eval("Prism in ::Prism")
+
+ assert_prism_eval(":prism in :prism")
+ assert_prism_eval("%s[prism\#{1}] in %s[prism\#{1}]")
+ assert_prism_eval("\"foo\" in /.../")
+ assert_prism_eval("\"foo1\" in /...\#{1}/")
+ assert_prism_eval("4 in ->(v) { v.even? }")
+
+ assert_prism_eval("5 in foo")
+
+ assert_prism_eval("1 in 2")
+ end
+
+ def test_MatchRequiredNode
+ assert_prism_eval("1 => 1")
+ assert_prism_eval("1.0 => 1.0")
+ assert_prism_eval("1i => 1i")
+ assert_prism_eval("1r => 1r")
+
+ assert_prism_eval("\"foo\" => \"foo\"")
+ assert_prism_eval("\"foo \#{1}\" => \"foo \#{1}\"")
+
+ assert_prism_eval("false => false")
+ assert_prism_eval("nil => nil")
+ assert_prism_eval("true => true")
+
+ assert_prism_eval("5 => 0..10")
+ assert_prism_eval("5 => 0...10")
+
+ assert_prism_eval("[\"5\"] => %w[5]")
+
+ assert_prism_eval(":prism => :prism")
+ assert_prism_eval("%s[prism\#{1}] => %s[prism\#{1}]")
+ assert_prism_eval("\"foo\" => /.../")
+ assert_prism_eval("\"foo1\" => /...\#{1}/")
+ assert_prism_eval("4 => ->(v) { v.even? }")
+
+ assert_prism_eval("5 => foo")
+ end
+
+ def test_PinnedExpressionNode
+ assert_prism_eval("4 in ^(4)")
+ end
+
+ def test_PinnedVariableNode
+ assert_prism_eval("module Prism; @@prism = 1; 1 in ^@@prism; end")
+ assert_prism_eval("module Prism; @prism = 1; 1 in ^@prism; end")
+ assert_prism_eval("$prism = 1; 1 in ^$prism")
+ assert_prism_eval("prism = 1; 1 in ^prism")
+ end
+
+ ############################################################################
+ # Miscellaneous #
+ ############################################################################
+
+ def test_eval
+ assert_prism_eval("eval('1 + 1')", raw: true)
+ assert_prism_eval("a = 1; eval('a + 1')", raw: true)
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_splat(**bar)
+ eval("bar")
+ end
+ prism_eval_splat(bar: 10)
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_keywords(baz:)
+ eval("baz")
+ end
+ prism_eval_keywords(baz: 10)
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ [1].each do |a|
+ [2].each do |b|
+ c = 3
+ eval("a + b + c")
+ end
+ end
+ CODE
+
+ assert_prism_eval(<<~CODE, raw: true)
+ def prism_eval_binding(b)
+ eval("bar", b)
+ end
+
+ bar = :ok
+ prism_eval_binding(binding)
+ CODE
+ end
+
+ def test_ScopeNode
+ assert_separately(%w[], <<~'RUBY')
+ def compare_eval(source)
+ ruby_eval = RubyVM::InstructionSequence.compile("module A; " + source + "; end").eval
+ prism_eval = RubyVM::InstructionSequence.compile_prism("module B; " + source + "; end").eval
+
+ assert_equal ruby_eval, prism_eval
+ end
+
+ def assert_prism_eval(source)
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+
+ begin
+ compare_eval(source)
+
+ # Test "popped" functionality
+ compare_eval("#{source}; 1")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+
+ assert_prism_eval("a = 1; 1.times do; { a: }; end")
+ assert_prism_eval("a = 1; def foo(a); a; end")
+ RUBY
+ end
+
+ ############################################################################
+ # Errors #
+ ############################################################################
+
+ def test_MissingNode
+ # TODO
+ end
+
+ ############################################################################
+ # Encoding #
+ ############################################################################
+
+ def test_encoding
+ assert_prism_eval('"però"')
+ assert_prism_eval(":però")
+ end
+
+ def test_parse_file
+ assert_nothing_raised do
+ RubyVM::InstructionSequence.compile_file_prism(__FILE__)
+ end
+
+ error = assert_raise Errno::ENOENT do
+ RubyVM::InstructionSequence.compile_file_prism("idontexist.rb")
+ end
+
+ assert_equal "No such file or directory - idontexist.rb", error.message
+
+ assert_raise TypeError do
+ RubyVM::InstructionSequence.compile_file_prism(nil)
+ end
+ end
+
+ private
+
+ def compare_eval(source, raw:, location:)
+ source = raw ? source : "class Prism::TestCompilePrism\n#{source}\nend"
+
+ ruby_eval = RubyVM::InstructionSequence.compile(source).eval
+ prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval
+
+ if ruby_eval.is_a? Proc
+ assert_equal ruby_eval.class, prism_eval.class, "@#{location.path}:#{location.lineno}"
+ else
+ assert_equal ruby_eval, prism_eval, "@#{location.path}:#{location.lineno}"
+ end
+ end
+
+ def assert_prism_eval(source, raw: false)
+ location = caller_locations(1, 1).first
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+
+ begin
+ compare_eval(source, raw:, location:)
+
+ # Test "popped" functionality
+ compare_eval("#{source}; 1", raw:, location:)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
+end
diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb
index a3a7546575..c0cfb73235 100644
--- a/test/ruby/test_complex.rb
+++ b/test/ruby/test_complex.rb
@@ -221,10 +221,16 @@ class Complex_Test < Test::Unit::TestCase
assert_equal([1,2], Complex.polar(1,2).polar)
assert_equal(Complex.polar(1.0, Math::PI * 2 / 3), Complex.polar(1, Math::PI * 2 / 3))
- assert_in_out_err([], <<-'end;', ['OK'], [])
- Complex.polar(1, Complex(1, 0))
- puts :OK
- end;
+ one = 1+0i
+ c = Complex.polar(0, one)
+ assert_equal(0, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one, 0)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
end
def test_uplus
@@ -520,6 +526,71 @@ class Complex_Test < Test::Unit::TestCase
r = c ** Rational(-2,3)
assert_in_delta(0.432, r.real, 0.001)
assert_in_delta(-0.393, r.imag, 0.001)
+ end
+
+ def test_expt_for_special_angle
+ c = Complex(1, 0) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(-1, 0) ** 10000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(-1, 0) ** 10000000000000000000000000000001
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000001
+ assert_equal(Complex(0, 1), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000002
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, 1) ** 100000000000000000000000000000003
+ assert_equal(Complex(0, -1), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000000
+ assert_equal(Complex(1, 0), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000001
+ assert_equal(Complex(0, -1), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000002
+ assert_equal(Complex(-1, 0), c)
+
+ c = Complex(0, -1) ** 100000000000000000000000000000003
+ assert_equal(Complex(0, 1), c)
+
+ c = Complex(1, 1) ** 1
+ assert_equal(Complex(1, 1), c)
+
+ c = Complex(1, 1) ** 2
+ assert_equal(Complex(0, 2), c)
+
+ c = Complex(1, 1) ** 3
+ assert_equal(Complex(-2, 2), c)
+
+ c = Complex(1, 1) ** 4
+ assert_equal(Complex(-4, 0), c)
+
+ c = Complex(1, 1) ** 5
+ assert_equal(Complex(-4, -4), c)
+
+ c = Complex(1, 1) ** 6
+ assert_equal(Complex(0, -8), c)
+
+ c = Complex(1, 1) ** 7
+ assert_equal(Complex(8, -8), c)
+
+ c = Complex(-2, -2) ** 3
+ assert_equal(Complex(16, -16), c)
+
+ c = Complex(2, -2) ** 3
+ assert_equal(Complex(-16, -16), c)
+
+ c = Complex(-2, 2) ** 3
+ assert_equal(Complex(16, 16), c)
c = Complex(0.0, -888888888888888.0)**8888
assert_not_predicate(c.real, :nan?)
@@ -567,20 +638,24 @@ class Complex_Test < Test::Unit::TestCase
assert_raise_with_message(TypeError, /C\u{1f5ff}/) { Complex(1).coerce(obj) }
end
- class ObjectX
- def +(x) Rational(1) end
+ class ObjectX < Numeric
+ def initialize(real = true, n = 1) @n = n; @real = real; end
+ def +(x) Rational(@n) end
alias - +
alias * +
alias / +
alias quo +
alias ** +
- def coerce(x) [x, Complex(1)] end
+ def coerce(x) [x, Complex(@n)] end
+ def real?; @real; end
end
def test_coerce2
x = ObjectX.new
- %w(+ - * / quo **).each do |op|
- assert_kind_of(Numeric, Complex(1).__send__(op, x))
+ y = ObjectX.new(false)
+ %w(+ - * / quo ** <=>).each do |op|
+ assert_kind_of(Numeric, Complex(1).__send__(op, x), op)
+ assert_kind_of(Numeric, Complex(1).__send__(op, y), op)
end
end
@@ -843,20 +918,42 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(0), '_5'.to_c)
assert_equal(Complex(5), '5_'.to_c)
assert_equal(Complex(5), '5x'.to_c)
+ assert_equal(Complex(51), '5_1'.to_c)
+ assert_equal(Complex(5), '5__1'.to_c)
assert_equal(Complex(5), '5+_3i'.to_c)
assert_equal(Complex(5), '5+3_i'.to_c)
assert_equal(Complex(5,3), '5+3i_'.to_c)
assert_equal(Complex(5,3), '5+3ix'.to_c)
+ assert_equal(Complex(5,31), '5+3_1i'.to_c)
+ assert_equal(Complex(5), '5+3__1i'.to_c)
+ assert_equal(Complex(51), Complex('5_1'))
+ assert_equal(Complex(5,31), Complex('5+3_1i'))
+ assert_equal(Complex(5,31), Complex('5+3_1I'))
+ assert_equal(Complex(5,31), Complex('5+3_1j'))
+ assert_equal(Complex(5,31), Complex('5+3_1J'))
+ assert_equal(Complex(0,31), Complex('3_1i'))
+ assert_equal(Complex(0,31), Complex('3_1I'))
+ assert_equal(Complex(0,31), Complex('3_1j'))
+ assert_equal(Complex(0,31), Complex('3_1J'))
assert_raise(ArgumentError){ Complex('')}
assert_raise(ArgumentError){ Complex('_')}
assert_raise(ArgumentError){ Complex("\f\n\r\t\v5\0")}
assert_raise(ArgumentError){ Complex('_5')}
assert_raise(ArgumentError){ Complex('5_')}
+ assert_raise(ArgumentError){ Complex('5__1')}
assert_raise(ArgumentError){ Complex('5x')}
assert_raise(ArgumentError){ Complex('5+_3i')}
assert_raise(ArgumentError){ Complex('5+3_i')}
assert_raise(ArgumentError){ Complex('5+3i_')}
assert_raise(ArgumentError){ Complex('5+3ix')}
+ assert_raise(ArgumentError){ Complex('5+3__1i')}
+ assert_raise(ArgumentError){ Complex('5+3__1I')}
+ assert_raise(ArgumentError){ Complex('5+3__1j')}
+ assert_raise(ArgumentError){ Complex('5+3__1J')}
+ assert_raise(ArgumentError){ Complex('3__1i')}
+ assert_raise(ArgumentError){ Complex('3__1I')}
+ assert_raise(ArgumentError){ Complex('3__1j')}
+ assert_raise(ArgumentError){ Complex('3__1J')}
assert_equal(Complex(Rational(1,5)), '1/5'.to_c)
assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c)
@@ -883,31 +980,27 @@ class Complex_Test < Test::Unit::TestCase
}
end
- def test_Complex_without_exception
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex('5x', exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(nil, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(Object.new, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, nil, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, Object.new, exception: false))
- }
+ def assert_complex_with_exception(error, *args, message: "")
+ assert_raise(error, message) do
+ Complex(*args, exception: true)
+ end
+ assert_nothing_raised(error, message) do
+ assert_nil(Complex(*args, exception: false))
+ assert_nil($!)
+ end
+ end
+
+ def test_Complex_with_exception
+ assert_complex_with_exception(ArgumentError, '5x')
+ assert_complex_with_exception(TypeError, nil)
+ assert_complex_with_exception(TypeError, Object.new)
+ assert_complex_with_exception(TypeError, 1, nil)
+ assert_complex_with_exception(TypeError, 1, Object.new)
o = Object.new
def o.to_c; raise; end
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(o, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, o, exception: false))
- }
+ assert_complex_with_exception(RuntimeError, o)
+ assert_complex_with_exception(TypeError, 1, o)
end
def test_respond
@@ -961,6 +1054,29 @@ class Complex_Test < Test::Unit::TestCase
assert_raise(RangeError){Rational(Complex(3,2))}
end
+ def test_to_r_with_float
+ assert_equal(Rational(3), Complex(3, 0.0).to_r)
+ assert_raise(RangeError){Complex(3, 1.0).to_r}
+ end
+
+ def test_to_r_with_numeric_obj
+ c = Class.new(Numeric)
+
+ num = 0
+ c.define_method(:to_s) { num.to_s }
+ c.define_method(:==) { num == it }
+ c.define_method(:<) { num < it }
+
+ o = c.new
+ assert_equal(Rational(3), Complex(3, o).to_r)
+
+ num = 1
+ assert_raise(RangeError){Complex(3, o).to_r}
+
+ c.define_method(:to_r) { 0r }
+ assert_equal(Rational(3), Complex(3, o).to_r)
+ end
+
def test_to_c
c = nil.to_c
assert_equal([0,0], [c.real, c.imag])
@@ -1135,15 +1251,34 @@ class Complex_Test < Test::Unit::TestCase
end
def test_canonicalize_polar
- obj = Class.new(Numeric) do
- def initialize
- @x = 2
+ error = "not a real"
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i, 0)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(0, 1i)
+ end
+ n = Class.new(Numeric) do
+ def initialize(x = 1)
+ @x = x
end
def real?
(@x -= 1) > 0
end
- end.new
- assert_raise(TypeError) do
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj, 0)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
Complex.polar(1, obj)
end
end
diff --git a/test/ruby/test_continuation.rb b/test/ruby/test_continuation.rb
index 8c62d20840..612dbf28c9 100644
--- a/test/ruby/test_continuation.rb
+++ b/test/ruby/test_continuation.rb
@@ -4,6 +4,10 @@ EnvUtil.suppress_warning {require 'continuation'}
require 'fiber'
class TestContinuation < Test::Unit::TestCase
+ def setup
+ omit 'requires callcc support' unless respond_to?(:callcc)
+ end
+
def test_create
assert_equal(:ok, callcc{:ok})
assert_equal(:ok, callcc{|c| c.call :ok})
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
new file mode 100644
index 0000000000..bb38f8ec91
--- /dev/null
+++ b/test/ruby/test_data.rb
@@ -0,0 +1,283 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+require 'test/unit'
+require 'timeout'
+
+class TestData < Test::Unit::TestCase
+ def test_define
+ klass = Data.define(:foo, :bar)
+ assert_kind_of(Class, klass)
+ assert_equal(%i[foo bar], klass.members)
+
+ assert_raise(NoMethodError) { Data.new(:foo) }
+ assert_raise(TypeError) { Data.define(0) }
+
+ # Because some code is shared with Struct, check we don't share unnecessary functionality
+ assert_raise(TypeError) { Data.define(:foo, keyword_init: true) }
+
+ assert_not_respond_to(Data.define, :define, "Cannot define from defined Data class")
+ end
+
+ def test_define_edge_cases
+ # non-ascii
+ klass = Data.define(:"r\u{e9}sum\u{e9}")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"r\u{e9}sum\u{e9}"))
+
+ # junk string
+ klass = Data.define(:"a\000")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"a\000"))
+
+ # special characters in attribute names
+ klass = Data.define(:a, :b?)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b?)
+
+ klass = Data.define(:a, :b!)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b!)
+
+ assert_raise(ArgumentError) { Data.define(:x=) }
+ assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) }
+ end
+
+ def test_define_with_block
+ klass = Data.define(:a, :b) do
+ def c
+ a + b
+ end
+ end
+
+ assert_equal(3, klass.new(1, 2).c)
+ end
+
+ def test_initialize
+ klass = Data.define(:foo, :bar)
+
+ # Regular
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(1, 2))
+ assert_predicate(test, :frozen?)
+
+ # Keywords
+ test_kw = klass.new(foo: 1, bar: 2)
+ assert_equal(1, test_kw.foo)
+ assert_equal(2, test_kw.bar)
+ assert_equal(test_kw, klass.new(foo: 1, bar: 2))
+ assert_equal(test_kw, test)
+
+ # Wrong protocol
+ assert_raise(ArgumentError) { klass.new(1) }
+ assert_raise(ArgumentError) { klass.new(1, 2, 3) }
+ assert_raise(ArgumentError) { klass.new(foo: 1) }
+ assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) }
+ # Could be converted to foo: 1, bar: 2, but too smart is confusing
+ assert_raise(ArgumentError) { klass.new(1, bar: 2) }
+ end
+
+ def test_initialize_redefine
+ klass = Data.define(:foo, :bar) do
+ attr_reader :passed
+
+ def initialize(*args, **kwargs)
+ @passed = [args, kwargs]
+ super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args
+ end
+ end
+
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed)
+
+ # Positional arguments are converted to keyword ones
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed)
+
+ # Missing arguments can be fixed in initialize
+ assert_equal([[], {foo: 1}], klass.new(foo: 1).passed)
+ assert_equal([[], {foo: 42}], klass.new(42).passed)
+
+ # Extra keyword arguments can be dropped in initialize
+ assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed)
+ end
+
+ def test_instance_behavior
+ klass = Data.define(:foo, :bar)
+
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(%i[foo bar], test.members)
+ assert_equal(1, test.public_send(:foo))
+ assert_equal(0, test.method(:foo).arity)
+ assert_equal([], test.method(:foo).parameters)
+
+ assert_equal({foo: 1, bar: 2}, test.to_h)
+ assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] })
+
+ assert_equal([1, 2], test.deconstruct)
+ assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo]))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz]))
+ assert_equal({}, test.deconstruct_keys(%i[foo bar baz]))
+ assert_raise(TypeError) { test.deconstruct_keys(0) }
+
+ assert_kind_of(Integer, test.hash)
+ end
+
+ def test_hash
+ measure = Data.define(:amount, :unit)
+
+ assert_equal(measure[1, 'km'].hash, measure[1, 'km'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[10, 'km'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[1, 'm'].hash)
+ assert_not_equal(measure[1, 'km'].hash, measure[1.0, 'km'].hash)
+
+ # Structurally similar data class, but shouldn't be considered
+ # the same hash key
+ measurement = Data.define(:amount, :unit)
+
+ assert_not_equal(measure[1, 'km'].hash, measurement[1, 'km'].hash)
+ end
+
+ def test_inspect
+ klass = Data.define(:a)
+ o = klass.new(1)
+ assert_equal("#<data a=1>", o.inspect)
+
+ Object.const_set(:Foo, klass)
+ assert_equal("#<data Foo a=1>", o.inspect)
+ Object.instance_eval { remove_const(:Foo) }
+
+ klass = Data.define(:@a)
+ o = klass.new(1)
+ assert_equal("#<data :@a=1>", o.inspect)
+ end
+
+ def test_equal
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_equal(o1, o2)
+ assert_not_equal(o1, o3)
+ end
+
+ def test_eql
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_operator(o1, :eql?, o2)
+ assert_not_operator(o1, :eql?, o3)
+ end
+
+ def test_with
+ klass = Data.define(:foo, :bar)
+ source = klass.new(foo: 1, bar: 2)
+
+ # Simple
+ test = source.with
+ assert_equal(source.object_id, test.object_id)
+
+ # Changes
+ test = source.with(foo: 10)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 2))
+
+ test = source.with(foo: 10, bar: 20)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Keyword splat
+ changes = { foo: 10, bar: 20 }
+ test = source.with(**changes)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Wrong protocol
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(10)
+ end
+ assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do
+ source.with(foo: 1, bar: 2, baz: 3, quux: 4)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(1, bar: 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do
+ source.with(1, 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with({ bar: 2 })
+ end
+ end
+
+ def test_with_initialize
+ oddclass = Data.define(:odd) do
+ def initialize(odd:)
+ raise ArgumentError, "Not odd" unless odd.odd?
+ super(odd: odd)
+ end
+ end
+ assert_raise_with_message(ArgumentError, "Not odd") {
+ oddclass.new(odd: 0)
+ }
+ odd = oddclass.new(odd: 1)
+ assert_raise_with_message(ArgumentError, "Not odd") {
+ odd.with(odd: 2)
+ }
+ end
+
+ def test_memberless
+ klass = Data.define
+
+ test = klass.new
+
+ assert_equal(klass.new, test)
+ assert_not_equal(Data.define.new, test)
+
+ assert_equal('#<data >', test.inspect)
+ assert_equal([], test.members)
+ assert_equal({}, test.to_h)
+ end
+
+ def test_dup
+ klass = Data.define(:foo, :bar)
+ test = klass.new(foo: 1, bar: 2)
+ assert_equal(klass.new(foo: 1, bar: 2), test.dup)
+ assert_predicate(test.dup, :frozen?)
+ end
+
+ Klass = Data.define(:foo, :bar)
+
+ def test_marshal
+ test = Klass.new(foo: 1, bar: 2)
+ loaded = Marshal.load(Marshal.dump(test))
+ assert_equal(test, loaded)
+ assert_not_same(test, loaded)
+ assert_predicate(loaded, :frozen?)
+ end
+end
diff --git a/test/ruby/test_default_gems.rb b/test/ruby/test_default_gems.rb
index 467e5ecf83..b82e304cbd 100644
--- a/test/ruby/test_default_gems.rb
+++ b/test/ruby/test_default_gems.rb
@@ -2,15 +2,28 @@
require 'rubygems'
class TestDefaultGems < Test::Unit::TestCase
+ def self.load(file)
+ code = File.read(file, mode: "r:UTF-8:-", &:read)
+
+ # These regex patterns are from load_gemspec method of rbinstall.rb.
+ # - `git ls-files` is useless under ruby's repository
+ # - `2>/dev/null` works only on Unix-like platforms
+ code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split\([^\)]*\)/m, '[]')
+ code.gsub!(/IO\.popen\(.*git.*?\)/, '[] || itself')
+
+ eval(code, binding, file)
+ end
def test_validate_gemspec
- omit "git not found" unless system("git", "rev-parse", %i[out err]=>IO::NULL)
srcdir = File.expand_path('../../..', __FILE__)
- Dir.glob("#{srcdir}/{lib,ext}/**/*.gemspec").map do |src|
- assert_nothing_raised do
- raise("invalid spec in #{src}") unless Gem::Specification.load(src)
+ specs = 0
+ Dir.chdir(srcdir) do
+ all_assertions_foreach(nil, *Dir["{lib,ext}/**/*.gemspec"]) do |src|
+ specs += 1
+ assert_kind_of(Gem::Specification, self.class.load(src), "invalid spec in #{src}")
end
end
+ assert_operator specs, :>, 0, "gemspecs not found"
end
end
diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb
index 3324a09afe..0505bdada6 100644
--- a/test/ruby/test_defined.rb
+++ b/test/ruby/test_defined.rb
@@ -127,6 +127,18 @@ class TestDefined < Test::Unit::TestCase
assert_equal nil, defined?($2)
end
+ def test_defined_assignment
+ assert_equal("assignment", defined?(a = 1))
+ assert_equal("assignment", defined?(a += 1))
+ assert_equal("assignment", defined?(a &&= 1))
+ assert_equal("assignment", eval('defined?(A = 1)'))
+ assert_equal("assignment", eval('defined?(A += 1)'))
+ assert_equal("assignment", eval('defined?(A &&= 1)'))
+ assert_equal("assignment", eval('defined?(A::B = 1)'))
+ assert_equal("assignment", eval('defined?(A::B += 1)'))
+ assert_equal("assignment", eval('defined?(A::B &&= 1)'))
+ end
+
def test_defined_literal
assert_equal("nil", defined?(nil))
assert_equal("true", defined?(true))
@@ -303,6 +315,20 @@ class TestDefined < Test::Unit::TestCase
assert_equal("super", o.x, bug8367)
end
+ def test_super_in_basic_object
+ BasicObject.class_eval do
+ def a
+ defined?(super)
+ end
+ end
+
+ assert_nil(a)
+ ensure
+ BasicObject.class_eval do
+ undef_method :a if defined?(a)
+ end
+ end
+
def test_super_toplevel
assert_separately([], "assert_nil(defined?(super))")
end
diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb
index 514c6e5921..2cc1c3ef4a 100644
--- a/test/ruby/test_dir.rb
+++ b/test/ruby/test_dir.rb
@@ -19,11 +19,21 @@ class TestDir < Test::Unit::TestCase
@dirs << File.join(i, "")
end
end
+ @envs = nil
end
def teardown
$VERBOSE = @verbose
FileUtils.remove_entry_secure @root if File.directory?(@root)
+ ENV.update(@envs) if @envs
+ end
+
+ def setup_envs(envs = %w"HOME LOGDIR")
+ @envs ||= {}
+ envs.each do |e, v|
+ @envs[e] = ENV.delete(e)
+ ENV[e] = v if v
+ end
end
def test_seek
@@ -86,33 +96,31 @@ class TestDir < Test::Unit::TestCase
d.close
end
- def test_chdir
+ def test_class_chdir
pwd = Dir.pwd
- env_home = ENV["HOME"]
- env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ setup_envs
assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) }
assert_raise(ArgumentError) { Dir.chdir }
ENV["HOME"] = pwd
Dir.chdir do
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }
+ conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/
+ assert_warning(conflicting) { Dir.chdir(pwd) }
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) }
+ assert_warning(conflicting) { Dir.chdir(@root) }
assert_equal(@root, Dir.pwd)
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }
+ assert_warning(conflicting) { Dir.chdir(pwd) }
assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) }.join }
assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) { } }.join }
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }
+ assert_warning(conflicting) { Dir.chdir(pwd) }
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) }
+ assert_warning(conflicting) { Dir.chdir(@root) }
assert_equal(@root, Dir.pwd)
- assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }
+ assert_warning(conflicting) { Dir.chdir(pwd) }
Dir.chdir(@root) do
assert_equal(@root, Dir.pwd)
end
@@ -125,8 +133,73 @@ class TestDir < Test::Unit::TestCase
rescue
abort("cannot return the original directory: #{ pwd }")
end
- ENV["HOME"] = env_home
- ENV["LOGDIR"] = env_logdir
+ end
+
+ def test_instance_chdir
+ pwd = Dir.pwd
+ dir = Dir.new(pwd)
+ root_dir = Dir.new(@root)
+ setup_envs
+
+ ENV["HOME"] = pwd
+ ret = root_dir.chdir do |*a|
+ conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/
+
+ assert_empty(a)
+
+ assert_warning(conflicting) { dir.chdir }
+ assert_warning(conflicting) { root_dir.chdir }
+
+ assert_equal(@root, Dir.pwd)
+
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir }.join }
+ assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir{} }.join }
+
+ assert_warning(conflicting) { dir.chdir }
+ assert_equal(pwd, Dir.pwd)
+
+ assert_warning(conflicting) { root_dir.chdir }
+ assert_equal(@root, Dir.pwd)
+
+ assert_warning(conflicting) { dir.chdir }
+
+ root_dir.chdir do
+ assert_equal(@root, Dir.pwd)
+ end
+ assert_equal(pwd, Dir.pwd)
+
+ 42
+ end
+
+ assert_separately(["-", @root], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ root = ARGV.shift
+
+ $dir_warnings = []
+
+ def Warning.warn(message)
+ $dir_warnings << message
+ end
+
+ line2 = line1 = __LINE__; Dir.chdir(root) do
+ line2 = __LINE__; Dir.chdir
+ end
+
+ message = $dir_warnings.shift
+ assert_include(message, "#{__FILE__}:#{line2}:")
+ assert_include(message, "#{__FILE__}:#{line1}:")
+ assert_empty($dir_warnings)
+ end;
+
+ assert_equal(42, ret)
+ ensure
+ begin
+ assert_equal(0, dir.chdir)
+ rescue
+ abort("cannot return the original directory: #{ pwd }")
+ end
+ dir.close
+ root_dir.close
end
def test_chdir_conflict
@@ -259,6 +332,20 @@ class TestDir < Test::Unit::TestCase
end
end
+ def test_glob_recursive_with_brace
+ Dir.chdir(@root) do
+ bug19042 = '[ruby-core:110220] [Bug #19042]'
+ %w"c/dir_a c/dir_b c/dir_b/dir".each do |d|
+ Dir.mkdir(d)
+ end
+ expected = %w"c/dir_a/file c/dir_b/dir/file"
+ expected.each do |f|
+ File.write(f, "")
+ end
+ assert_equal(expected, Dir.glob("**/{dir_a,dir_b/dir}/file"), bug19042)
+ end
+ end
+
def test_glob_order
Dir.chdir(@root) do
assert_equal(["#{@root}/a", "#{@root}/b"], Dir.glob("#{@root}/[ba]"))
@@ -502,13 +589,62 @@ class TestDir < Test::Unit::TestCase
assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819)
assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819)
end
+
+ def test_home_windows
+ setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH])
+
+ ENV['HOME'] = "C:\\ruby\\home"
+ assert_equal("C:/ruby/home", Dir.home)
+
+ ENV['USERPROFILE'] = "C:\\ruby\\userprofile"
+ assert_equal("C:/ruby/home", Dir.home)
+ ENV.delete('HOME')
+ assert_equal("C:/ruby/userprofile", Dir.home)
+
+ ENV['HOMEDRIVE'] = "C:"
+ ENV['HOMEPATH'] = "\\ruby\\homepath"
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ ENV.delete('USERPROFILE')
+ assert_equal("C:/ruby/homepath", Dir.home)
+ end
+
+ def test_home_at_startup_windows
+ env = {'HOME' => "C:\\ruby\\home"}
+ args = [env]
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/home", Dir.home)
+ end;
+
+ env['USERPROFILE'] = "C:\\ruby\\userprofile"
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/home", Dir.home)
+ end;
+
+ env['HOME'] = nil
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ end;
+
+ env['HOMEDRIVE'] = "C:"
+ env['HOMEPATH'] = "\\ruby\\homepath"
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ end;
+
+ env['USERPROFILE'] = nil
+ assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_equal("C:/ruby/homepath", Dir.home)
+ end;
+ end
end
def test_home
- env_home = ENV["HOME"]
- env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ setup_envs
ENV["HOME"] = @nodir
assert_nothing_raised(ArgumentError) do
@@ -526,9 +662,16 @@ class TestDir < Test::Unit::TestCase
%W[no:such:user \u{7559 5b88}:\u{756a}].each do |user|
assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)}
end
- ensure
- ENV["HOME"] = env_home
- ENV["LOGDIR"] = env_logdir
+ end
+
+ if Encoding.find("filesystem") == Encoding::UTF_8
+ # On Windows and macOS, file system encoding is always UTF-8.
+ def test_home_utf8
+ setup_envs
+
+ ENV["HOME"] = "/\u{e4}~\u{1f3e0}"
+ assert_equal("/\u{e4}~\u{1f3e0}", Dir.home)
+ end
end
def test_symlinks_not_resolved
@@ -559,6 +702,21 @@ class TestDir < Test::Unit::TestCase
}
end
+ def test_for_fd
+ if Dir.respond_to? :for_fd
+ begin
+ new_dir = Dir.new('..')
+ for_fd_dir = Dir.for_fd(new_dir.fileno)
+ assert_equal(new_dir.chdir{Dir.pwd}, for_fd_dir.chdir{Dir.pwd})
+ ensure
+ new_dir&.close
+ for_fd_dir&.close
+ end
+ else
+ assert_raise(NotImplementedError) { Dir.for_fd(0) }
+ end
+ end
+
def test_empty?
assert_not_send([Dir, :empty?, @root])
a = File.join(@root, "a")
diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb
index 67bad8a514..cdf8b44ef2 100644
--- a/test/ruby/test_dir_m17n.rb
+++ b/test/ruby/test_dir_m17n.rb
@@ -56,7 +56,7 @@ class TestDir_M17N < Test::Unit::TestCase
return if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs"
with_tmpdir {|d|
assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d)
- filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8
+ filename = "\xff".dup.force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
@@ -64,7 +64,7 @@ class TestDir_M17N < Test::Unit::TestCase
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
- filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8
+ filename = "\xff".dup.force_encoding("UTF-8") # invalid byte sequence as UTF-8
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
@@ -77,7 +77,7 @@ class TestDir_M17N < Test::Unit::TestCase
def test_filename_as_bytes_extutf8
with_tmpdir {|d|
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
- filename = "\xc2\xa1".force_encoding("utf-8")
+ filename = "\xc2\xa1".dup.force_encoding("utf-8")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
@@ -85,9 +85,9 @@ class TestDir_M17N < Test::Unit::TestCase
EOS
assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d)
if /mswin|mingw|darwin/ =~ RUBY_PLATFORM
- filename = "\x8f\xa2\xc2".force_encoding("euc-jp")
+ filename = "\x8f\xa2\xc2".dup.force_encoding("euc-jp")
else
- filename = "\xc2\xa1".force_encoding("euc-jp")
+ filename = "\xc2\xa1".dup.force_encoding("euc-jp")
end
assert_nothing_raised(Errno::ENOENT) do
open(filename) {}
@@ -96,8 +96,8 @@ class TestDir_M17N < Test::Unit::TestCase
# no meaning test on windows
unless /mswin|mingw|darwin/ =~ RUBY_PLATFORM
assert_separately(%W[-EUTF-8], <<-'EOS', :chdir=>d)
- filename1 = "\xc2\xa1".force_encoding("utf-8")
- filename2 = "\xc2\xa1".force_encoding("euc-jp")
+ filename1 = "\xc2\xa1".dup.force_encoding("utf-8")
+ filename2 = "\xc2\xa1".dup.force_encoding("euc-jp")
filename3 = filename1.encode("euc-jp")
filename4 = filename2.encode("utf-8")
assert_file.stat(filename1)
@@ -121,13 +121,13 @@ class TestDir_M17N < Test::Unit::TestCase
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename)
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
assert_nothing_raised(Errno::ENOENT) do
open(filename) {}
end
@@ -149,7 +149,7 @@ class TestDir_M17N < Test::Unit::TestCase
EOS
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP
- filename2 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
+ filename2 = "\xA4\xA2".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
assert_include(ents, filename1)
@@ -158,7 +158,7 @@ class TestDir_M17N < Test::Unit::TestCase
assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d)
filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP
filename2 = "\u3042" # HIRAGANA LETTER A which is representable in EUC-JP
- filename3 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
+ filename3 = "\xA4\xA2".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP
assert_file.stat(filename1)
assert_file.stat(filename2)
assert_file.stat(filename3)
@@ -172,7 +172,7 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
@@ -189,7 +189,7 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
ents = Dir.entries(".")
if /darwin/ =~ RUBY_PLATFORM
@@ -200,7 +200,7 @@ class TestDir_M17N < Test::Unit::TestCase
assert_include(ents, filename)
EOS
assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding('ASCII-8BIT')
+ filename = "\xA4\xA2".dup.force_encoding('ASCII-8BIT')
ents = Dir.entries(".")
unless ents.include?(filename)
case RUBY_PLATFORM
@@ -231,7 +231,7 @@ class TestDir_M17N < Test::Unit::TestCase
return if /cygwin/ =~ RUBY_PLATFORM
with_tmpdir {|d|
assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d)
- filename = "\xA4\xA2".force_encoding("euc-jp")
+ filename = "\xA4\xA2".dup.force_encoding("euc-jp")
File.open(filename, "w") {}
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
@@ -241,7 +241,7 @@ class TestDir_M17N < Test::Unit::TestCase
assert_include(ents, filename)
EOS
assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d)
- filename = "\u3042"
+ filename = "\u3042".dup
opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM
ents = Dir.entries(".", **(opts||{}))
if /darwin/ =~ RUBY_PLATFORM
@@ -318,7 +318,7 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_warning_opendir
with_enc_path do |dir|
- open("#{dir}/x", "w") {}
+ File.binwrite("#{dir}/x", "")
File.chmod(0300, dir)
next if File.readable?(dir)
assert_warning(/#{dir}/) do
@@ -329,7 +329,7 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_warning_match_all
with_enc_path do |dir|
- open("#{dir}/x", "w") {}
+ File.binwrite("#{dir}/x", "")
File.chmod(0000, dir)
next if File.readable?(dir)
assert_warning(/#{dir}/) do
@@ -350,7 +350,7 @@ class TestDir_M17N < Test::Unit::TestCase
end
def test_glob_escape_multibyte
- name = "\x81\\".force_encoding(Encoding::Shift_JIS)
+ name = "\x81\\".dup.force_encoding(Encoding::Shift_JIS)
with_tmpdir do
open(name, "w") {} rescue next
match, = Dir.glob("#{name}*")
@@ -362,9 +362,9 @@ class TestDir_M17N < Test::Unit::TestCase
def test_glob_encoding
with_tmpdir do
list = %W"file_one.ext file_two.ext \u{6587 4ef6}1.txt \u{6587 4ef6}2.txt"
- list.each {|f| open(f, "w") {}}
- a = "file_one*".force_encoding Encoding::IBM437
- b = "file_two*".force_encoding Encoding::EUC_JP
+ list.each {|f| File.binwrite(f, "")}
+ a = "file_one*".dup.force_encoding Encoding::IBM437
+ b = "file_two*".dup.force_encoding Encoding::EUC_JP
assert_equal([a, b].map(&:encoding), Dir[a, b].map(&:encoding))
if Bug::File::Fs.fsname(Dir.pwd) == "apfs"
# High Sierra's APFS cannot use filenames with undefined character
@@ -375,7 +375,7 @@ class TestDir_M17N < Test::Unit::TestCase
Dir.mkdir(dir)
list << dir
bug12081 = '[ruby-core:73868] [Bug #12081]'
- a = "*".force_encoding("us-ascii")
+ a = "*".dup.force_encoding("us-ascii")
result = Dir[a].map {|n|
if n.encoding == Encoding::ASCII_8BIT ||
n.encoding == Encoding::ISO_8859_1 ||
diff --git a/test/ruby/test_dup.rb b/test/ruby/test_dup.rb
new file mode 100644
index 0000000000..75c4fc0339
--- /dev/null
+++ b/test/ruby/test_dup.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestDup < Test::Unit::TestCase
+ module M001; end
+ module M002; end
+ module M003; include M002; end
+ module M002; include M001; end
+ module M003; include M002; end
+
+ def test_dup
+ foo = Object.new
+ def foo.test
+ "test"
+ end
+ bar = foo.dup
+ def bar.test2
+ "test2"
+ end
+
+ assert_equal("test2", bar.test2)
+ assert_raise(NoMethodError) { bar.test }
+ assert_equal("test", foo.test)
+
+ assert_raise(NoMethodError) {foo.test2}
+
+ assert_equal([M003, M002, M001], M003.ancestors)
+ end
+
+ def test_frozen_properties_not_retained_on_dup
+ obj = Object.new.freeze
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ refute_predicate(duped_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_dup
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ duped_obj = obj.dup
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_dup
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ duped_obj = obj.dup
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(duped_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_not_retained_on_dup_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ refute_predicate(duped_obj, :frozen?)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_user_flags
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1, 2, 3].dup
+ assert_equal [], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1,2,3,4,5,6,7][1..-2].dup
+ x.push(1,1,1,1,1)
+ assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Hash
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ h = {}
+ h.default_proc = proc { raise }
+ h = h.dup
+ assert_equal nil, h[:not_exist], '[Bug #14847]'
+ EOS
+ end
+end
diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb
index 1aad0de347..1d0641e918 100644
--- a/test/ruby/test_econv.rb
+++ b/test/ruby/test_econv.rb
@@ -931,7 +931,7 @@ class TestEncodingConverter < Test::Unit::TestCase
def test_default_external
Encoding.list.grep(->(enc) {/\AISO-8859-\d+\z/i =~ enc.name}) do |enc|
- assert_separately(%W[--disable=gems -d - #{enc.name}], <<-EOS, ignore_stderr: true)
+ assert_separately(%W[-d - #{enc.name}], <<-EOS, ignore_stderr: true)
Encoding.default_external = ext = ARGV[0]
Encoding.default_internal = int ='utf-8'
assert_nothing_raised do
diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb
index 4a6dd932ed..f2c609a4cd 100644
--- a/test/ruby/test_encoding.rb
+++ b/test/ruby/test_encoding.rb
@@ -55,39 +55,6 @@ class TestEncoding < Test::Unit::TestCase
assert_raise(TypeError, bug5150) {Encoding.find(1)}
end
- def test_replicate
- assert_separately([], "#{<<~'END;'}")
- assert_instance_of(Encoding, Encoding::UTF_8.replicate("UTF-8-ANOTHER#{Time.now.to_f}"))
- assert_instance_of(Encoding, Encoding::ISO_2022_JP.replicate("ISO-2022-JP-ANOTHER#{Time.now.to_f}"))
- bug3127 = '[ruby-dev:40954]'
- assert_raise(TypeError, bug3127) {Encoding::UTF_8.replicate(0)}
- assert_raise_with_message(ArgumentError, /\bNUL\b/, bug3127) {Encoding::UTF_8.replicate("\0")}
- END;
- end
-
- def test_extra_encoding
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
- begin;
- 200.times {|i|
- Encoding::UTF_8.replicate("dummy#{i}")
- }
- e = Encoding.list.last
- format = "%d".force_encoding(e)
- assert_equal("0", format % 0)
- assert_equal(e, format.dup.encoding)
- assert_equal(e, (format*1).encoding)
-
- assert_equal(e, (("x"*30).force_encoding(e)*1).encoding)
- GC.start
-
- name = "A" * 64
- Encoding.list.each do |enc|
- assert_raise(ArgumentError) {enc.replicate(name)}
- name.succ!
- end
- end;
- end
-
def test_dummy_p
assert_equal(true, Encoding::ISO_2022_JP.dummy?)
assert_equal(false, Encoding::UTF_8.dummy?)
@@ -139,7 +106,7 @@ class TestEncoding < Test::Unit::TestCase
end
def test_errinfo_after_autoload
- assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
bug9038 = '[ruby-core:57949] [Bug #9038]'
begin;
e = assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) {
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb
index cc217b05cc..7503e06272 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -843,6 +843,8 @@ class TestEnumerable < Test::Unit::TestCase
end
def test_callcc
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
assert_raise(RuntimeError) do
c = nil
@obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
@@ -1334,4 +1336,16 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([], @obj.filter_map { nil })
assert_instance_of(Enumerator, @obj.filter_map)
end
+
+ def test_ruby_svar
+ klass = Class.new do
+ include Enumerable
+ def each
+ %w(bar baz).each{|e| yield e}
+ end
+ end
+ svars = []
+ klass.new.grep(/(b.)/) { svars << $1 }
+ assert_equal(["ba", "ba"], svars)
+ end
end
diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb
index c823b79c6d..7599d43463 100644
--- a/test/ruby/test_enumerator.rb
+++ b/test/ruby/test_enumerator.rb
@@ -127,6 +127,17 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal([[1,5],[2,6],[3,7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
end
+ def test_with_index_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ assert_equal([[1, 0], [2, 1], [3, 2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a)
+ assert_equal([[1, 5], [2, 6], [3, 7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a)
+
+ s = 1 << (8 * 1.size - 2)
+ assert_equal([[1, s], [2, s + 1], [3, s + 2]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_a)
+ end
+ end
+
def test_with_index_large_offset
bug8010 = '[ruby-dev:47131] [Bug #8010]'
s = 1 << (8*1.size-2)
@@ -244,6 +255,26 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal(res, exc.result)
end
+ def test_stopiteration_rescue
+ e = [1].each
+ res = e.each {}
+ e.next
+ exc0 = assert_raise(StopIteration) { e.peek }
+ assert_include(exc0.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_nil(exc0.cause)
+ assert_equal(res, exc0.result)
+
+ exc1 = assert_raise(StopIteration) { e.next }
+ assert_include(exc1.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc1.cause)
+ assert_equal(res, exc1.result)
+
+ exc2 = assert_raise(StopIteration) { e.next }
+ assert_include(exc2.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc2.cause)
+ assert_equal(res, exc2.result)
+ end
+
def test_next_values
o = Object.new
def o.each
@@ -832,6 +863,21 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal(33, chain.next)
end
+ def test_lazy_chain_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ ea = (10..).lazy.select(&:even?).take(10)
+ ed = (20..).lazy.select(&:odd?)
+ chain = (ea + ed).select{|x| x % 3 == 0}
+ assert_equal(12, chain.next)
+ assert_equal(18, chain.next)
+ assert_equal(24, chain.next)
+ assert_equal(21, chain.next)
+ assert_equal(27, chain.next)
+ assert_equal(33, chain.next)
+ end
+ end
+
def test_chain_undef_methods
chain = [1].to_enum + [2].to_enum
meths = (chain.methods & [:feed, :next, :next_values, :peek, :peek_values])
@@ -906,4 +952,95 @@ class TestEnumerator < Test::Unit::TestCase
e.chain.each(&->{})
assert_equal(true, e.is_lambda)
end
+
+ def test_product_new
+ # 0-dimensional
+ e = Enumerator::Product.new
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(1, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [nil], heads
+
+ # 1-dimensional
+ e = Enumerator::Product.new(1..3)
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, e.to_a
+
+ # 2-dimensional
+ e = Enumerator::Product.new(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3 * 2, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [1, 1, 2, 2, 3, 3], heads
+
+ # Any enumerable is 0 size
+ assert_equal(0, Enumerator::Product.new([], 1..).size)
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator::Product.new(1..3, foo: 1, bar: 2)
+ }
+ end
+
+ def test_s_product
+ # without a block
+ e = Enumerator.product(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+
+ # with a block
+ elts = []
+ ret = Enumerator.product(1..3) { |x| elts << x }
+ assert_equal(nil, ret)
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, Enumerator.product(1..3).to_a
+
+ # an infinite enumerator and a finite enumerable
+ e = Enumerator.product(1.., 'a'..'c')
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(nil, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ assert_equal(0, Enumerator.product([], 1..).size)
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator.product(1..3, foo: 1, bar: 2)
+ }
+ end
+
+ def test_freeze
+ e = 3.times.freeze
+ assert_raise(FrozenError) { e.next }
+ assert_raise(FrozenError) { e.next_values }
+ assert_raise(FrozenError) { e.peek }
+ assert_raise(FrozenError) { e.peek_values }
+ assert_raise(FrozenError) { e.feed 1 }
+ assert_raise(FrozenError) { e.rewind }
+ end
end
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index 87ccd5102b..4b5f18e7bb 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -69,25 +69,20 @@ class TestEnv < Test::Unit::TestCase
end
def test_clone
- warning = /ENV\.clone is deprecated; use ENV\.to_h instead/
- clone = assert_deprecated_warning(warning) {
+ message = /Cannot clone ENV/
+ assert_raise_with_message(TypeError, message) {
ENV.clone
}
- assert_same(ENV, clone)
-
- clone = assert_deprecated_warning(warning) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: false)
}
- assert_same(ENV, clone)
-
- clone = assert_deprecated_warning(warning) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: nil)
}
- assert_same(ENV, clone)
-
- assert_raise(TypeError) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: true)
}
+
assert_raise(ArgumentError) {
ENV.clone(freeze: 1)
}
@@ -494,12 +489,20 @@ class TestEnv < Test::Unit::TestCase
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"})
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"})
+ check(ENV.to_hash.to_a, [%w(foo zot), %w(baz quux), %w(a b), %w(c d)])
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo foo_bar_zot), %w(baz baz_qux_quux), %w(a b), %w(c d)])
end
def test_huge_value
@@ -609,8 +612,8 @@ class TestEnv < Test::Unit::TestCase
<<-"end;"
envvars_to_check = [
"foo\0bar",
- "#{'\xa1\xa1'}".force_encoding(Encoding::UTF_16LE),
- "foo".force_encoding(Encoding::ISO_2022_JP),
+ "#{'\xa1\xa1'}".dup.force_encoding(Encoding::UTF_16LE),
+ "foo".dup.force_encoding(Encoding::ISO_2022_JP),
]
envvars_to_check.each do |#{var_name}|
#{str_for_yielding_exception_class(code_str)}
@@ -680,37 +683,6 @@ class TestEnv < Test::Unit::TestCase
end;
end
- def test_clone_in_ractor
- assert_ractor(<<-"end;")
- r = Ractor.new do
- original_warning_state = Warning[:deprecated]
- Warning[:deprecated] = false
-
- begin
- Ractor.yield ENV.clone.object_id
- Ractor.yield ENV.clone(freeze: false).object_id
- Ractor.yield ENV.clone(freeze: nil).object_id
-
- #{str_for_yielding_exception_class("ENV.clone(freeze: true)")}
- #{str_for_yielding_exception_class("ENV.clone(freeze: 1)")}
- #{str_for_yielding_exception_class("ENV.clone(foo: false)")}
- #{str_for_yielding_exception_class("ENV.clone(1)")}
- #{str_for_yielding_exception_class("ENV.clone(1, foo: false)")}
-
- ensure
- Warning[:deprecated] = original_warning_state
- end
- end
- assert_equal(ENV.object_id, r.take)
- assert_equal(ENV.object_id, r.take)
- assert_equal(ENV.object_id, r.take)
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
- 4.times do
- #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, "r")}
- end
- end;
- end
-
def test_has_value_in_ractor
assert_ractor(<<-"end;")
r = Ractor.new do
diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb
index d55977c986..082d1dc03c 100644
--- a/test/ruby/test_eval.rb
+++ b/test/ruby/test_eval.rb
@@ -488,6 +488,9 @@ class TestEval < Test::Unit::TestCase
end
end
assert_equal(feature6609, feature6609_method)
+ ensure
+ Object.undef_method(:feature6609_block) rescue nil
+ Object.undef_method(:feature6609_method) rescue nil
end
def test_eval_using_integer_as_binding
@@ -544,8 +547,8 @@ class TestEval < Test::Unit::TestCase
end
def test_eval_location_binding
- assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", nil))
- assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", binding))
+ assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", nil))
+ assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", binding))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo'))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo'))
assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2))
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index 0b05ff7c51..4b7d709906 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -416,7 +416,7 @@ class TestException < Test::Unit::TestCase
assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/)
- assert_in_out_err([], <<-INPUT, [], /backtrace must be Array of String \(TypeError\)$/)
+ assert_in_out_err([], <<-INPUT, [], /backtrace must be an Array of String or an Array of Thread::Backtrace::Location \(TypeError\)$/)
begin
raise
rescue
@@ -478,6 +478,12 @@ end.join
def to_s; ""; end
end
assert_equal(e.inspect, e.new.inspect)
+
+ # https://bugs.ruby-lang.org/issues/18170#note-13
+ assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect)
+ assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect)
+ assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect)
+ assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').inspect)
end
def test_to_s
@@ -502,6 +508,16 @@ end.join
assert_raise(TypeError) { e.set_backtrace(1) }
assert_raise(TypeError) { e.set_backtrace([1]) }
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(caller_locations(1, 1) + ["foo"])
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
+
+ error = assert_raise(TypeError) do
+ e.set_backtrace(["foo"] + caller_locations(1, 1))
+ end
+ assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"
end
def test_exit_success_p
@@ -534,6 +550,14 @@ end.join
assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding)
end
+ def test_errno_constants
+ assert_equal [:NOERROR], Errno.constants.grep_v(/\AE/)
+ all_assertions_foreach("should be a subclass of SystemCallError", *Errno.constants) do |c|
+ e = Errno.const_get(c)
+ assert_operator e, :<, SystemCallError, proc {e.ancestors.inspect}
+ end
+ end
+
def test_too_many_args_in_eval
bug5720 = '[ruby-core:41520]'
arg_string = (0...140000).to_a.join(", ")
@@ -679,7 +703,7 @@ end.join
def test_machine_stackoverflow
bug9109 = '[ruby-dev:47804] [Bug #9109]'
- assert_separately(%w[--disable-gem], <<-SRC)
+ assert_separately([], <<-SRC)
assert_raise(SystemStackError, #{bug9109.dump}) {
h = {a: ->{h[:a].call}}
h[:a].call
@@ -690,7 +714,7 @@ end.join
def test_machine_stackoverflow_by_define_method
bug9454 = '[ruby-core:60113] [Bug #9454]'
- assert_separately(%w[--disable-gem], <<-SRC)
+ assert_separately([], <<-SRC)
assert_raise(SystemStackError, #{bug9454.dump}) {
define_method(:foo) {self.foo}
self.foo
@@ -795,7 +819,7 @@ end.join
def test_cause_at_end
errs = [
/-: unexpected return\n/,
- /.*undefined local variable or method `n'.*\n/,
+ /.*undefined local variable or method 'n'.*\n/,
]
assert_in_out_err([], <<-'end;', [], errs)
END{n}; END{return}
@@ -1031,7 +1055,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_message_of_name_error
- assert_raise_with_message(NameError, /\Aundefined method `foo' for module `#<Module:.*>'$/) do
+ assert_raise_with_message(NameError, /\Aundefined method 'foo' for module '#<Module:.*>'$/) do
Module.new do
module_function :foo
end
@@ -1040,8 +1064,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
def capture_warning_warn(category: false)
verbose = $VERBOSE
- deprecated = Warning[:deprecated]
- experimental = Warning[:experimental]
+ categories = Warning.categories.to_h {|cat| [cat, Warning[cat]]}
warning = []
::Warning.class_eval do
@@ -1053,22 +1076,20 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
warning << [str, category]
end
else
- define_method(:warn) do |str|
+ define_method(:warn) do |str, category: nil|
warning << str
end
end
end
$VERBOSE = true
- Warning[:deprecated] = true
- Warning[:experimental] = true
+ Warning.categories.each {|cat| Warning[cat] = true}
yield
return warning
ensure
$VERBOSE = verbose
- Warning[:deprecated] = deprecated
- Warning[:experimental] = experimental
+ categories.each {|cat, flag| Warning[cat] = flag}
::Warning.class_eval do
remove_method :warn
@@ -1079,7 +1100,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
def test_warning_warn
warning = capture_warning_warn {$asdfasdsda_test_warning_warn}
- assert_match(/global variable `\$asdfasdsda_test_warning_warn' not initialized/, warning[0])
+ assert_match(/global variable '\$asdfasdsda_test_warning_warn' not initialized/, warning[0])
assert_equal(["a\nz\n"], capture_warning_warn {warn "a\n", "z"})
assert_equal([], capture_warning_warn {warn})
@@ -1087,19 +1108,13 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_warn_deprecated_backwards_compatibility_category
- omit "no method to test"
-
- warning = capture_warning_warn { }
-
- assert_match(/deprecated/, warning[0])
- end
-
- def test_warn_deprecated_category
- omit "no method to test"
-
- warning = capture_warning_warn(category: true) { }
+ (message, category), = capture_warning_warn(category: true) do
+ $; = "www"
+ $; = nil
+ end
- assert_equal :deprecated, warning[0][1]
+ assert_include message, 'deprecated'
+ assert_equal :deprecated, category
end
def test_kernel_warn_uplevel
@@ -1155,7 +1170,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_warning_warn_super
- assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /global variable `\$asdfiasdofa_test_warning_warn_super' not initialized/)
+ assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /global variable '\$asdfiasdofa_test_warning_warn_super' not initialized/)
{#
module Warning
def warn(message)
@@ -1171,48 +1186,24 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
def test_warning_category
assert_raise(TypeError) {Warning[nil]}
assert_raise(ArgumentError) {Warning[:XXXX]}
- assert_include([true, false], Warning[:deprecated])
- assert_include([true, false], Warning[:experimental])
- end
- def test_warning_category_deprecated
- warning = EnvUtil.verbose_warning do
- deprecated = Warning[:deprecated]
- Warning[:deprecated] = true
- Warning.warn "deprecated feature", category: :deprecated
- ensure
- Warning[:deprecated] = deprecated
- end
- assert_equal "deprecated feature", warning
-
- warning = EnvUtil.verbose_warning do
- deprecated = Warning[:deprecated]
- Warning[:deprecated] = false
- Warning.warn "deprecated feature", category: :deprecated
- ensure
- Warning[:deprecated] = deprecated
- end
- assert_empty warning
- end
+ all_assertions_foreach("categories", *Warning.categories) do |cat|
+ value = Warning[cat]
+ assert_include([true, false], value)
- def test_warning_category_experimental
- warning = EnvUtil.verbose_warning do
- experimental = Warning[:experimental]
- Warning[:experimental] = true
- Warning.warn "experimental feature", category: :experimental
- ensure
- Warning[:experimental] = experimental
- end
- assert_equal "experimental feature", warning
-
- warning = EnvUtil.verbose_warning do
- experimental = Warning[:experimental]
- Warning[:experimental] = false
- Warning.warn "experimental feature", category: :experimental
+ enabled = EnvUtil.verbose_warning do
+ Warning[cat] = true
+ Warning.warn "#{cat} feature", category: cat
+ end
+ disabled = EnvUtil.verbose_warning do
+ Warning[cat] = false
+ Warning.warn "#{cat} feature", category: cat
+ end
ensure
- Warning[:experimental] = experimental
+ Warning[cat] = value
+ assert_equal "#{cat} feature", enabled
+ assert_empty disabled
end
- assert_empty warning
end
def test_undef_Warning_warn
@@ -1260,7 +1251,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
begin;
class Bug < RuntimeError
def backtrace
- IO.readlines(IO::NULL)
+ File.readlines(IO::NULL)
end
end
bug = Bug.new '[ruby-core:85939] [Bug #14577]'
@@ -1304,7 +1295,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
def test_backtrace_in_eval
bug = '[ruby-core:84434] [Bug #14229]'
- assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval\):1:/, bug)
+ assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval at .*\):1:/, bug)
end
def test_full_message
@@ -1406,6 +1397,14 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end;
end
+ def test_marshal_circular_cause
+ dump = "\x04\bo:\x11RuntimeError\b:\tmesgI\"\berr\x06:\x06ET:\abt[\x00:\ncause@\x05"
+ assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do
+ e = Marshal.load(dump)
+ assert_same(e, e.cause)
+ end
+ end
+
def test_super_in_method_missing
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -1417,7 +1416,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
bug14670 = '[ruby-dev:50522] [Bug #14670]'
- assert_raise_with_message(NoMethodError, /`foo'/, bug14670) do
+ assert_raise_with_message(NoMethodError, /'foo'/, bug14670) do
Object.new.foo
end
end;
@@ -1441,6 +1440,15 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
end
+ def test_detailed_message_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+ end
+ end
+
def test_full_message_with_custom_detailed_message
e = RuntimeError.new("message")
opt_ = nil
@@ -1451,4 +1459,70 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_match("BOO!", e.full_message.lines.first)
assert_equal({ highlight: Exception.to_tty? }, opt_)
end
+
+ def test_full_message_with_encoding
+ message = "\u{dc}bersicht"
+ begin
+ begin
+ raise message
+ rescue => e
+ raise "\n#{e.message}"
+ end
+ rescue => e
+ end
+ assert_include(e.full_message, message)
+ end
+
+ def test_syntax_error_detailed_message
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ Thread.new {}.join
+ "<#{super}>\n""<#{File.basename(__FILE__)}>"
+ rescue ThreadError => e
+ e.message
+ end
+ end
+ end;
+ pattern = /^<detail\.rb>/
+ assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern)
+
+ File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 1 +
+ end;
+ assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,|
+ assert_empty(stdout)
+ assert_not_empty(stderr.grep(pattern))
+ error, = stderr.grep(/unexpected end-of-input/)
+ assert_not_nil(error)
+ assert_match(/<.*unexpected end-of-input.*>/, error)
+ end
+ end
+ end
+
+ def test_syntax_error_path
+ e = assert_raise(SyntaxError) {
+ eval("1+", nil, "test_syntax_error_path.rb")
+ }
+ assert_equal("test_syntax_error_path.rb", e.path)
+
+ Dir.mktmpdir do |dir|
+ lib = File.join(dir, "syntax_error-path.rb")
+ File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ STDERR.puts "\n""path=#{path}\n"
+ super
+ end
+ end
+ end;
+ main = File.join(dir, "syntax_error.rb")
+ File.write(main, "1+\n")
+ assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*])
+ end
+ end
end
diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb
index 5825aaf6cc..45e5d12092 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -34,7 +34,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers
- omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
max = 1000
assert_equal(max, max.times{
Fiber.new{}
@@ -82,12 +82,14 @@ class TestFiber < Test::Unit::TestCase
f.resume
f.resume
}
- assert_raise(RuntimeError){
- Fiber.new{
- @c = callcc{|c| @c = c}
- }.resume
- @c.call # cross fiber callcc
- }
+ if respond_to?(:callcc)
+ assert_raise(RuntimeError){
+ Fiber.new{
+ @c = callcc{|c| @c = c}
+ }.resume
+ @c.call # cross fiber callcc
+ }
+ end
assert_raise(RuntimeError){
Fiber.new{
raise
@@ -422,7 +424,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_fatal_in_fiber
- assert_in_out_err(["-r-test-/fatal/rb_fatal", "-e", <<-EOS], "", [], /ok/)
+ assert_in_out_err(["-r-test-/fatal", "-e", <<-EOS], "", [], /ok/)
Fiber.new{
Bug.rb_fatal "ok"
}.resume
diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb
index 905416911a..aa10566bfa 100644
--- a/test/ruby/test_file.rb
+++ b/test/ruby/test_file.rb
@@ -460,6 +460,48 @@ class TestFile < Test::Unit::TestCase
end
end
+ def test_file_open_newline_option
+ Dir.mktmpdir(__method__.to_s) do |tmpdir|
+ path = File.join(tmpdir, "foo")
+ test = lambda do |newline|
+ File.open(path, "wt", newline: newline) do |f|
+ f.write "a\n"
+ f.puts "b"
+ end
+ File.binread(path)
+ end
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\r\nb\r\n", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+
+ test = lambda do |newline|
+ File.open(path, "rt", newline: newline) do |f|
+ f.read
+ end
+ end
+
+ File.binwrite(path, "a\nb\n")
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\nb\n", test.(:crlf))
+ assert_equal("a\nb\n", test.(:cr))
+
+ File.binwrite(path, "a\r\nb\r\n")
+ assert_equal("a\r\nb\r\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ # Work on both Windows and non-Windows
+ assert_include(["a\r\nb\r\n", "a\nb\n"], test.(:crlf))
+ assert_equal("a\r\nb\r\n", test.(:cr))
+
+ File.binwrite(path, "a\rb\r")
+ assert_equal("a\rb\r", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\rb\r", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+ end
+ end
+
def test_open_nul
Dir.mktmpdir(__method__.to_s) do |tmpdir|
path = File.join(tmpdir, "foo")
@@ -485,7 +527,7 @@ class TestFile < Test::Unit::TestCase
io.write "foo"
io.flush
assert_equal 3, io.size
- assert_raise(IOError) { io.path }
+ assert_nil io.path
ensure
io&.close
end
@@ -512,4 +554,250 @@ class TestFile < Test::Unit::TestCase
assert_file.absolute_path?("/foo/bar\\baz")
end
end
+
+ class NewlineConvTests < Test::Unit::TestCase
+ TEST_STRING_WITH_CRLF = "line1\r\nline2\r\n".freeze
+ TEST_STRING_WITH_LF = "line1\nline2\n".freeze
+
+ def setup
+ @tmpdir = Dir.mktmpdir(self.class.name)
+ @read_path_with_crlf = File.join(@tmpdir, "read_path_with_crlf")
+ File.binwrite(@read_path_with_crlf, TEST_STRING_WITH_CRLF)
+ @read_path_with_lf = File.join(@tmpdir, "read_path_with_lf")
+ File.binwrite(@read_path_with_lf, TEST_STRING_WITH_LF)
+ @write_path = File.join(@tmpdir, "write_path")
+ File.binwrite(@write_path, '')
+ end
+
+ def teardown
+ FileUtils.rm_rf @tmpdir
+ end
+
+ def windows?
+ /cygwin|mswin|mingw/ =~ RUBY_PLATFORM
+ end
+
+ def open_file_with(method, filename, mode)
+ read_or_write = mode.include?('w') ? :write : :read
+ binary_or_text = mode.include?('b') ? :binary : :text
+
+ f = case method
+ when :ruby_file_open
+ File.open(filename, mode)
+ when :c_rb_file_open
+ Bug::File::NewlineConv.rb_file_open(filename, read_or_write, binary_or_text)
+ when :c_rb_io_fdopen
+ Bug::File::NewlineConv.rb_io_fdopen(filename, read_or_write, binary_or_text)
+ else
+ raise "Don't know how to open with #{method}"
+ end
+
+ begin
+ yield f
+ ensure
+ f.close
+ end
+ end
+
+ def assert_file_contents_has_lf(f)
+ assert_equal TEST_STRING_WITH_LF, f.read
+ end
+
+ def assert_file_contents_has_crlf(f)
+ assert_equal TEST_STRING_WITH_CRLF, f.read
+ end
+
+ def assert_file_contents_has_lf_on_windows(f)
+ if windows?
+ assert_file_contents_has_lf(f)
+ else
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def assert_file_contents_has_crlf_on_windows(f)
+ if windows?
+ assert_file_contents_has_crlf(f)
+ else
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_read_crlf
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_read_crlf
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_ruby_file_open_text_mode_read_lf
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_read_lf
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_ruby_file_open_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_ruby_file_open_text_mode_write_lf
+ open_file_with(:ruby_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_write_lf
+ open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_ruby_file_open_bin_mode_write_crlf
+ open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_crlf
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_read_crlf
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_lf
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_read_lf
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_write_lf
+ open_file_with(:c_rb_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_write_lf
+ open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_file_open_bin_mode_write_crlf
+ open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_file_open_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_c_rb_file_open_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_c_rb_file_open_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_file_open_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_crlf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_crlf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_lf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_lf
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_write_lf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_write_lf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) }
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_write_crlf
+ open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF }
+ File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) }
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf_on_windows(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_crlf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_crlf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_text_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+
+ def test_c_rb_io_fdopen_bin_mode_read_lf_with_utf8_encoding
+ open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') do |f|
+ f.set_encoding Encoding::UTF_8, '-'
+ assert_file_contents_has_lf(f)
+ end
+ end
+ end
end
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 579e4d77a9..fbb18f07f9 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -649,7 +649,7 @@ class TestFileExhaustive < Test::Unit::TestCase
# ignore unsupporting filesystems
rescue Errno::EPERM
# Docker prohibits statx syscall by the default.
- skip("statx(2) is prohibited by seccomp")
+ omit("statx(2) is prohibited by seccomp")
end
assert_raise(Errno::ENOENT) { File.birthtime(nofile) }
end if File.respond_to?(:birthtime)
@@ -1407,6 +1407,8 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_exclusive
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
timeout = EnvUtil.apply_timeout_scale(0.1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_EX)
@@ -1436,6 +1438,8 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_shared
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
timeout = EnvUtil.apply_timeout_scale(0.1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_SH)
@@ -1513,9 +1517,12 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(File.zero?(f), test(?z, f), f)
stat = File.stat(f)
- assert_equal(stat.atime, File.atime(f), f)
- assert_equal(stat.ctime, File.ctime(f), f)
- assert_equal(stat.mtime, File.mtime(f), f)
+ unless stat.chardev?
+ # null device may be accessed by other processes
+ assert_equal(stat.atime, File.atime(f), f)
+ assert_equal(stat.ctime, File.ctime(f), f)
+ assert_equal(stat.mtime, File.mtime(f), f)
+ end
assert_bool_equal(stat.blockdev?, File.blockdev?(f), f)
assert_bool_equal(stat.chardev?, File.chardev?(f), f)
assert_bool_equal(stat.directory?, File.directory?(f), f)
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index 57a46fce92..b91b904d1e 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -141,6 +141,9 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError){Float("1__1")}
assert_raise(ArgumentError){Float("1.")}
assert_raise(ArgumentError){Float("1.e+00")}
+ assert_raise(ArgumentError){Float("0x.1")}
+ assert_raise(ArgumentError){Float("0x1.")}
+ assert_raise(ArgumentError){Float("0x1.0")}
assert_raise(ArgumentError){Float("0x1.p+0")}
# add expected behaviour here.
assert_equal(10, Float("1_0"))
@@ -224,6 +227,12 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-3.5, (-11.5).remainder(-4))
assert_predicate(Float::NAN.remainder(4), :nan?)
assert_predicate(4.remainder(Float::NAN), :nan?)
+
+ ten = Object.new
+ def ten.coerce(other)
+ [other, 10]
+ end
+ assert_equal(4, 14.0.remainder(ten))
end
def test_to_s
@@ -483,6 +492,17 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-1.26, -1.255.round(2))
end
+ def test_round_half_even_with_precision
+ assert_equal(767573.18759, 767573.1875850001.round(5, half: :even))
+ assert_equal(767573.18758, 767573.187585.round(5, half: :even))
+ assert_equal(767573.18758, 767573.1875849998.round(5, half: :even))
+ assert_equal(767573.18758, 767573.187575.round(5, half: :even))
+ assert_equal(-767573.18759, -767573.1875850001.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.187585.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.1875849998.round(5, half: :even))
+ assert_equal(-767573.18758, -767573.187575.round(5, half: :even))
+ end
+
def test_floor_with_precision
assert_equal(+0.0, +0.001.floor(1))
assert_equal(-0.1, -0.001.floor(1))
diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb
new file mode 100644
index 0000000000..2918a2afd8
--- /dev/null
+++ b/test/ruby/test_frozen.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestFrozen < Test::Unit::TestCase
+ def test_setting_ivar_on_frozen_obj
+ obj = Object.new
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_obj_with_ivars
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@b, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string
+ str = "str"
+ str.freeze
+ assert_raise(FrozenError) { str.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) }
+ end
+end
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index b081e9fa78..39b001c3d0 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -128,6 +128,8 @@ class TestGc < Test::Unit::TestCase
omit 'stress' if GC.stress
stat = GC.stat
+ # marking_time + sweeping_time could differ from time by 1 because they're stored in nanoseconds
+ assert_in_delta stat[:time], stat[:marking_time] + stat[:sweeping_time], 1
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect
assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]
@@ -149,10 +151,15 @@ class TestGc < Test::Unit::TestCase
GC.stat(stat)
GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
- GC.stat_heap(i, stat_heap)
- GC.stat(stat)
+ begin
+ reenable_gc = !GC.disable
+ GC.stat_heap(i, stat_heap)
+ GC.stat(stat)
+ ensure
+ GC.enable if reenable_gc
+ end
- assert_equal GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * (2**i), stat_heap[:slot_size]
+ assert_equal GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] * (2**i), stat_heap[:slot_size]
assert_operator stat_heap[:heap_allocatable_pages], :<=, stat[:heap_allocatable_pages]
assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages]
assert_operator stat_heap[:heap_eden_slots], :>=, 0
@@ -160,6 +167,11 @@ class TestGc < Test::Unit::TestCase
assert_operator stat_heap[:heap_tomb_slots], :>=, 0
assert_operator stat_heap[:total_allocated_pages], :>=, 0
assert_operator stat_heap[:total_freed_pages], :>=, 0
+ assert_operator stat_heap[:force_major_gc_count], :>=, 0
+ assert_operator stat_heap[:force_incremental_marking_finish_count], :>=, 0
+ assert_operator stat_heap[:total_allocated_objects], :>=, 0
+ assert_operator stat_heap[:total_freed_objects], :>=, 0
+ assert_operator stat_heap[:total_freed_objects], :<=, stat_heap[:total_allocated_objects]
end
GC.stat_heap(0, stat_heap)
@@ -171,6 +183,7 @@ class TestGc < Test::Unit::TestCase
end
def test_stat_heap_all
+ omit "flaky with RJIT, which allocates objects itself" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
stat_heap_all = {}
stat_heap = {}
@@ -182,6 +195,11 @@ class TestGc < Test::Unit::TestCase
GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
GC.stat_heap(i, stat_heap)
+ # Remove keys that can vary between invocations
+ %i(total_allocated_objects).each do |sym|
+ stat_heap[sym] = stat_heap_all[i][sym] = 0
+ end
+
assert_equal stat_heap, stat_heap_all[i]
end
@@ -193,8 +211,10 @@ class TestGc < Test::Unit::TestCase
stat = GC.stat
stat_heap = GC.stat_heap
- GC.stat(stat)
- GC.stat_heap(nil, stat_heap)
+ 2.times do
+ GC.stat(stat)
+ GC.stat_heap(nil, stat_heap)
+ end
stat_heap_sum = Hash.new(0)
stat_heap.values.each do |hash|
@@ -207,17 +227,36 @@ class TestGc < Test::Unit::TestCase
assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + stat_heap_sum[:heap_tomb_slots]
assert_equal stat[:total_allocated_pages], stat_heap_sum[:total_allocated_pages]
assert_equal stat[:total_freed_pages], stat_heap_sum[:total_freed_pages]
+ assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects]
+ assert_equal stat[:total_freed_objects], stat_heap_sum[:total_freed_objects]
+ end
+
+ def test_measure_total_time
+ assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ GC.measure_total_time = false
+
+ time_before = GC.stat(:time)
+
+ # Generate some garbage
+ Random.new.bytes(100 * 1024 * 1024)
+ GC.start
+
+ time_after = GC.stat(:time)
+
+ # If time measurement is disabled, the time stat should not change
+ assert_equal time_before, time_after
+ RUBY
end
def test_latest_gc_info
omit 'stress' if GC.stress
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- count.times{ "a" + "b" }
- assert_equal :newobj, GC.latest_gc_info[:gc_by]
- eom
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ GC.start
+ count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
+ count.times{ "a" + "b" }
+ assert_equal :newobj, GC.latest_gc_info[:gc_by]
+ RUBY
GC.latest_gc_info(h = {}) # allocate hash and rehearsal
GC.start
@@ -228,6 +267,7 @@ class TestGc < Test::Unit::TestCase
assert_equal :force, h[:major_by] if use_rgengc?
assert_equal :method, h[:gc_by]
assert_equal true, h[:immediate_sweep]
+ assert_equal true, h.key?(:need_major_by)
GC.stress = true
assert_equal :force, GC.latest_gc_info[:major_by]
@@ -245,8 +285,82 @@ class TestGc < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")}
end
+ def test_latest_gc_info_need_major_by
+ return unless use_rgengc?
+ omit 'stress' if GC.stress
+
+ 3.times { GC.start }
+ assert_nil GC.latest_gc_info(:need_major_by)
+
+ # allocate objects until need_major_by is set or major GC happens
+ objects = []
+ while GC.latest_gc_info(:need_major_by).nil?
+ objects.append(100.times.map { '*' })
+ end
+
+ # We need to ensure that no GC gets ran before the call to GC.start since
+ # it would trigger a major GC. Assertions could allocate objects and
+ # trigger a GC so we don't run assertions until we perform the major GC.
+ need_major_by = GC.latest_gc_info(:need_major_by)
+ GC.start(full_mark: false) # should be upgraded to major
+ major_by = GC.latest_gc_info(:major_by)
+
+ assert_not_nil(need_major_by)
+ assert_not_nil(major_by)
+ end
+
+ def test_latest_gc_info_weak_references_count
+ assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ count = 10_000
+ # Some weak references may be created, so allow some margin of error
+ error_tolerance = 100
+
+ # Run full GC to clear out weak references
+ GC.start
+ # Run full GC again to collect stats about weak references
+ GC.start
+
+ before_weak_references_count = GC.latest_gc_info(:weak_references_count)
+ before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
+
+ # Create some objects and place it in a WeakMap
+ wmap = ObjectSpace::WeakMap.new
+ ary = Array.new(count)
+ enum = count.times
+ enum.each.with_index do |i|
+ obj = Object.new
+ ary[i] = obj
+ wmap[obj] = nil
+ end
+
+ # Run full GC to collect stats about weak references
+ GC.start
+
+ assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + count - error_tolerance)
+ assert_operator(GC.latest_gc_info(:retained_weak_references_count), :>=, before_retained_weak_references_count + count - error_tolerance)
+ assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+
+ before_weak_references_count = GC.latest_gc_info(:weak_references_count)
+ before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
+
+ ary = nil
+
+ # Free ary, which should empty out the wmap
+ GC.start
+ # Run full GC again to collect stats about weak references
+ GC.start
+
+ # Sometimes the WeakMap has one element, which might be held on by registers.
+ assert_operator(wmap.size, :<=, 1)
+
+ assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance)
+ assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance)
+ assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+ RUBY
+ end
+
def test_stress_compile_send
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "")
+ assert_in_out_err([], <<-EOS, [], [], "")
GC.stress = true
begin
eval("A::B.c(1, 1, d: 234)")
@@ -256,7 +370,7 @@ class TestGc < Test::Unit::TestCase
end
def test_singleton_method
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]")
+ assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:42832]")
GC.stress = true
10.times do
obj = Object.new
@@ -268,7 +382,7 @@ class TestGc < Test::Unit::TestCase
end
def test_singleton_method_added
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:44436]")
+ assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:44436]")
class BasicObject
undef singleton_method_added
def singleton_method_added(mid)
@@ -284,19 +398,23 @@ class TestGc < Test::Unit::TestCase
def test_gc_parameter
env = {
- "RUBY_GC_MALLOC_LIMIT" => "60000000",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
+ "RUBY_GC_HEAP_INIT_SLOTS" => "100"
}
- assert_normal_exit("exit", "[ruby-core:39777]", :child_env => env)
+ assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [])
+ assert_in_out_err([env, "-W:deprecated", "-e", "exit"], "", [],
+ /The environment variable RUBY_GC_HEAP_INIT_SLOTS is deprecated; use environment variables RUBY_GC_HEAP_%d_INIT_SLOTS instead/)
- env = {
- "RUBYOPT" => "",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
- }
- assert_in_out_err([env, "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W1", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_INIT_SLOTS=100000/, "[ruby-core:39795]")
+ env = {}
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000"
+ end
+ assert_normal_exit("exit", "", :child_env => env)
+
+ env = {}
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "0"
+ end
+ assert_normal_exit("exit", "", :child_env => env)
env = {
"RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0",
@@ -306,16 +424,13 @@ class TestGc < Test::Unit::TestCase
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]")
- env = {
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000",
- "RUBY_GC_HEAP_FREE_SLOTS" => "10000",
- "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.9",
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0\.9/, "")
-
- # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
- assert_in_out_err([env, "-e", "1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") if use_rgengc?
+ if use_rgengc?
+ env = {
+ "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.4",
+ }
+ # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
+ assert_in_out_err([env, "-e", "GC.start; 1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "")
+ end
env = {
"RUBY_GC_MALLOC_LIMIT" => "60000000",
@@ -338,6 +453,127 @@ class TestGc < Test::Unit::TestCase
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
end
+
+ ["0.01", "0.1", "1.0"].each do |i|
+ env = {"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0", "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO" => i}
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
+ GC.disable
+ GC.start
+ assert_equal((GC.stat[:old_objects] * #{i}).to_i, GC.stat[:remembered_wb_unprotected_objects_limit])
+ RUBY
+ end
+ end
+
+ def test_gc_parameter_init_slots
+ assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ # Constant from gc.c.
+ GC_HEAP_INIT_SLOTS = 10_000
+ GC.stat_heap.each do |_, s|
+ multiple = s[:slot_size] / (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD])
+ # Allocatable pages are assumed to have lost 1 slot due to alignment.
+ slots_per_page = (GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] / multiple) - 1
+
+ total_slots = s[:heap_eden_slots] + s[:heap_allocatable_pages] * slots_per_page
+ assert_operator(total_slots, :>=, GC_HEAP_INIT_SLOTS, s)
+ end
+ RUBY
+
+ env = {}
+ # Make the heap big enough to ensure the heap never needs to grow.
+ sizes = GC.stat_heap.keys.reverse.map { |i| (i + 1) * 100_000 }
+ GC.stat_heap.keys.each do |heap|
+ env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = sizes[heap].to_s
+ end
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
+ SIZES = #{sizes}
+ GC.stat_heap.each do |i, s|
+ multiple = s[:slot_size] / (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD])
+ # Allocatable pages are assumed to have lost 1 slot due to alignment.
+ slots_per_page = (GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] / multiple) - 1
+
+ total_slots = s[:heap_eden_slots] + s[:heap_allocatable_pages] * slots_per_page
+
+ # The delta is calculated as follows:
+ # - For allocated pages, each page can vary by 1 slot due to alignment.
+ # - For allocatable pages, we can end up with at most 1 extra page of slots.
+ assert_in_delta(SIZES[i], total_slots, s[:heap_eden_pages] + slots_per_page, s)
+ end
+ RUBY
+
+ # Check that the configured sizes are "remembered" across GC invocations.
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
+ SIZES = #{sizes}
+
+ # Fill size pool 0 with transient objects.
+ ary = []
+ while GC.stat_heap(0, :heap_allocatable_pages) != 0
+ ary << Object.new
+ end
+ ary.clear
+ ary = nil
+
+ # Clear all the objects that were allocated.
+ GC.start
+
+ # Check that we still have the same number of slots as initially configured.
+ GC.stat_heap.each do |i, s|
+ multiple = s[:slot_size] / (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD])
+ # Allocatable pages are assumed to have lost 1 slot due to alignment.
+ slots_per_page = (GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] / multiple) - 1
+
+ total_slots = s[:heap_eden_slots] + s[:heap_allocatable_pages] * slots_per_page
+
+ # The delta is calculated as follows:
+ # - For allocated pages, each page can vary by 1 slot due to alignment.
+ # - For allocatable pages, we can end up with at most 1 extra page of slots.
+ assert_in_delta(SIZES[i], total_slots, s[:heap_eden_pages] + slots_per_page, s)
+ end
+ RUBY
+
+ # Check that we don't grow the heap in minor GC if we have alloctable pages.
+ env["RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO"] = "0.3"
+ env["RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO"] = "0.99"
+ env["RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO"] = "1.0"
+ env["RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR"] = "100" # Large value to disable major GC
+ assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
+ SIZES = #{sizes}
+
+ # Run a major GC to clear out dead objects.
+ GC.start
+
+ # Disable GC so we can control when GC is ran.
+ GC.disable
+
+ # Run minor GC enough times so that we don't grow the heap because we
+ # haven't yet ran RVALUE_OLD_AGE minor GC cycles.
+ GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE].times { GC.start(full_mark: false) }
+
+ # Fill size pool 0 to over 50% full so that the number of allocatable
+ # pages that will be created will be over the number in heap_allocatable_pages
+ # (calculated using RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO).
+ # 70% was chosen here to guarantee that.
+ ary = []
+ while GC.stat_heap(0, :heap_allocatable_pages) >
+ (GC.stat_heap(0, :heap_allocatable_pages) + GC.stat_heap(0, :heap_eden_pages)) * 0.3
+ ary << Object.new
+ end
+
+ GC.start(full_mark: false)
+
+ # Check that we still have the same number of slots as initially configured.
+ GC.stat_heap.each do |i, s|
+ multiple = s[:slot_size] / (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD])
+ # Allocatable pages are assumed to have lost 1 slot due to alignment.
+ slots_per_page = (GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] / multiple) - 1
+
+ total_slots = s[:heap_eden_slots] + s[:heap_allocatable_pages] * slots_per_page
+
+ # The delta is calculated as follows:
+ # - For allocated pages, each page can vary by 1 slot due to alignment.
+ # - For allocatable pages, we can end up with at most 1 extra page of slots.
+ assert_in_delta(SIZES[i], total_slots, s[:heap_eden_pages] + slots_per_page, s)
+ end
+ RUBY
end
def test_profiler_enabled
@@ -351,19 +587,27 @@ class TestGc < Test::Unit::TestCase
def test_profiler_clear
omit "for now"
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30
- GC::Profiler.enable
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 30)
+ GC::Profiler.enable
- GC.start
- assert_equal(1, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
+ GC.start
+ assert_equal(1, GC::Profiler.raw_data.size)
+ GC::Profiler.clear
+ assert_equal(0, GC::Profiler.raw_data.size)
- 200.times{ GC.start }
- assert_equal(200, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
- eom
+ 200.times{ GC.start }
+ assert_equal(200, GC::Profiler.raw_data.size)
+ GC::Profiler.clear
+ assert_equal(0, GC::Profiler.raw_data.size)
+ RUBY
+ end
+
+ def test_profiler_raw_data
+ GC::Profiler.enable
+ GC.start
+ assert GC::Profiler.raw_data
+ ensure
+ GC::Profiler.disable
end
def test_profiler_total_time
@@ -377,28 +621,62 @@ class TestGc < Test::Unit::TestCase
end
def test_finalizing_main_thread
- assert_in_out_err(%w[--disable-gems], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
+ assert_in_out_err([], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
ObjectSpace.define_finalizer(Thread.main) { p 'finalize' }
EOS
end
def test_expand_heap
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
- "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
+ assert_separately([], __FILE__, __LINE__, <<~'RUBY')
+ GC.start
+ base_length = GC.stat[:heap_eden_pages]
+ (base_length * 500).times{ 'a' }
+ GC.start
+ base_length = GC.stat[:heap_eden_pages]
+ (base_length * 500).times{ 'a' }
+ GC.start
+ assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
+ "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
- a = []
- (base_length * 500).times{ a << 'a'; nil }
- GC.start
- assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
- eom
+ a = []
+ (base_length * 500).times{ a << 'a'; nil }
+ GC.start
+ assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
+ RUBY
+ end
+
+ def test_thrashing_for_young_objects
+ # This test prevents bugs like [Bug #18929]
+
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ # Grow the heap
+ @ary = 100_000.times.map { Object.new }
+
+ # Warmup to make sure heap stabilizes
+ 1_000_000.times { Object.new }
+
+ before_stats = GC.stat
+ before_stat_heap = GC.stat_heap
+
+ 1_000_000.times { Object.new }
+
+ # Previous loop may have caused GC to be in an intermediate state,
+ # running a minor GC here will guarantee that GC will be complete
+ GC.start(full_mark: false)
+
+ after_stats = GC.stat
+ after_stat_heap = GC.stat_heap
+
+ # Debugging output to for failures in trunk-repeat50@phosphorus-docker
+ debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}"
+
+ # Should not be thrashing in page creation
+ assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg
+ assert_equal 0, after_stats[:heap_tomb_pages], debug_msg
+ assert_equal 0, after_stats[:total_freed_pages], debug_msg
+ # Only young objects, so should not trigger major GC
+ assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg
+ RUBY
end
def test_gc_internals
@@ -460,11 +738,11 @@ class TestGc < Test::Unit::TestCase
end
def test_finalizer_passed_object_id
- assert_in_out_err(%w[--disable-gems], <<-EOS, ["true"], [])
+ assert_in_out_err([], <<~RUBY, ["true"], [])
o = Object.new
obj_id = o.object_id
ObjectSpace.define_finalizer(o, ->(id){ p id == obj_id })
- EOS
+ RUBY
end
def test_verify_internal_consistency
@@ -549,6 +827,15 @@ class TestGc < Test::Unit::TestCase
obj = nil
end
end;
+
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #20042]'
+ begin;
+ def (f = Object.new).call = nil # missing ID
+ o = Object.new
+ ObjectSpace.define_finalizer(o, f)
+ o = nil
+ GC.start
+ end;
end
def test_object_ids_never_repeat
@@ -564,4 +851,30 @@ class TestGc < Test::Unit::TestCase
Module.new.class_eval( (["# shareable_constant_value: literal"] +
(0..100000).map {|i| "M#{ i } = {}" }).join("\n"))
end
+
+ def test_old_to_young_reference
+ original_gc_disabled = GC.disable
+
+ require "objspace"
+
+ old_obj = Object.new
+ 4.times { GC.start }
+
+ assert_include ObjectSpace.dump(old_obj), '"old":true'
+
+ young_obj = Object.new
+ old_obj.instance_variable_set(:@test, young_obj)
+
+ # Not immediately promoted to old generation
+ 3.times do
+ assert_not_include ObjectSpace.dump(young_obj), '"old":true'
+ GC.start
+ end
+
+ # Takes 4 GC to promote to old generation
+ GC.start
+ assert_include ObjectSpace.dump(young_obj), '"old":true'
+ ensure
+ GC.enable if !original_gc_disabled
+ end
end
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index da0023e6f3..f47c0b046b 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'test/unit'
-require 'fiddle'
-require 'etc'
if RUBY_PLATFORM =~ /s390x/
warn "Currently, it is known that the compaction does not work well on s390x; contribution is welcome https://github.com/ruby/ruby/pull/5077"
@@ -11,16 +9,7 @@ end
class TestGCCompact < Test::Unit::TestCase
module CompactionSupportInspector
def supports_auto_compact?
- return false if /wasm/ =~ RUBY_PLATFORM
- return true unless defined?(Etc::SC_PAGE_SIZE)
-
- begin
- return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0
- rescue NotImplementedError
- rescue ArgumentError
- end
-
- true
+ GC::OPTS.include?("GC_COMPACTION_SUPPORTED")
end
end
@@ -121,10 +110,6 @@ class TestGCCompact < Test::Unit::TestCase
end
end
- def os_page_size
- return true unless defined?(Etc::SC_PAGE_SIZE)
- end
-
def test_gc_compact_stats
list = []
@@ -139,10 +124,6 @@ class TestGCCompact < Test::Unit::TestCase
refute_predicate compact_stats[:moved], :empty?
end
- def memory_location(obj)
- (Fiddle.dlwrap(obj) >> 1)
- end
-
def big_list(level = 10)
if level > 0
big_list(level - 1)
@@ -155,27 +136,12 @@ class TestGCCompact < Test::Unit::TestCase
end
end
- # Find an object that's allocated in a slot that had a previous
- # tenant, and that tenant moved and is still alive
- def find_object_in_recycled_slot(addresses)
- new_object = nil
-
- 100_000.times do
- new_object = Object.new
- if addresses.index memory_location(new_object)
- break
- end
- end
-
- new_object
- end
-
def test_complex_hash_keys
list_of_objects = big_list
hash = list_of_objects.hash
GC.verify_compaction_references(toward: :empty)
assert_equal hash, list_of_objects.hash
- GC.verify_compaction_references(double_heap: false)
+ GC.verify_compaction_references(expand_heap: false)
assert_equal hash, list_of_objects.hash
end
@@ -200,4 +166,290 @@ class TestGCCompact < Test::Unit::TestCase
GC.compact
assert_equal count + 1, GC.stat(:compact_count)
end
+
+ def test_compacting_from_trace_point
+ obj = Object.new
+ def obj.tracee
+ :ret # expected to emit both line and call event from one instruction
+ end
+
+ results = []
+ TracePoint.new(:call, :line) do |tp|
+ results << tp.event
+ GC.verify_compaction_references
+ end.enable(target: obj.method(:tracee)) do
+ obj.tracee
+ end
+
+ assert_equal([:call, :line], results)
+ end
+
+ def test_updating_references_for_heap_allocated_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+
+ # Pointer in slice should point to buffer of ary
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_heap_allocated_frozen_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_frozen_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_moving_arrays_down_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $arys = ARY_COUNT.times.map do
+ ary = "abbbbbbbbbb".chars
+ ary.uniq!
+ end
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_arrays_up_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ ary = "hello".chars
+ $arys = ARY_COUNT.times.map do
+ x = []
+ ary.each { |e| x << e }
+ x
+ end
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_objects_between_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ class Foo
+ def add_ivars
+ 10.times do |i|
+ instance_variable_set("@foo" + i.to_s, 0)
+ end
+ end
+ end
+
+ OBJ_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $ary = OBJ_COUNT.times.map { Foo.new }
+ $ary.each(&:add_ivars)
+
+ GC.start
+ Foo.new.add_ivars
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 10)
+ refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_strings_up_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30, signal: :SEGV)
+ begin;
+ STR_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4
+ $ary = STR_COUNT.times.map { +"" << str }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 10)
+ refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_strings_down_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30, signal: :SEGV)
+ begin;
+ STR_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4).squeeze! }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT - 10)
+ refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
+ end;
+ end
+
+ def test_moving_hashes_down_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ # AR and ST hashes are in the same size pool on 32 bit
+ omit unless RbConfig::SIZEOF["uint64_t"] <= RbConfig::SIZEOF["void*"]
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30, signal: :SEGV)
+ begin;
+ HASH_COUNT = 50000
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ Fiber.new {
+ base_hash = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8 }
+ $ary = HASH_COUNT.times.map { base_hash.dup }
+ $ary.each_with_index { |h, i| h[:i] = 9 }
+ }.resume
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_down][:T_HASH], :>=, HASH_COUNT - 10)
+ end;
+ end
+
+ def test_moving_objects_between_size_pools_keeps_shape_frozen_status
+ # [Bug #19536]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class A
+ def add_ivars
+ @a = @b = @c = @d = 1
+ end
+
+ def set_a
+ @a = 10
+ end
+ end
+
+ a = A.new
+ a.add_ivars
+ a.freeze
+
+ b = A.new
+ b.add_ivars
+ b.set_a # Set the inline cache in set_a
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_raise(FrozenError) { a.set_a }
+ end;
+ end
end
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 073a0dabe8..f60ba0cffd 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -4,7 +4,6 @@ require 'test/unit'
EnvUtil.suppress_warning {require 'continuation'}
class TestHash < Test::Unit::TestCase
-
def test_hash
x = @cls[1=>2, 2=>4, 3=>6]
y = @cls[1=>2, 2=>4, 3=>6] # y = {1, 2, 2, 4, 3, 6} # 1.9 doesn't support
@@ -85,20 +84,9 @@ class TestHash < Test::Unit::TestCase
self => 'self', true => 'true', nil => 'nil',
'nil' => nil
]
- @verbose = $VERBOSE
end
def teardown
- $VERBOSE = @verbose
- end
-
- def test_bad_initialize_copy
- h = Class.new(Hash) {
- def initialize_copy(h)
- super(Object.new)
- end
- }.new
- assert_raise(TypeError) { h.dup }
end
def test_clear_initialize_copy
@@ -113,35 +101,6 @@ class TestHash < Test::Unit::TestCase
assert_equal(2, h[1])
end
- def test_dup_will_not_rehash
- assert_hash_does_not_rehash(&:dup)
- end
-
- def assert_hash_does_not_rehash
- obj = Object.new
- class << obj
- attr_accessor :hash_calls
- def hash
- @hash_calls += 1
- super
- end
- end
- obj.hash_calls = 0
- hash = {obj => 42}
- assert_equal(1, obj.hash_calls)
- yield hash
- assert_equal(1, obj.hash_calls)
- end
-
- def test_select_reject_will_not_rehash
- assert_hash_does_not_rehash do |hash|
- hash.select { true }
- end
- assert_hash_does_not_rehash do |hash|
- hash.reject { false }
- end
- end
-
def test_s_AREF_from_hash
h = @cls["a" => 100, "b" => 200]
assert_equal(100, h['a'])
@@ -219,6 +178,16 @@ class TestHash < Test::Unit::TestCase
assert_equal('default', h['spurious'])
end
+ def test_st_literal_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true)
+ begin;
+ 1_000_000.times do
+ # >8 element hashes are ST allocated rather than AR allocated
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9}
+ end
+ end;
+ end
+
def test_try_convert
assert_equal({1=>2}, Hash.try_convert({1=>2}))
assert_equal(nil, Hash.try_convert("1=>2"))
@@ -294,64 +263,6 @@ class TestHash < Test::Unit::TestCase
assert_equal(256, h[z])
end
- def test_AREF_fstring_key
- # warmup ObjectSpace.count_objects
- ObjectSpace.count_objects
-
- h = {"abc" => 1}
- before = ObjectSpace.count_objects[:T_STRING]
- 5.times{ h["abc"] }
- assert_equal before, ObjectSpace.count_objects[:T_STRING]
- end
-
- def test_ASET_fstring_key
- a, b = {}, {}
- assert_equal 1, a["abc"] = 1
- assert_equal 1, b["abc"] = 1
- assert_same a.keys[0], b.keys[0]
- end
-
- def test_ASET_fstring_non_literal_key
- underscore = "_"
- non_literal_strings = Proc.new{ ["abc#{underscore}def", "abc" * 5, "abc" + "def", "" << "ghi" << "jkl"] }
-
- a, b = {}, {}
- non_literal_strings.call.each do |string|
- assert_equal 1, a[string] = 1
- end
-
- non_literal_strings.call.each do |string|
- assert_equal 1, b[string] = 1
- end
-
- [a.keys, b.keys].transpose.each do |key_a, key_b|
- assert_same key_a, key_b
- end
- end
-
- def test_hash_aset_fstring_identity
- h = {}.compare_by_identity
- h['abc'] = 1
- h['abc'] = 2
- assert_equal 2, h.size, '[ruby-core:78783] [Bug #12855]'
- end
-
- def test_hash_aref_fstring_identity
- h = {}.compare_by_identity
- h['abc'] = 1
- assert_nil h['abc'], '[ruby-core:78783] [Bug #12855]'
- end
-
- def test_NEWHASH_fstring_key
- a = {"ABC" => :t}
- b = {"ABC" => :t}
- assert_same a.keys[0], b.keys[0]
- assert_same "ABC".freeze, a.keys[0]
- var = +'ABC'
- c = { var => :t }
- assert_same "ABC".freeze, c.keys[0]
- end
-
def test_EQUAL # '=='
h1 = @cls[ "a" => 1, "c" => 2 ]
h2 = @cls[ "a" => 1, "c" => 2, 7 => 35 ]
@@ -458,6 +369,10 @@ class TestHash < Test::Unit::TestCase
end
end
assert_equal(base.dup, h)
+
+ h = base.dup
+ assert_same h, h.delete_if {h.assoc(nil); true}
+ assert_empty h
end
def test_keep_if
@@ -828,14 +743,6 @@ class TestHash < Test::Unit::TestCase
assert_predicate(h, :compare_by_identity?)
end
- def test_replace_bug15358
- h1 = {}
- h2 = {a:1,b:2,c:3,d:4,e:5}
- h2.replace(h1)
- GC.start
- assert(true)
- end
-
def test_shift
h = @h.dup
@@ -951,13 +858,6 @@ class TestHash < Test::Unit::TestCase
assert_instance_of(Hash, h)
end
- def test_nil_to_h
- h = nil.to_h
- assert_equal({}, h)
- assert_nil(h.default)
- assert_nil(h.default_proc)
- end
-
def test_to_s
h = @cls[ 1 => 2, "cat" => "dog", 1.5 => :fred ]
assert_equal(h.inspect, h.to_s)
@@ -1004,12 +904,6 @@ class TestHash < Test::Unit::TestCase
assert_equal([], expected - vals)
end
- def test_initialize_wrong_arguments
- assert_raise(ArgumentError) do
- Hash.new(0) { }
- end
- end
-
def test_create
assert_equal({1=>2, 3=>4}, @cls[[[1,2],[3,4]]])
assert_raise(ArgumentError) { @cls[0, 1, 2] }
@@ -1291,15 +1185,6 @@ class TestHash < Test::Unit::TestCase
assert_raise(FrozenError) { h2.replace(42) }
end
- def test_replace_memory_leak
- assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}")
- h = ("aa".."zz").each_with_index.to_h
- 10_000.times {h.dup}
- begin;
- 500_000.times {h.dup.replace(h)}
- end;
- end
-
def test_size2
assert_equal(0, @cls[].size)
end
@@ -1383,6 +1268,15 @@ class TestHash < Test::Unit::TestCase
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
end
+ def test_update_on_identhash
+ key = +'a'
+ i = @cls[].compare_by_identity
+ i[key] = 0
+ h = @cls[].update(i)
+ key.upcase!
+ assert_equal(0, h.fetch('a'))
+ end
+
def test_merge
h1 = @cls[1=>2, 3=>4]
h2 = {1=>3, 5=>7}
@@ -1405,10 +1299,10 @@ class TestHash < Test::Unit::TestCase
expected[7] = 8
h2 = h.merge(7=>8)
assert_equal(expected, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h2 = h.merge({})
assert_equal(h, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h = @cls[]
h.compare_by_identity
@@ -1416,10 +1310,10 @@ class TestHash < Test::Unit::TestCase
h1.compare_by_identity
h2 = h.merge(7=>8)
assert_equal(h1, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h2 = h.merge({})
assert_equal(h, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
end
def test_merge!
@@ -1474,6 +1368,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
h = @cls[1=>2]
c = nil
f = false
@@ -1494,6 +1390,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_iter_level
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
h = @cls[1=>2, 3=>4]
c = nil
@@ -1512,6 +1410,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_escape
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
assert_nothing_raised(RuntimeError, bug9105) do
h=@cls[]
@@ -1526,6 +1426,8 @@ class TestHash < Test::Unit::TestCase
end
def test_callcc_reenter
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
bug9105 = '[ruby-dev:47803] [Bug #9105]'
assert_nothing_raised(RuntimeError, bug9105) do
h = @cls[1=>2,3=>4]
@@ -1573,6 +1475,16 @@ class TestHash < Test::Unit::TestCase
assert_predicate(h.dup, :compare_by_identity?, bug8703)
end
+ def test_compare_by_identy_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20145]", rss: true)
+ begin;
+ h = { 1 => 2 }.compare_by_identity
+ 1_000_000.times do
+ h.select { false }
+ end
+ end;
+ end
+
def test_same_key
bug9646 = '[ruby-dev:48047] [Bug #9646] Infinite loop at Hash#each'
h = @cls[a=[], 1]
@@ -1703,115 +1615,6 @@ class TestHash < Test::Unit::TestCase
end
end
- def test_exception_in_rehash_memory_leak
- return unless @cls == Hash
-
- bug9187 = '[ruby-core:58728] [Bug #9187]'
-
- prepare = <<-EOS
- class Foo
- def initialize
- @raise = false
- end
-
- def hash
- raise if @raise
- @raise = true
- return 0
- end
- end
- h = {Foo.new => true}
- EOS
-
- code = <<-EOS
- 10_0000.times do
- h.rehash rescue nil
- end
- GC.start
- EOS
-
- assert_no_memory_leak([], prepare, code, bug9187)
- end
-
- def test_wrapper
- bug9381 = '[ruby-core:59638] [Bug #9381]'
-
- wrapper = Class.new do
- def initialize(obj)
- @obj = obj
- end
-
- def hash
- @obj.hash
- end
-
- def eql?(other)
- @obj.eql?(other)
- end
- end
-
- bad = [
- 5, true, false, nil,
- 0.0, 1.72723e-77,
- :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym,
- "str",
- ].select do |x|
- hash = {x => bug9381}
- hash[wrapper.new(x)] != bug9381
- end
- assert_empty(bad, bug9381)
- end
-
- def assert_hash_random(obj, dump = obj.inspect)
- a = [obj.hash.to_s]
- 3.times {
- assert_in_out_err(["-e", "print (#{dump}).hash"], "") do |r, e|
- a += r
- assert_equal([], e)
- end
- }
- assert_not_equal([obj.hash.to_s], a.uniq)
- assert_operator(a.uniq.size, :>, 2, proc {a.inspect})
- end
-
- def test_string_hash_random
- assert_hash_random('abc')
- end
-
- def test_symbol_hash_random
- assert_hash_random(:-)
- assert_hash_random(:foo)
- assert_hash_random("dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym)
- end
-
- def test_integer_hash_random
- assert_hash_random(0)
- assert_hash_random(+1)
- assert_hash_random(-1)
- assert_hash_random(+(1<<100))
- assert_hash_random(-(1<<100))
- end
-
- def test_float_hash_random
- assert_hash_random(0.0)
- assert_hash_random(+1.0)
- assert_hash_random(-1.0)
- assert_hash_random(1.72723e-77)
- assert_hash_random(Float::INFINITY, "Float::INFINITY")
- end
-
- def test_label_syntax
- return unless @cls == Hash
-
- feature4935 = '[ruby-core:37553] [Feature #4935]'
- x = 'world'
- hash = assert_nothing_raised(SyntaxError, feature4935) do
- break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}}))
- end
- assert_equal({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash, feature4935)
- x = x
- end
-
def test_dig
h = @cls[a: @cls[b: [1, 2, 3]], c: 4]
assert_equal(1, h.dig(:a, :b, 0))
@@ -1831,12 +1634,12 @@ class TestHash < Test::Unit::TestCase
def o.respond_to?(*args)
super
end
- assert_raise(TypeError, bug12030) {{foo: o}.dig(:foo, :foo)}
+ assert_raise(TypeError, bug12030) {@cls[foo: o].dig(:foo, :foo)}
end
def test_cmp
- h1 = {a:1, b:2}
- h2 = {a:1, b:2, c:3}
+ h1 = @cls[a:1, b:2]
+ h2 = @cls[a:1, b:2, c:3]
assert_operator(h1, :<=, h1)
assert_operator(h1, :<=, h2)
@@ -1860,8 +1663,8 @@ class TestHash < Test::Unit::TestCase
end
def test_cmp_samekeys
- h1 = {a:1}
- h2 = {a:2}
+ h1 = @cls[a:1]
+ h2 = @cls[a:2]
assert_operator(h1, :<=, h1)
assert_not_operator(h1, :<=, h2)
@@ -1885,15 +1688,15 @@ class TestHash < Test::Unit::TestCase
end
def test_to_proc
- h = {
+ h = @cls[
1 => 10,
2 => 20,
3 => 30,
- }
+ ]
assert_equal([10, 20, 30], [1, 2, 3].map(&h))
- assert_equal(true, h.to_proc.lambda?)
+ assert_predicate(h.to_proc, :lambda?)
end
def test_transform_keys
@@ -2023,22 +1826,6 @@ class TestHash < Test::Unit::TestCase
assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x)
end
- def test_broken_hash_value
- bug14218 = '[ruby-core:84395] [Bug #14218]'
-
- assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; a < 0 && b < 0 && a + b > 0}, bug14218)
- assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; 0 + a + b != 0 + b + a}, bug14218)
- end
-
- def test_reserved_hash_val
- s = Struct.new(:hash)
- h = {}
- keys = [*0..8]
- keys.each {|i| h[s.new(i)]=true}
- msg = proc {h.inspect}
- assert_equal(keys, h.keys.map(&:hash), msg)
- end
-
def hrec h, n, &b
if n > 0
h.each{hrec(h, n-1, &b)}
@@ -2068,6 +1855,27 @@ class TestHash < Test::Unit::TestCase
# ignore
end
+ # Previously this test would fail because rb_hash inside opt_aref would look
+ # at the current method name
+ def test_hash_recursion_independent_of_mid
+ o = Class.new do
+ def hash(h, k)
+ h[k]
+ end
+
+ def any_other_name(h, k)
+ h[k]
+ end
+ end.new
+
+ rec = []; rec << rec
+
+ h = @cls[]
+ h[rec] = 1
+ assert o.hash(h, rec)
+ assert o.any_other_name(h, rec)
+ end
+
class TestSubHash < TestHash
class SubHash < Hash
end
@@ -2077,6 +1885,341 @@ class TestHash < Test::Unit::TestCase
super
end
end
+end
+
+class TestHashOnly < Test::Unit::TestCase
+ def test_bad_initialize_copy
+ h = Class.new(Hash) {
+ def initialize_copy(h)
+ super(Object.new)
+ end
+ }.new
+ assert_raise(TypeError) { h.dup }
+ end
+
+ def test_dup_will_not_rehash
+ assert_hash_does_not_rehash(&:dup)
+ end
+
+ def assert_hash_does_not_rehash
+ obj = Object.new
+ class << obj
+ attr_accessor :hash_calls
+ def hash
+ @hash_calls += 1
+ super
+ end
+ end
+ obj.hash_calls = 0
+ hash = {obj => 42}
+ assert_equal(1, obj.hash_calls)
+ yield hash
+ assert_equal(1, obj.hash_calls)
+ end
+
+ def test_select_reject_will_not_rehash
+ assert_hash_does_not_rehash do |hash|
+ hash.select { true }
+ end
+ assert_hash_does_not_rehash do |hash|
+ hash.reject { false }
+ end
+ end
+
+ def test_st_literal_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true)
+ begin;
+ 1_000_000.times do
+ # >8 element hashes are ST allocated rather than AR allocated
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9}
+ end
+ end;
+ end
+
+ def test_compare_by_id_memory_leak
+ assert_no_memory_leak([], "", <<~RUBY, rss: true)
+ 1_000_000.times do
+ {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8}.compare_by_identity
+ end
+ RUBY
+ end
+
+ def test_try_convert
+ assert_equal({1=>2}, Hash.try_convert({1=>2}))
+ assert_equal(nil, Hash.try_convert("1=>2"))
+ o = Object.new
+ def o.to_hash; {3=>4} end
+ assert_equal({3=>4}, Hash.try_convert(o))
+ end
+
+ def test_AREF_fstring_key
+ # warmup ObjectSpace.count_objects
+ ObjectSpace.count_objects
+
+ h = {"abc" => 1}
+ before = ObjectSpace.count_objects[:T_STRING]
+ 5.times{ h["abc"] }
+ assert_equal before, ObjectSpace.count_objects[:T_STRING]
+ end
+
+ def test_AREF_fstring_key_default_proc
+ assert_separately(['--disable-frozen-string-literal'], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ h = Hash.new do |h, k|
+ k.frozen?
+ end
+
+ str = "foo"
+ refute str.frozen?
+ refute h[str]
+ refute h["foo"]
+ end;
+ end
+
+ def test_ASET_fstring_key
+ a, b = {}, {}
+ assert_equal 1, a["abc"] = 1
+ assert_equal 1, b["abc"] = 1
+ assert_same a.keys[0], b.keys[0]
+ end
+
+ def test_ASET_fstring_non_literal_key
+ underscore = "_"
+ non_literal_strings = Proc.new{ ["abc#{underscore}def", "abc" * 5, "abc" + "def", "" << "ghi" << "jkl"] }
+
+ a, b = {}, {}
+ non_literal_strings.call.each do |string|
+ assert_equal 1, a[string] = 1
+ end
+
+ non_literal_strings.call.each do |string|
+ assert_equal 1, b[string] = 1
+ end
+
+ [a.keys, b.keys].transpose.each do |key_a, key_b|
+ assert_same key_a, key_b
+ end
+ end
+
+ def test_hash_aset_fstring_identity
+ h = {}.compare_by_identity
+ h['abc'] = 1
+ h['abc'] = 2
+ assert_equal 2, h.size, '[ruby-core:78783] [Bug #12855]'
+ end
+
+ def test_hash_aref_fstring_identity
+ h = {}.compare_by_identity
+ h['abc'] = 1
+ assert_nil h['abc'], '[ruby-core:78783] [Bug #12855]'
+ end
+
+ def test_NEWHASH_fstring_key
+ a = {"ABC" => :t}
+ b = {"ABC" => :t}
+ assert_same a.keys[0], b.keys[0]
+ assert_same "ABC".freeze, a.keys[0]
+ var = +'ABC'
+ c = { var => :t }
+ assert_same "ABC".freeze, c.keys[0]
+ end
+
+ def test_rehash_memory_leak
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ ar_hash = 1.times.map { |i| [i, i] }.to_h
+ st_hash = 10.times.map { |i| [i, i] }.to_h
+
+ code = proc do
+ ar_hash.rehash
+ st_hash.rehash
+ end
+ 1_000.times(&code)
+ PREP
+ 1_000_000.times(&code)
+ CODE
+ end
+
+ def test_replace_bug15358
+ h1 = {}
+ h2 = {a:1,b:2,c:3,d:4,e:5}
+ h2.replace(h1)
+ GC.start
+ assert(true)
+ end
+
+ def test_replace_st_with_ar
+ # ST hash
+ h1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 }
+ # AR hash
+ h2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 }
+ # Replace ST hash with AR hash
+ h1.replace(h2)
+ assert_equal(h2, h1)
+ end
+
+ def test_nil_to_h
+ h = nil.to_h
+ assert_equal({}, h)
+ assert_nil(h.default)
+ assert_nil(h.default_proc)
+ end
+
+ def test_initialize_wrong_arguments
+ assert_raise(ArgumentError) do
+ Hash.new(0) { }
+ end
+ end
+
+ def test_replace_memory_leak
+ assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ h = ("aa".."zz").each_with_index.to_h
+ 10_000.times {h.dup}
+ begin;
+ 500_000.times {h.dup.replace(h)}
+ end;
+ end
+
+ def hash_iter_recursion(h, level)
+ return if level == 0
+ h.each_key {}
+ h.each_value { hash_iter_recursion(h, level - 1) }
+ end
+
+ def test_iterlevel_in_ivar_bug19589
+ h = { a: nil }
+ hash_iter_recursion(h, 200)
+ assert true
+ end
+
+ def test_exception_in_rehash_memory_leak
+ bug9187 = '[ruby-core:58728] [Bug #9187]'
+
+ prepare = <<-EOS
+ class Foo
+ def initialize
+ @raise = false
+ end
+
+ def hash
+ raise if @raise
+ @raise = true
+ return 0
+ end
+ end
+ h = {Foo.new => true}
+ EOS
+
+ code = <<-EOS
+ 10_0000.times do
+ h.rehash rescue nil
+ end
+ GC.start
+ EOS
+
+ assert_no_memory_leak([], prepare, code, bug9187)
+ end
+
+ def test_memory_size_after_delete
+ require 'objspace'
+ h = {}
+ 1000.times {|i| h[i] = true}
+ big = ObjectSpace.memsize_of(h)
+ 1000.times {|i| h.delete(i)}
+ assert_operator ObjectSpace.memsize_of(h), :<, big/10
+ end
+
+ def test_wrapper
+ bug9381 = '[ruby-core:59638] [Bug #9381]'
+
+ wrapper = Class.new do
+ def initialize(obj)
+ @obj = obj
+ end
+
+ def hash
+ @obj.hash
+ end
+
+ def eql?(other)
+ @obj.eql?(other)
+ end
+ end
+
+ bad = [
+ 5, true, false, nil,
+ 0.0, 1.72723e-77,
+ :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym,
+ "str",
+ ].select do |x|
+ hash = {x => bug9381}
+ hash[wrapper.new(x)] != bug9381
+ end
+ assert_empty(bad, bug9381)
+ end
+
+ def assert_hash_random(obj, dump = obj.inspect)
+ a = [obj.hash.to_s]
+ 3.times {
+ assert_in_out_err(["-e", "print (#{dump}).hash"], "") do |r, e|
+ a += r
+ assert_equal([], e)
+ end
+ }
+ assert_not_equal([obj.hash.to_s], a.uniq)
+ assert_operator(a.uniq.size, :>, 2, proc {a.inspect})
+ end
+
+ def test_string_hash_random
+ assert_hash_random('abc')
+ end
+
+ def test_symbol_hash_random
+ assert_hash_random(:-)
+ assert_hash_random(:foo)
+ assert_hash_random("dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym)
+ end
+
+ def test_integer_hash_random
+ assert_hash_random(0)
+ assert_hash_random(+1)
+ assert_hash_random(-1)
+ assert_hash_random(+(1<<100))
+ assert_hash_random(-(1<<100))
+ end
+
+ def test_float_hash_random
+ assert_hash_random(0.0)
+ assert_hash_random(+1.0)
+ assert_hash_random(-1.0)
+ assert_hash_random(1.72723e-77)
+ assert_hash_random(Float::INFINITY, "Float::INFINITY")
+ end
+
+ def test_label_syntax
+ feature4935 = '[ruby-core:37553] [Feature #4935]'
+ x = 'world'
+ hash = assert_nothing_raised(SyntaxError, feature4935) do
+ break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}}))
+ end
+ assert_equal({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash, feature4935)
+ x = x
+ end
+
+ def test_broken_hash_value
+ bug14218 = '[ruby-core:84395] [Bug #14218]'
+
+ assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; a < 0 && b < 0 && a + b > 0}, bug14218)
+ assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; 0 + a + b != 0 + b + a}, bug14218)
+ end
+
+ def test_reserved_hash_val
+ s = Struct.new(:hash)
+ h = {}
+ keys = [*0..8]
+ keys.each {|i| h[s.new(i)]=true}
+ msg = proc {h.inspect}
+ assert_equal(keys, h.keys.map(&:hash), msg)
+ end
ruby2_keywords def get_flagged_hash(*args)
args.last
@@ -2102,23 +2245,11 @@ class TestHash < Test::Unit::TestCase
assert_raise(TypeError) { Hash.ruby2_keywords_hash(1) }
end
- def test_ar2st
- # insert
- obj = Object.new
- obj.instance_variable_set(:@h, h = {})
- def obj.hash
- 10.times{|i| @h[i] = i}
- 0
+ def ar2st_object
+ class << (obj = Object.new)
+ attr_reader :h
end
- def obj.inspect
- 'test'
- end
- h[obj] = true
- assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, test=>true}', h.inspect
-
- # delete
- obj = Object.new
- obj.instance_variable_set(:@h, h = {})
+ obj.instance_variable_set(:@h, {})
def obj.hash
10.times{|i| @h[i] = i}
0
@@ -2129,6 +2260,21 @@ class TestHash < Test::Unit::TestCase
def obj.eql? other
other.class == Object
end
+ obj
+ end
+
+ def test_ar2st_insert
+ obj = ar2st_object
+ h = obj.h
+
+ h[obj] = true
+ assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, test=>true}', h.inspect
+ end
+
+ def test_ar2st_delete
+ obj = ar2st_object
+ h = obj.h
+
obj2 = Object.new
def obj2.hash
0
@@ -2137,20 +2283,12 @@ class TestHash < Test::Unit::TestCase
h[obj2] = true
h.delete obj
assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9}', h.inspect
+ end
+
+ def test_ar2st_lookup
+ obj = ar2st_object
+ h = obj.h
- # lookup
- obj = Object.new
- obj.instance_variable_set(:@h, h = {})
- def obj.hash
- 10.times{|i| @h[i] = i}
- 0
- end
- def obj.inspect
- 'test'
- end
- def obj.eql? other
- other.class == Object
- end
obj2 = Object.new
def obj2.hash
0
@@ -2191,4 +2329,35 @@ class TestHash < Test::Unit::TestCase
end;
end
end
+
+ def test_compare_by_identity_during_iteration
+ h = { 1 => 1 }
+ h.each do
+ assert_raise(RuntimeError, "compare_by_identity during iteration") do
+ h.compare_by_identity
+ end
+ end
+ end
+
+ def test_ar_hash_to_st_hash
+ assert_normal_exit("#{<<~"begin;"}\n#{<<~'end;'}", 'https://bugs.ruby-lang.org/issues/20050#note-5')
+ begin;
+ srand(0)
+ class Foo
+ def to_a
+ []
+ end
+
+ def hash
+ $h.delete($h.keys.sample) if rand < 0.1
+ to_a.hash
+ end
+ end
+
+ 1000.times do
+ $h = {}
+ (0..10).each {|i| $h[Foo.new] ||= {} }
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index a2b181c642..3349a1c493 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -2,16 +2,9 @@
require 'test/unit'
class TestInteger < Test::Unit::TestCase
- BDSIZE = 0x4000000000000000.coerce(0)[0].size
- def self.bdsize(x)
- ((x + 1) / 8 + BDSIZE) / BDSIZE * BDSIZE
- end
- def bdsize(x)
- self.class.bdsize(x)
- end
-
FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX']
+ LONG_MAX = RbConfig::LIMITS['LONG_MAX']
def test_aref
@@ -96,11 +89,16 @@ class TestInteger < Test::Unit::TestCase
assert_equal(0, 1 << -0x40000001)
assert_equal(0, 1 << -0x80000000)
assert_equal(0, 1 << -0x80000001)
- # assert_equal(bdsize(0x80000000), (1 << 0x80000000).size)
+
+ char_bit = RbConfig::LIMITS["UCHAR_MAX"].bit_length
+ size_max = RbConfig::LIMITS["SIZE_MAX"]
+ size_bit_max = size_max * char_bit
+ assert_raise_with_message(RangeError, /shift width/) {
+ 1 << size_bit_max
+ }
end
def test_rshift
- # assert_equal(bdsize(0x40000001), (1 >> -0x40000001).size)
assert_predicate((1 >> 0x80000000), :zero?)
assert_predicate((1 >> 0xffffffff), :zero?)
assert_predicate((1 >> 0x100000000), :zero?)
@@ -140,20 +138,6 @@ class TestInteger < Test::Unit::TestCase
assert_equal(1234, Integer(1234))
assert_equal(1, Integer(1.234))
- # base argument
- assert_equal(1234, Integer("1234", 10))
- assert_equal(668, Integer("1234", 8))
- assert_equal(4660, Integer("1234", 16))
- assert_equal(49360, Integer("1234", 36))
- # decimal, not octal
- assert_equal(1234, Integer("01234", 10))
- assert_raise(ArgumentError) { Integer("0x123", 10) }
- assert_raise(ArgumentError) { Integer(1234, 10) }
- assert_raise(ArgumentError) { Integer(12.34, 10) }
- assert_raise(ArgumentError) { Integer(Object.new, 1) }
-
- assert_raise(ArgumentError) { Integer(1, 1, 1) }
-
assert_equal(2 ** 50, Integer(2.0 ** 50))
assert_raise(TypeError) { Integer(nil) }
@@ -247,6 +231,39 @@ class TestInteger < Test::Unit::TestCase
end;
end
+ def test_Integer_when_to_str
+ def (obj = Object.new).to_str
+ "0x10"
+ end
+ assert_equal(16, Integer(obj))
+ end
+
+ def test_Integer_with_base
+ assert_equal(1234, Integer("1234", 10))
+ assert_equal(668, Integer("1234", 8))
+ assert_equal(4660, Integer("1234", 16))
+ assert_equal(49360, Integer("1234", 36))
+ # decimal, not octal
+ assert_equal(1234, Integer("01234", 10))
+ assert_raise(ArgumentError) { Integer("0x123", 10) }
+ assert_raise(ArgumentError) { Integer(1234, 10) }
+ assert_raise(ArgumentError) { Integer(12.34, 10) }
+ assert_raise(ArgumentError) { Integer(Object.new, 1) }
+
+ assert_raise(ArgumentError) { Integer(1, 1, 1) }
+
+ def (base = Object.new).to_int
+ 8
+ end
+ assert_equal(8, Integer("10", base))
+
+ assert_raise(TypeError) { Integer("10", "8") }
+ def (base = Object.new).to_int
+ "8"
+ end
+ assert_raise(TypeError) { Integer("10", base) }
+ end
+
def test_int_p
assert_not_predicate(1.0, :integer?)
assert_predicate(1, :integer?)
@@ -304,23 +321,34 @@ class TestInteger < Test::Unit::TestCase
begin;
called = false
Integer.class_eval do
- alias old_plus +
- undef +
- define_method(:+){|x| called = true; 1}
+ alias old_succ succ
+ undef succ
+ define_method(:succ){|x| called = true; x+1}
alias old_lt <
undef <
define_method(:<){|x| called = true}
end
+
+ fix = 1
+ fix.times{break 0}
+ fix_called = called
+
+ called = false
+
big = 2**65
big.times{break 0}
+ big_called = called
+
Integer.class_eval do
- undef +
- alias + old_plus
+ undef succ
+ alias succ old_succ
undef <
alias < old_lt
end
+
+ # Asssert that Fixnum and Bignum behave consistently
bug18377 = "[ruby-core:106361]"
- assert_equal(false, called, bug18377)
+ assert_equal(fix_called, big_called, bug18377)
end;
end
@@ -676,6 +704,14 @@ class TestInteger < Test::Unit::TestCase
def test_fdiv
assert_equal(1.0, 1.fdiv(1))
assert_equal(0.5, 1.fdiv(2))
+
+ m = 50 << Float::MANT_DIG
+ prev = 1.0
+ (1..100).each do |i|
+ val = (m + i).fdiv(m)
+ assert_operator val, :>=, prev, "1+epsilon*(#{i}/100)"
+ prev = val
+ end
end
def test_obj_fdiv
@@ -704,4 +740,25 @@ class TestInteger < Test::Unit::TestCase
def o.to_int; Object.new; end
assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)}
end
+
+ def test_ceildiv
+ assert_equal(0, 0.ceildiv(3))
+ assert_equal(1, 1.ceildiv(3))
+ assert_equal(1, 3.ceildiv(3))
+ assert_equal(2, 4.ceildiv(3))
+
+ assert_equal(-1, 4.ceildiv(-3))
+ assert_equal(-1, -4.ceildiv(3))
+ assert_equal(2, -4.ceildiv(-3))
+
+ assert_equal(3, 3.ceildiv(1.2))
+ assert_equal(3, 3.ceildiv(6/5r))
+
+ assert_equal(10, (10**100-11).ceildiv(10**99-1))
+ assert_equal(11, (10**100-9).ceildiv(10**99-1))
+
+ o = Object.new
+ def o.coerce(other); [other, 10]; end
+ assert_equal(124, 1234.ceildiv(o))
+ end
end
diff --git a/test/ruby/test_integer_comb.rb b/test/ruby/test_integer_comb.rb
index 1ad13dd31b..150f45cfd7 100644
--- a/test/ruby/test_integer_comb.rb
+++ b/test/ruby/test_integer_comb.rb
@@ -408,19 +408,32 @@ class TestIntegerComb < Test::Unit::TestCase
end
def test_remainder
+ coerce = EnvUtil.labeled_class("CoerceNum") do
+ def initialize(num)
+ @num = num
+ end
+ def coerce(other)
+ [other, @num]
+ end
+ def inspect
+ "#{self.class.name}(#@num)"
+ end
+ alias to_s inspect
+ end
+
VS.each {|a|
- VS.each {|b|
- if b == 0
+ (VS + VS.map {|b| [coerce.new(b), b]}).each {|b, i = b|
+ if i == 0
assert_raise(ZeroDivisionError) { a.divmod(b) }
else
- r = a.remainder(b)
+ r = assert_nothing_raised(ArgumentError, "#{a}.remainder(#{b})") {a.remainder(b)}
assert_kind_of(Integer, r)
if a < 0
- assert_operator(-b.abs, :<, r, "#{a}.remainder(#{b})")
+ assert_operator(-i.abs, :<, r, "#{a}.remainder(#{b})")
assert_operator(0, :>=, r, "#{a}.remainder(#{b})")
elsif 0 < a
assert_operator(0, :<=, r, "#{a}.remainder(#{b})")
- assert_operator(b.abs, :>, r, "#{a}.remainder(#{b})")
+ assert_operator(i.abs, :>, r, "#{a}.remainder(#{b})")
else
assert_equal(0, r, "#{a}.remainder(#{b})")
end
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index ed0198321d..476d9f882f 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -312,7 +312,7 @@ class TestIO < Test::Unit::TestCase
w.print "a\n\nb\n\n"
w.close
end, proc do |r|
- assert_equal "a\n\nb\n", r.gets(nil, chomp: true)
+ assert_equal("a\n\nb\n\n", r.gets(nil, chomp: true), "[Bug #18770]")
assert_nil r.gets("")
r.close
end)
@@ -655,7 +655,7 @@ class TestIO < Test::Unit::TestCase
if have_nonblock?
def test_copy_stream_no_busy_wait
- omit "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ omit "RJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
omit "multiple threads already active" if Thread.list.size > 1
msg = 'r58534 [ruby-core:80969] [Backport #13533]'
@@ -900,6 +900,10 @@ class TestIO < Test::Unit::TestCase
end if defined? UNIXSocket
def test_copy_stream_socket4
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(0, f.pos)
@@ -916,9 +920,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket5
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(bigcontent[0,100], f.read(100))
@@ -936,9 +944,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket6
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
mkcdtmpdir {
megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent }
@@ -959,9 +971,13 @@ class TestIO < Test::Unit::TestCase
assert_equal(megacontent, result)
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket7
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
GC.start
mkcdtmpdir {
megacontent = "abc" * 1234567
@@ -996,7 +1012,7 @@ class TestIO < Test::Unit::TestCase
end
}
}
- end if defined? UNIXSocket and IO.method_defined?("nonblock=")
+ end
def test_copy_stream_strio
src = StringIO.new("abcd")
@@ -1441,6 +1457,16 @@ class TestIO < Test::Unit::TestCase
End
end
+ def test_dup_timeout
+ with_pipe do |r, w|
+ r.timeout = 0.1
+ r2 = r.dup
+ assert_equal(0.1, r2.timeout)
+ ensure
+ r2&.close
+ end
+ end
+
def test_inspect
with_pipe do |r, w|
assert_match(/^#<IO:fd \d+>$/, r.inspect)
@@ -1597,6 +1623,28 @@ class TestIO < Test::Unit::TestCase
end
end
+ def test_read_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'r') do |file|
+ file.read_nonblock(4)
+ end
+ end
+ end
+
+ def test_write_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'w') do |file|
+ file.write_nonblock("Ruby")
+ end
+ end
+ end
+
+ def test_explicit_path
+ io = IO.for_fd(0, path: "Fake Path", autoclose: false)
+ assert_match %r"Fake Path", io.inspect
+ assert_equal "Fake Path", io.path
+ end
+
def test_write_nonblock_simple_no_exceptions
pipe(proc do |w|
w.write_nonblock('1', exception: false)
@@ -1631,7 +1679,7 @@ class TestIO < Test::Unit::TestCase
end if have_nonblock?
def test_read_nonblock_no_exceptions
- omit '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # TODO: consider acquiring GVL from MJIT worker.
+ omit '[ruby-core:90895] RJIT worker may leave fd open in a forked child' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # TODO: consider acquiring GVL from RJIT worker.
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, exception: false)
w.puts "HI!"
@@ -1850,6 +1898,110 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_readline_bad_param_raises
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline Object.new
+ end
+ end
+
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline 1, 2
+ end
+ end
+ end
+
+ def test_readline_raises
+ File.open(__FILE__) do |f|
+ assert_equal File.read(__FILE__), f.readline(nil)
+ assert_raise(EOFError) do
+ f.readline
+ end
+ end
+ end
+
+ def test_readline_separators
+ File.open(__FILE__) do |f|
+ line = f.readline("def")
+ assert_equal File.read(__FILE__)[/\A.*?def/m], line
+ end
+
+ File.open(__FILE__) do |f|
+ line = f.readline("def", chomp: true)
+ assert_equal File.read(__FILE__)[/\A.*?(?=def)/m], line
+ end
+ end
+
+ def test_readline_separators_limits
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "def"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline sep, str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_limit_without_separator
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "\n"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_chomp_true
+ File.open(__FILE__) do |f|
+ line = f.readline(chomp: true)
+ assert_equal File.readlines(__FILE__).first.chomp, line
+ end
+ end
+
def test_set_lineno_readline
pipe(proc do |w|
w.puts "foo"
@@ -1894,6 +2046,20 @@ class TestIO < Test::Unit::TestCase
assert_equal("baz\n", e.next)
assert_raise(StopIteration) { e.next }
end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]")
+ end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]")
+ end)
end
def test_each_byte2
@@ -2199,6 +2365,14 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_sysread_with_negative_length
+ make_tempfile {|t|
+ open(t.path) do |f|
+ assert_raise(ArgumentError) { f.sysread(-1) }
+ end
+ }
+ end
+
def test_flag
make_tempfile {|t|
assert_raise(ArgumentError) do
@@ -2277,9 +2451,9 @@ class TestIO < Test::Unit::TestCase
end
def test_autoclose_true_closed_by_finalizer
- # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1465760
- # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1469765
- omit 'this randomly fails with MJIT' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1465760
+ # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1469765
+ omit 'this randomly fails with RJIT' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
feature2250 = '[ruby-core:26222]'
pre = 'ft2250'
@@ -2342,15 +2516,19 @@ class TestIO < Test::Unit::TestCase
end
def test_open_pipe
- open("|" + EnvUtil.rubybin, "r+") do |f|
- f.puts "puts 'foo'"
- f.close_write
- assert_equal("foo\n", f.read)
+ assert_deprecated_warning(/Kernel#open with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
+ open("|" + EnvUtil.rubybin, "r+") do |f|
+ f.puts "puts 'foo'"
+ f.close_write
+ assert_equal("foo\n", f.read)
+ end
end
end
def test_read_command
- assert_equal("foo\n", IO.read("|echo foo"))
+ assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
+ assert_equal("foo\n", IO.read("|echo foo"))
+ end
assert_raise(Errno::ENOENT, Errno::EINVAL) do
File.read("|#{EnvUtil.rubybin} -e puts")
end
@@ -2364,7 +2542,9 @@ class TestIO < Test::Unit::TestCase
Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts")
end
assert_raise(Errno::ESPIPE) do
- IO.read("|echo foo", 1, 1)
+ assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
+ IO.read("|echo foo", 1, 1)
+ end
end
end
@@ -2549,11 +2729,16 @@ class TestIO < Test::Unit::TestCase
def test_foreach
a = []
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x }
+
+ assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
+ IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x }
+ end
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x }
+ assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
+ IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x }
+ end
assert_equal(["zot\n"], a)
make_tempfile {|t|
@@ -2588,6 +2773,8 @@ class TestIO < Test::Unit::TestCase
bug = '[ruby-dev:31525]'
assert_raise(ArgumentError, bug) {IO.foreach}
+ assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}}
+
a = nil
assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a}
assert_equal(["foo\n", "bar\n", "baz\n"], a, bug)
@@ -2596,6 +2783,8 @@ class TestIO < Test::Unit::TestCase
assert_raise_with_message(IOError, /not opened for reading/, bug6054) do
IO.foreach(t.path, mode:"w").next
end
+
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}}
}
end
@@ -2605,6 +2794,7 @@ class TestIO < Test::Unit::TestCase
assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b"))
assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2))
assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2))
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}}
}
end
@@ -2791,6 +2981,9 @@ class TestIO < Test::Unit::TestCase
assert_equal("foo\nbar\nbaz\n", File.read(t.path))
assert_equal("foo\nba", File.read(t.path, 6))
assert_equal("bar\n", File.read(t.path, 4, 4))
+
+ assert_raise(ArgumentError) { File.read(t.path, -1) }
+ assert_raise(ArgumentError) { File.read(t.path, 1, -1) }
}
end
@@ -3097,6 +3290,8 @@ __END__
end
def test_cross_thread_close_stdio
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], <<-'end;')
IO.pipe do |r,w|
$stdin.reopen(r)
@@ -3734,7 +3929,7 @@ __END__
end
def test_race_gets_and_close
- opt = { signal: :ABRT, timeout: 200 }
+ opt = { signal: :ABRT, timeout: 10 }
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", **opt)
bug13076 = '[ruby-core:78845] [Bug #13076]'
begin;
@@ -3765,6 +3960,8 @@ __END__
end
def test_race_closed_stream
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
bug13158 = '[ruby-core:79262] [Bug #13158]'
@@ -3859,6 +4056,8 @@ __END__
end
def test_closed_stream_in_rescue
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
10.times do
@@ -3892,7 +4091,7 @@ __END__
assert_raise(EOFError) { f.pread(1, f.size) }
end
}
- end if IO.method_defined?(:pread)
+ end
def test_pwrite
make_tempfile { |t|
@@ -3901,7 +4100,7 @@ __END__
assert_equal("ooo", f.pread(3, 4))
end
}
- end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite)
+ end
def test_select_exceptfds
if Etc.uname[:sysname] == 'SunOS'
@@ -3927,6 +4126,9 @@ __END__
noex = Thread.new do # everything right and never see exceptions :)
until sig_rd.wait_readable(0)
IO.pipe do |r, w|
+ assert_nil r.timeout
+ assert_nil w.timeout
+
th = Thread.new { r.read(1) }
w.write(dot)
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 0f8a9c5e80..7a58ec0c5a 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -80,7 +80,7 @@ class TestIOBuffer < Test::Unit::TestCase
end
def test_file_mapped_invalid
- assert_raise NoMethodError do
+ assert_raise TypeError do
IO::Buffer.map("foobar")
end
end
@@ -102,11 +102,6 @@ class TestIOBuffer < Test::Unit::TestCase
IO::Buffer.for(string) do |buffer|
refute buffer.readonly?
- # Cannot modify string as it's locked by the buffer:
- assert_raise RuntimeError do
- string[0] = "h"
- end
-
buffer.set_value(:U8, 0, "h".ord)
# Buffer releases it's ownership of the string:
@@ -116,6 +111,16 @@ class TestIOBuffer < Test::Unit::TestCase
end
end
+ def test_string_mapped_buffer_locked
+ string = "Hello World"
+ IO::Buffer.for(string) do |buffer|
+ # Cannot modify string as it's locked by the buffer:
+ assert_raise RuntimeError do
+ string[0] = "h"
+ end
+ end
+ end
+
def test_non_string
not_string = Object.new
@@ -124,6 +129,20 @@ class TestIOBuffer < Test::Unit::TestCase
end
end
+ def test_string
+ result = IO::Buffer.string(12) do |buffer|
+ buffer.set_string("Hello World!")
+ end
+
+ assert_equal "Hello World!", result
+ end
+
+ def test_string_negative
+ assert_raise ArgumentError do
+ IO::Buffer.string(-1)
+ end
+ end
+
def test_resize_mapped
buffer = IO::Buffer.new
@@ -142,6 +161,24 @@ class TestIOBuffer < Test::Unit::TestCase
assert_equal message, buffer.get_string(0, message.bytesize)
end
+ def test_resize_zero_internal
+ buffer = IO::Buffer.new(1)
+
+ buffer.resize(0)
+ assert_equal 0, buffer.size
+
+ buffer.resize(1)
+ assert_equal 1, buffer.size
+ end
+
+ def test_resize_zero_external
+ buffer = IO::Buffer.for('1')
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.resize(0)
+ end
+ end
+
def test_compare_same_size
buffer1 = IO::Buffer.new(1)
assert_equal buffer1, buffer1
@@ -162,6 +199,14 @@ class TestIOBuffer < Test::Unit::TestCase
assert_positive buffer2 <=> buffer1
end
+ def test_compare_zero_length
+ buffer1 = IO::Buffer.new(0)
+ buffer2 = IO::Buffer.new(1)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
def test_slice
buffer = IO::Buffer.new(128)
slice = buffer.slice(8, 32)
@@ -169,16 +214,26 @@ class TestIOBuffer < Test::Unit::TestCase
assert_equal("Hello World", buffer.get_string(8, 11))
end
- def test_slice_bounds
+ def test_slice_arguments
+ buffer = IO::Buffer.for("Hello World")
+
+ slice = buffer.slice
+ assert_equal "Hello World", slice.get_string
+
+ slice = buffer.slice(2)
+ assert_equal("llo World", slice.get_string)
+ end
+
+ def test_slice_bounds_error
buffer = IO::Buffer.new(128)
assert_raise ArgumentError do
buffer.slice(128, 10)
end
- # assert_raise RuntimeError do
- # pp buffer.slice(-10, 10)
- # end
+ assert_raise ArgumentError do
+ buffer.slice(-10, 10)
+ end
end
def test_locked
@@ -209,6 +264,26 @@ class TestIOBuffer < Test::Unit::TestCase
chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY)
assert_equal Encoding::BINARY, chunk.encoding
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(0, 129)
+ end
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(129)
+ end
+
+ assert_raise_with_message(ArgumentError, /Offset can't be negative/) do
+ buffer.get_string(-1)
+ end
+ end
+
+ def test_zero_length_get_string
+ buffer = IO::Buffer.new.slice(0, 0)
+ assert_equal "", buffer.get_string
+
+ buffer = IO::Buffer.new(0)
+ assert_equal "", buffer.get_string
end
# We check that values are correctly round tripped.
@@ -235,17 +310,78 @@ class TestIOBuffer < Test::Unit::TestCase
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
}
- def test_get_set_primitives
+ def test_get_set_value
buffer = IO::Buffer.new(128)
- RANGES.each do |type, values|
+ RANGES.each do |data_type, values|
values.each do |value|
- buffer.set_value(type, 0, value)
- assert_equal value, buffer.get_value(type, 0), "Converting #{value} as #{type}."
+ buffer.set_value(data_type, 0, value)
+ assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
end
end
end
+ def test_get_set_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}."
+ end
+ end
+
+ def test_zero_length_get_set_values
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.get_values([], 0)
+ assert_equal 0, buffer.set_values([], 0, [])
+ end
+
+ def test_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}."
+ end
+ end
+
+ def test_each
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+ data_type_size = IO::Buffer.size_of(data_type)
+ values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]}
+
+ buffer.set_values(format, 0, values)
+ assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}."
+ end
+ end
+
+ def test_zero_length_each
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.each(:U8).to_a
+ end
+
+ def test_each_byte
+ string = "The quick brown fox jumped over the lazy dog."
+ buffer = IO::Buffer.for(string)
+
+ assert_equal string.bytes, buffer.each_byte.to_a
+ end
+
+ def test_zero_length_each_byte
+ buffer = IO::Buffer.new(0)
+
+ assert_equal [], buffer.each_byte.to_a
+ end
+
def test_clear
buffer = IO::Buffer.new(16)
buffer.set_string("Hello World!")
@@ -277,17 +413,38 @@ class TestIOBuffer < Test::Unit::TestCase
input.close
end
- def test_read
+ def hello_world_tempfile
io = Tempfile.new
io.write("Hello World")
io.seek(0)
- buffer = IO::Buffer.new(128)
- buffer.read(io, 5)
-
- assert_equal "Hello", buffer.get_string(0, 5)
+ yield io
ensure
- io.close!
+ io&.close!
+ end
+
+ def test_read
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_length
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, 5)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_offset
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, nil, 6)
+ assert_equal "Hello", buffer.get_string(6, 5)
+ end
end
def test_write
@@ -295,7 +452,7 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.new(128)
buffer.set_string("Hello")
- buffer.write(io, 5)
+ buffer.write(io)
io.seek(0)
assert_equal "Hello", io.read(5)
@@ -309,7 +466,7 @@ class TestIOBuffer < Test::Unit::TestCase
io.seek(0)
buffer = IO::Buffer.new(128)
- buffer.pread(io, 5, 6)
+ buffer.pread(io, 6, 5)
assert_equal "World", buffer.get_string(0, 5)
assert_equal 0, io.tell
@@ -317,12 +474,41 @@ class TestIOBuffer < Test::Unit::TestCase
io.close!
end
+ def test_pread_offset
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ buffer = IO::Buffer.new(128)
+ buffer.pread(io, 6, 5, 6)
+
+ assert_equal "World", buffer.get_string(6, 5)
+ assert_equal 0, io.tell
+ ensure
+ io.close!
+ end
+
def test_pwrite
io = Tempfile.new
buffer = IO::Buffer.new(128)
buffer.set_string("World")
- buffer.pwrite(io, 5, 6)
+ buffer.pwrite(io, 6, 5)
+
+ assert_equal 0, io.tell
+
+ io.seek(6)
+ assert_equal "World", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_pwrite_offset
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("Hello World")
+ buffer.pwrite(io, 6, 5, 6)
assert_equal 0, io.tell
@@ -351,4 +537,39 @@ class TestIOBuffer < Test::Unit::TestCase
assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask)
assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
end
+
+ def test_shared
+ message = "Hello World"
+ buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED)
+
+ pid = fork do
+ buffer.set_string(message)
+ end
+
+ Process.wait(pid)
+ string = buffer.get_string(0, message.bytesize)
+ assert_equal message, string
+ rescue NotImplementedError
+ omit "Fork/shared memory is not supported."
+ end
+
+ def test_private
+ Tempfile.create(%w"buffer .txt") do |file|
+ file.write("Hello World")
+
+ buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
+ begin
+ assert buffer.private?
+ refute buffer.readonly?
+
+ buffer.set_string("J")
+
+ # It was not changed because the mapping was private:
+ file.seek(0)
+ assert_equal "Hello World", file.read
+ ensure
+ buffer&.free
+ end
+ end
+ end
end
diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb
index 27b16a2a36..b01d627d92 100644
--- a/test/ruby/test_io_m17n.rb
+++ b/test/ruby/test_io_m17n.rb
@@ -1142,12 +1142,94 @@ EOT
IO.pipe do |r, w|
assert_nothing_raised(bug5567) do
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx")}
+ w.puts("foo")
+ assert_equal("foo\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")}
+ w.puts("bar")
+ assert_equal("bar\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")}
+ w.puts("zot")
+ begin
+ assert_equal("zot\n", r.gets)
+ rescue Encoding::ConverterNotFoundError => e
+ assert_match(/\((\S+) to \1\)/, e.message)
+ end
end
end
end
+ def test_set_encoding_argument_parsing
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary')
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'))
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary:utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary', 'utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'), Encoding.find('utf-8'))
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('binary', Encoding.find('utf-8'))
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('binary'), 'utf-8')
+ assert_equal(nil, f.internal_encoding)
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1:utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1', 'utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('iso-8859-1'), Encoding.find('utf-8'))
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding('iso-8859-1', Encoding.find('utf-8'))
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+
+ File.open(File::NULL) do |f|
+ f.set_encoding(Encoding.find('iso-8859-1'), 'utf-8')
+ assert_equal(Encoding::UTF_8, f.internal_encoding)
+ assert_equal(Encoding::ISO_8859_1, f.external_encoding)
+ end
+ end
+
def test_textmode_twice
assert_raise(ArgumentError) {
open(__FILE__, "rt", textmode: true) {|f|
@@ -1314,23 +1396,27 @@ EOT
end
def test_open_pipe_r_enc
- open("|#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f|
- assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::ASCII_8BIT, s.encoding)
- assert_equal("\xff".force_encoding("ascii-8bit"), s)
- }
+ EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
+ open("|#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f|
+ assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
+ assert_equal(nil, f.internal_encoding)
+ s = f.read
+ assert_equal(Encoding::ASCII_8BIT, s.encoding)
+ assert_equal("\xff".force_encoding("ascii-8bit"), s)
+ }
+ end
end
def test_open_pipe_r_enc2
- open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f|
- assert_equal(Encoding::UTF_8, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::UTF_8, s.encoding)
- assert_equal("\u3042", s)
- }
+ EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
+ open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f|
+ assert_equal(Encoding::UTF_8, f.external_encoding)
+ assert_equal(nil, f.internal_encoding)
+ s = f.read
+ assert_equal(Encoding::UTF_8, s.encoding)
+ assert_equal("\u3042", s)
+ }
+ end
end
def test_s_foreach_enc
diff --git a/test/ruby/test_io_timeout.rb b/test/ruby/test_io_timeout.rb
new file mode 100644
index 0000000000..e017395980
--- /dev/null
+++ b/test/ruby/test_io_timeout.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: false
+
+require 'io/nonblock'
+
+class TestIOTimeout < Test::Unit::TestCase
+ def with_pipe
+ omit "UNIXSocket is not defined!" unless defined?(UNIXSocket)
+
+ begin
+ i, o = UNIXSocket.pair
+
+ yield i, o
+ ensure
+ i.close
+ o.close
+ end
+ end
+
+ def test_timeout_attribute
+ with_pipe do |i, o|
+ assert_nil i.timeout
+
+ i.timeout = 10
+ assert_equal 10, i.timeout
+ assert_nil o.timeout
+
+ o.timeout = 20
+ assert_equal 20, o.timeout
+ assert_equal 10, i.timeout
+ end
+ end
+
+ def test_timeout_read_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.read}
+ end
+ end
+
+ def test_timeout_gets_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.gets}
+ end
+ end
+
+ def test_timeout_puts
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+ o.puts("Hello World")
+ o.close
+
+ assert_equal "Hello World", i.gets.chomp
+ end
+ end
+end
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index 2a18ff02e1..d2a39e673f 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -157,18 +157,18 @@ class TestISeq < Test::Unit::TestCase
y = nil.instance_eval do
eval("proc {#{name} = []; proc {|x| #{name}}}").call
end
- assert_raise_with_message(Ractor::IsolationError, /`#{name}'/) do
+ assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
Ractor.make_shareable(y)
end
obj = Object.new
def obj.foo(*) nil.instance_eval{ ->{super} } end
- assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable `\*'/) do
+ assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
Ractor.make_shareable(obj.foo)
end
end
def test_disasm_encoding
- src = "\u{3042} = 1; \u{3042}; \u{3043}"
+ src = +"\u{3042} = 1; \u{3042}; \u{3043}"
asm = compile(src).disasm
assert_equal(src.encoding, asm.encoding)
assert_predicate(asm, :valid_encoding?)
@@ -355,12 +355,19 @@ class TestISeq < Test::Unit::TestCase
end
end
+ # [Bug #19173]
+ def test_compile_error
+ assert_raise SyntaxError do
+ RubyVM::InstructionSequence.compile 'using Module.new; yield'
+ end
+ end
+
def test_compile_file_error
Tempfile.create(%w"test_iseq .rb") do |f|
f.puts "end"
f.close
path = f.path
- assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected `end'/, [], success: true)
+ assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected 'end'/, [], success: true)
begin;
path = ARGV[0]
begin
@@ -490,7 +497,8 @@ class TestISeq < Test::Unit::TestCase
[7, :line],
[9, :return]]],
[["ensure in foo@2", [[7, :line]]]],
- [["rescue in foo@4", [[5, :line]]]]]],
+ [["rescue in foo@4", [[5, :line],
+ [5, :rescue]]]]]],
[["<class:D>@17", [[17, :class],
[18, :end]]]]], collect_iseq.call(sample_iseq)
end
@@ -558,6 +566,23 @@ class TestISeq < Test::Unit::TestCase
iseq2
end
+ def test_to_binary_with_hidden_local_variables
+ assert_iseq_to_binary("for foo in bar; end")
+
+ bin = RubyVM::InstructionSequence.compile(<<-RUBY).to_binary
+ Object.new.instance_eval do
+ a = []
+ def self.bar; [1] end
+ for foo in bar
+ a << (foo * 2)
+ end
+ a
+ end
+ RUBY
+ v = RubyVM::InstructionSequence.load_from_binary(bin).eval
+ assert_equal([2], v)
+ end
+
def test_to_binary_with_objects
assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join)
assert_iseq_to_binary("@x ||= (1..2)")
@@ -630,6 +655,8 @@ class TestISeq < Test::Unit::TestCase
}
lines
+ ensure
+ Object.send(:remove_const, :A) rescue nil
end
def test_to_binary_line_tracepoint
@@ -750,4 +777,84 @@ class TestISeq < Test::Unit::TestCase
assert_equal :new, r.take
RUBY
end
+
+ def test_ever_condition_loop
+ assert_ruby_status([], "BEGIN {exit}; while true && true; end")
+ end
+
+ def test_unreachable_syntax_error
+ mesg = /Invalid break/
+ assert_syntax_error("false and break", mesg)
+ assert_syntax_error("if false and break; end", mesg)
+ end
+
+ def test_unreachable_pattern_matching
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[1])
+ begin;
+ if true or {a: 0} in {a:}
+ p 1
+ else
+ p a
+ end
+ end;
+ end
+
+ def test_loading_kwargs_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
+ a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
+ begin;
+ 1_000_000.times do
+ RubyVM::InstructionSequence.load_from_binary(a)
+ end
+ end;
+ end
+
+ def test_ibf_bignum
+ iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5)
+ expected = iseq.eval
+ result = RubyVM::InstructionSequence.load_from_binary(iseq.to_binary).eval
+ assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)}
+ end
+
+ def test_compile_prism_with_file
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "name = 'Prism'; puts 'hello'"
+ f.close
+
+ assert_nothing_raised(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(f)
+ end
+ end
+ end
+
+ def block_using_method
+ yield
+ end
+
+ def block_unused_method
+ end
+
+ def test_unused_param
+ a = RubyVM::InstructionSequence.of(method(:block_using_method)).to_a
+
+ omit 'TODO: Prism' if a.dig(4, :parser) != :"parse.y"
+
+ assert_equal true, a.dig(11, :use_block)
+
+ b = RubyVM::InstructionSequence.of(method(:block_unused_method)).to_a
+ assert_equal nil, b.dig(11, :use_block)
+ end
+
+ def test_compile_prism_with_invalid_object_type
+ assert_raise(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(Object.new)
+ end
+ end
+
+ def test_load_from_binary_only_accepts_string_param
+ assert_raise(TypeError) do
+ var_0 = 0
+ RubyVM::InstructionSequence.load_from_binary(var_0)
+ end
+ end
end
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 9978072744..34a80c3729 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -182,6 +182,52 @@ class TestKeywordArguments < Test::Unit::TestCase
[:keyrest, :kw], [:block, :b]], method(:f9).parameters)
end
+ def test_keyword_with_anonymous_keyword_splat
+ def self.a(b: 1, **) [b, **] end
+ kw = {b: 2, c: 3}
+ assert_equal([2, {c: 3}], a(**kw))
+ assert_equal({b: 2, c: 3}, kw)
+ end
+
+ def test_keyword_splat_nil
+ # cfunc call
+ assert_equal(nil, p(**nil))
+
+ def self.a0; end
+ assert_equal(nil, a0(**nil))
+ assert_equal(nil, :a0.to_proc.call(self, **nil))
+ assert_equal(nil, a0(**nil, &:block))
+
+ def self.o(x=1); x end
+ assert_equal(1, o(**nil))
+ assert_equal(2, o(2, **nil))
+ assert_equal(1, o(*nil, **nil))
+ assert_equal(1, o(**nil, **nil))
+ assert_equal({a: 1}, o(a: 1, **nil))
+ assert_equal({a: 1}, o(**nil, a: 1))
+
+ # symproc call
+ assert_equal(1, :o.to_proc.call(self, **nil))
+
+ def self.s(*a); a end
+ assert_equal([], s(**nil))
+ assert_equal([1], s(1, **nil))
+ assert_equal([], s(*nil, **nil))
+
+ def self.kws(**a); a end
+ assert_equal({}, kws(**nil))
+ assert_equal({}, kws(*nil, **nil))
+
+ def self.skws(*a, **kw); [a, kw] end
+ assert_equal([[], {}], skws(**nil))
+ assert_equal([[1], {}], skws(1, **nil))
+ assert_equal([[], {}], skws(*nil, **nil))
+
+ assert_equal({}, {**nil})
+ assert_equal({a: 1}, {a: 1, **nil})
+ assert_equal({a: 1}, {**nil, a: 1})
+ end
+
def test_lambda
f = ->(str: "foo", num: 424242) { [str, num] }
assert_equal(["foo", 424242], f[])
@@ -237,27 +283,27 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(true, Hash.ruby2_keywords_hash?(marked))
end
+ def assert_equal_not_same(kw, res)
+ assert_instance_of(Hash, res)
+ assert_equal(kw, res)
+ assert_not_same(kw, res)
+ end
+
def test_keyword_splat_new
kw = {}
h = {a: 1}
- def self.assert_equal_not_same(kw, res)
- assert_instance_of(Hash, res)
- assert_equal(kw, res)
- assert_not_same(kw, res)
- end
-
- def self.y(**kw) kw end
- m = method(:y)
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ def self.yo(**kw) kw end
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -266,25 +312,25 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.send(:call, **h))
m = method(:send)
- assert_equal(false, m.(:y, **{}).frozen?)
- assert_equal_not_same(kw, m.(:y, **kw))
- assert_equal_not_same(h, m.(:y, **h))
- assert_equal(false, m.send(:call, :y, **{}).frozen?)
- assert_equal_not_same(kw, m.send(:call, :y, **kw))
- assert_equal_not_same(h, m.send(:call, :y, **h))
-
- singleton_class.send(:remove_method, :y)
- define_singleton_method(:y) { |**kw| kw }
- m = method(:y)
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ assert_equal(false, m.(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.(:yo, **kw))
+ assert_equal_not_same(h, m.(:yo, **h))
+ assert_equal(false, m.send(:call, :yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, :yo, **kw))
+ assert_equal_not_same(h, m.send(:call, :yo, **h))
+
+ singleton_class.send(:remove_method, :yo)
+ define_singleton_method(:yo) { |**kw| kw }
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -292,17 +338,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- y = lambda { |**kw| kw }
- m = y.method(:call)
- assert_equal(false, y.(**{}).frozen?)
- assert_equal_not_same(kw, y.(**kw))
- assert_equal_not_same(h, y.(**h))
- assert_equal(false, y.send(:call, **{}).frozen?)
- assert_equal_not_same(kw, y.send(:call, **kw))
- assert_equal_not_same(h, y.send(:call, **h))
- assert_equal(false, y.public_send(:call, **{}).frozen?)
- assert_equal_not_same(kw, y.public_send(:call, **kw))
- assert_equal_not_same(h, y.public_send(:call, **h))
+ yo = lambda { |**kw| kw }
+ m = yo.method(:call)
+ assert_equal(false, yo.(**{}).frozen?)
+ assert_equal_not_same(kw, yo.(**kw))
+ assert_equal_not_same(h, yo.(**h))
+ assert_equal(false, yo.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, **kw))
+ assert_equal_not_same(h, yo.send(:call, **h))
+ assert_equal(false, yo.public_send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -310,17 +356,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- y = :y.to_proc
- m = y.method(:call)
- assert_equal(false, y.(self, **{}).frozen?)
- assert_equal_not_same(kw, y.(self, **kw))
- assert_equal_not_same(h, y.(self, **h))
- assert_equal(false, y.send(:call, self, **{}).frozen?)
- assert_equal_not_same(kw, y.send(:call, self, **kw))
- assert_equal_not_same(h, y.send(:call, self, **h))
- assert_equal(false, y.public_send(:call, self, **{}).frozen?)
- assert_equal_not_same(kw, y.public_send(:call, self, **kw))
- assert_equal_not_same(h, y.public_send(:call, self, **h))
+ yo = :yo.to_proc
+ m = yo.method(:call)
+ assert_equal(false, yo.(self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.(self, **kw))
+ assert_equal_not_same(h, yo.(self, **h))
+ assert_equal(false, yo.send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, self, **kw))
+ assert_equal_not_same(h, yo.send(:call, self, **h))
+ assert_equal(false, yo.public_send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, self, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, self, **h))
assert_equal(false, m.(self, **{}).frozen?)
assert_equal_not_same(kw, m.(self, **kw))
assert_equal_not_same(h, m.(self, **h))
@@ -329,20 +375,20 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.send(:call, self, **h))
c = Class.new do
- def y(**kw) kw end
+ def yo(**kw) kw end
end
o = c.new
- def o.y(**kw) super end
- m = o.method(:y)
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -350,17 +396,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- o.singleton_class.send(:remove_method, :y)
- def o.y(**kw) super(**kw) end
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -372,17 +418,17 @@ class TestKeywordArguments < Test::Unit::TestCase
def method_missing(_, **kw) kw end
end
o = c.new
- def o.y(**kw) super end
- m = o.method(:y)
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -390,17 +436,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- o.singleton_class.send(:remove_method, :y)
- def o.y(**kw) super(**kw) end
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -436,17 +482,41 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.(**h))
assert_equal_not_same(h, m.send(:call, **h))
- singleton_class.send(:remove_method, :y)
+ singleton_class.send(:remove_method, :yo)
def self.method_missing(_, **kw) kw end
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
+
+ def self.yo(*a, **kw) = kw
+ assert_equal_not_same kw, yo(**kw)
+ assert_equal_not_same kw, yo(**kw, **kw)
+
+ singleton_class.send(:remove_method, :yo)
+ def self.yo(opts) = opts
+ assert_equal_not_same h, yo(*[], **h)
+ a = []
+ assert_equal_not_same h, yo(*a, **h)
+ end
+
+ def test_keyword_splat_to_non_keyword_method
+ h = {a: 1}.freeze
+
+ def self.yo(kw) kw end
+ assert_equal_not_same(h, yo(**h))
+ assert_equal_not_same(h, method(:yo).(**h))
+ assert_equal_not_same(h, :yo.to_proc.(self, **h))
+
+ def self.yoa(*kw) kw[0] end
+ assert_equal_not_same(h, yoa(**h))
+ assert_equal_not_same(h, method(:yoa).(**h))
+ assert_equal_not_same(h, :yoa.to_proc.(self, **h))
end
def test_regular_kwsplat
@@ -2747,6 +2817,37 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_raise(FrozenError) { c.send(:ruby2_keywords, :baz) }
end
+ def test_anon_splat_ruby2_keywords
+ singleton_class.class_exec do
+ def bar(*a, **kw)
+ [a, kw]
+ end
+
+ ruby2_keywords def bar_anon(*)
+ bar(*)
+ end
+ end
+
+ a = [1, 2]
+ kw = {a: 1}
+ assert_equal([[1, 2], {a: 1}], bar_anon(*a, **kw))
+ assert_equal([1, 2], a)
+ assert_equal({a: 1}, kw)
+ end
+
+ def test_anon_splat_ruby2_keywords_bug_20388
+ extend(Module.new{def process(action, ...) 1 end})
+ extend(Module.new do
+ def process(action, *args)
+ args.freeze
+ super
+ end
+ ruby2_keywords :process
+ end)
+
+ assert_equal(1, process(:foo, bar: :baz))
+ end
+
def test_top_ruby2_keywords
assert_in_out_err([], <<-INPUT, ["[1, 2, 3]", "{:k=>1}"], [])
def bar(*a, **kw)
@@ -3922,6 +4023,20 @@ class TestKeywordArguments < Test::Unit::TestCase
}, bug8964
end
+ def test_large_kwsplat_to_method_taking_kw_and_kwsplat
+ assert_separately(['-'], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ n = 100000
+ x = Fiber.new do
+ h = {kw: 2}
+ n.times{|i| h[i.to_s.to_sym] = i}
+ def self.f(kw: 1, **kws) kws.size end
+ f(**h)
+ end.resume
+ assert_equal(n, x)
+ end;
+ end
+
def test_dynamic_symbol_keyword
bug10266 = '[ruby-dev:48564] [Bug #10266]'
assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}")
diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb
index 9949fab8c7..7738034240 100644
--- a/test/ruby/test_lambda.rb
+++ b/test/ruby/test_lambda.rb
@@ -177,32 +177,6 @@ class TestLambdaParameters < Test::Unit::TestCase
RUBY
end
- def pass_along(&block)
- lambda(&block)
- end
-
- def pass_along2(&block)
- pass_along(&block)
- end
-
- def test_create_non_lambda_for_proc_one_level
- prev_warning, Warning[:deprecated] = Warning[:deprecated], false
- f = pass_along {}
- refute_predicate(f, :lambda?, '[Bug #15620]')
- assert_nothing_raised(ArgumentError) { f.call(:extra_arg) }
- ensure
- Warning[:deprecated] = prev_warning
- end
-
- def test_create_non_lambda_for_proc_two_levels
- prev_warning, Warning[:deprecated] = Warning[:deprecated], false
- f = pass_along2 {}
- refute_predicate(f, :lambda?, '[Bug #15620]')
- assert_nothing_raised(ArgumentError) { f.call(:extra_arg) }
- ensure
- Warning[:deprecated] = prev_warning
- end
-
def test_instance_exec
bug12568 = '[ruby-core:76300] [Bug #12568]'
assert_nothing_raised(ArgumentError, bug12568) do
diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb
index 2116d0ee31..22127e903a 100644
--- a/test/ruby/test_lazy_enumerator.rb
+++ b/test/ruby/test_lazy_enumerator.rb
@@ -282,6 +282,11 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal(3, a.current)
end
+ def test_zip_map_lambda_bug_19569
+ ary = [1, 2, 3].to_enum.lazy.zip([:a, :b, :c]).map(&:last).to_a
+ assert_equal([:a, :b, :c], ary)
+ end
+
def test_take
a = Step.new(1..10)
assert_equal(1, a.take(5).first)
@@ -295,6 +300,26 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal(nil, a.current)
end
+ def test_take_0_bug_18971
+ def (bomb = Object.new.extend(Enumerable)).each
+ raise
+ end
+ [2..10, bomb].each do |e|
+ assert_equal([], e.lazy.take(0).map(&:itself).to_a)
+ assert_equal([], e.lazy.take(0).select(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).select(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).drop(0).to_a)
+ assert_equal([], e.lazy.take(0).find_all {|_| true}.to_a)
+ assert_equal([], e.lazy.take(0).zip((12..20)).to_a)
+ assert_equal([], e.lazy.take(0).uniq.to_a)
+ assert_equal([], e.lazy.take(0).sort.to_a)
+ end
+ end
+
def test_take_bad_arg
a = Step.new(1..10)
assert_raise(ArgumentError) { a.lazy.take(-1) }
diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb
index 99dd3a0c56..c6154af1f6 100644
--- a/test/ruby/test_literal.rb
+++ b/test/ruby/test_literal.rb
@@ -142,6 +142,8 @@ class TestRubyLiteral < Test::Unit::TestCase
end
def test_frozen_string
+ default = eval("'test'").frozen?
+
all_assertions do |a|
a.for("false with indicator") do
str = eval("# -*- frozen-string-literal: false -*-\n""'foo'")
@@ -161,19 +163,19 @@ class TestRubyLiteral < Test::Unit::TestCase
end
a.for("false with preceding garbage") do
str = eval("# x frozen-string-literal: false\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("true with preceding garbage") do
str = eval("# x frozen-string-literal: true\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("false with succeeding garbage") do
str = eval("# frozen-string-literal: false x\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
a.for("true with succeeding garbage") do
str = eval("# frozen-string-literal: true x\n""'foo'")
- assert_not_predicate(str, :frozen?)
+ assert_equal(default, str.frozen?)
end
end
end
@@ -184,6 +186,11 @@ class TestRubyLiteral < Test::Unit::TestCase
list.each { |str| assert_predicate str, :frozen? }
end
+ def test_string_in_hash_literal
+ hash = eval("# frozen-string-literal: false\n""{foo: 'foo'}")
+ assert_not_predicate(hash[:foo], :frozen?)
+ end
+
if defined?(RubyVM::InstructionSequence.compile_option) and
RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal)
def test_debug_frozen_string
@@ -496,10 +503,11 @@ class TestRubyLiteral < Test::Unit::TestCase
'1.0i',
'1.72723e-77',
'//',
+ '__LINE__',
+ '__FILE__',
+ '__ENCODING__',
) do |key|
- assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) do
- eval("{#{key} => :bar, #{key} => :foo}")
- end
+ assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) { eval("{#{key} => :bar, #{key} => :foo}") }
end
end
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb
index c00bf59e18..a6493374b5 100644
--- a/test/ruby/test_m17n.rb
+++ b/test/ruby/test_m17n.rb
@@ -226,38 +226,16 @@ class TestM17N < Test::Unit::TestCase
end
end
- STR_WITHOUT_BOM = "\u3042".freeze
- STR_WITH_BOM = "\uFEFF\u3042".freeze
- bug8940 = '[ruby-core:59757] [Bug #8940]'
- bug9415 = '[ruby-dev:47895] [Bug #9415]'
- %w/UTF-16 UTF-32/.each do |enc|
- %w/BE LE/.each do |endian|
- bom = "\uFEFF".encode("#{enc}#{endian}").force_encoding(enc)
-
- define_method("test_utf_16_32_inspect(#{enc}#{endian})") do
- s = STR_WITHOUT_BOM.encode(enc + endian)
- # When a UTF-16/32 string doesn't have a BOM,
- # inspect as a dummy encoding string.
- assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect,
- s.dup.force_encoding(enc).inspect)
- assert_normal_exit("#{bom.b.dump}.force_encoding('#{enc}').inspect", bug8940)
- end
-
- define_method("test_utf_16_32_codepoints(#{enc}#{endian})") do
- assert_equal([0xFEFF], bom.codepoints, bug9415)
- end
-
- define_method("test_utf_16_32_ord(#{enc}#{endian})") do
- assert_equal(0xFEFF, bom.ord, bug9415)
- end
-
- define_method("test_utf_16_32_inspect(#{enc}#{endian}-BOM)") do
- s = STR_WITH_BOM.encode(enc + endian)
- # When a UTF-16/32 string has a BOM,
- # inspect as a particular encoding string.
- assert_equal(s.inspect,
- s.dup.force_encoding(enc).inspect)
- end
+ def test_utf_dummy_are_like_regular_dummy_encodings
+ [Encoding::UTF_16, Encoding::UTF_32].each do |enc|
+ s = "\u3042".encode("UTF-32BE")
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
+ s = "\x00\x00\xFE\xFF"
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
+
+ assert_equal [0, 0, 254, 255], "\x00\x00\xFE\xFF".force_encoding(enc).codepoints
+ assert_equal 0, "\x00\x00\xFE\xFF".force_encoding(enc).ord
+ assert_equal 255, "\xFF\xFE\x00\x00".force_encoding(enc).ord
end
end
@@ -892,10 +870,22 @@ class TestM17N < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError) {
"%s%s" % [s("\xc2\xa1"), e("\xc2\xa1")]
}
+
+ assert_equal("\u3042".encode('Windows-31J'), "%c" % "\u3042\u3044".encode('Windows-31J'))
end
def test_sprintf_p
Encoding.list.each do |e|
+ unless e.ascii_compatible?
+ format = e.dummy? ? "%p".force_encoding(e) : "%p".encode(e)
+ assert_raise(Encoding::CompatibilityError) do
+ sprintf(format, nil)
+ end
+ assert_raise(Encoding::CompatibilityError) do
+ format % nil
+ end
+ next
+ end
format = "%p".force_encoding(e)
['', 'a', "\xC2\xA1", "\x00"].each do |s|
s.force_encoding(e)
@@ -1100,7 +1090,23 @@ class TestM17N < Test::Unit::TestCase
assert_nil(e("\xa1\xa2\xa3\xa4").index(e("\xa3")))
assert_nil(e("\xa1\xa2\xa3\xa4").rindex(e("\xa3")))
s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4")
- assert_raise(Encoding::CompatibilityError){s.rindex(a("\xb1\xa3"))}
+
+ a_with_e = /EUC-JP and BINARY \(ASCII-8BIT\)/
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.index(a("\xb1\xa3"))
+ end
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.rindex(a("\xb1\xa3"))
+ end
+
+ a_with_e = /BINARY \(ASCII-8BIT\) regexp with EUC-JP string/
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.index(Regexp.new(a("\xb1\xa3")))
+ end
+ assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do
+ s.rindex(Regexp.new(a("\xb1\xa3")))
+ end
+
bug11488 = '[ruby-core:70592] [Bug #11488]'
each_encoding("abcdef", "def") do |str, substr|
assert_equal(3, str.index(substr), bug11488)
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index 361d18dd4b..79d9577737 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'test/unit'
-require 'tempfile'
require_relative 'marshaltestlib'
class TestMarshal < Test::Unit::TestCase
@@ -33,7 +32,7 @@ class TestMarshal < Test::Unit::TestCase
end
def test_marshal
- a = [1, 2, 3, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
+ a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
assert_equal a, Marshal.load(Marshal.dump(a))
[[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
@@ -47,6 +46,26 @@ class TestMarshal < Test::Unit::TestCase
}
end
+ def test_marshal_integers
+ a = []
+ [-2, -1, 0, 1, 2].each do |i|
+ 0.upto(65).map do |exp|
+ a << 2**exp + i
+ end
+ end
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, 2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+ end
+
+ def test_marshal_small_bignum_backref
+ assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06")
+ end
+
StrClone = String.clone
def test_marshal_cloned_class
assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc"))))
@@ -72,6 +91,14 @@ class TestMarshal < Test::Unit::TestCase
TestMarshal.instance_eval { remove_const :StructInvalidMembers }
end
+ def test_load_range_as_struct
+ assert_raise(TypeError, 'GH-6832') do
+ # Can be obtained with:
+ # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))'
+ Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0")
+ end
+ end
+
class C
def initialize(str)
@str = str
@@ -286,11 +313,10 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109)
assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do
- re = Tempfile.create("marshal_regexp") do |f|
- f.binmode.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
- f.rewind
- re2 = Marshal.load(f)
- re2
+ re = IO.pipe do |r, w|
+ w.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
+ # Marshal.load would not overread and block
+ Marshal.load(r)
end
assert_equal(//, re)
end
@@ -583,6 +609,8 @@ class TestMarshal < Test::Unit::TestCase
def test_continuation
EnvUtil.suppress_warning {require "continuation"}
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
c = Bug9523.new
assert_raise_with_message(RuntimeError, /Marshal\.dump reentered at marshal_dump/) do
Marshal.dump(c)
diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb
index 73f44c6ae3..6e67099c6b 100644
--- a/test/ruby/test_math.rb
+++ b/test/ruby/test_math.rb
@@ -5,6 +5,7 @@ class TestMath < Test::Unit::TestCase
def assert_infinity(a, *rest)
rest = ["not infinity: #{a.inspect}"] if rest.empty?
assert_predicate(a, :infinite?, *rest)
+ assert_predicate(a, :positive?, *rest)
end
def assert_nan(a, *rest)
@@ -165,6 +166,9 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_nan(Math.log(0.0, 0.0)) }
assert_nothing_raised { assert_nan(Math.log(Float::NAN)) }
assert_nothing_raised { assert_nan(Math.log(1.0, Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log(0)) }
+ assert_nothing_raised { assert_infinity(-Math.log(0, 2)) }
+ check(307.95368556425274, Math.log(2**1023, 10))
end
def test_log2
@@ -179,6 +183,7 @@ class TestMath < Test::Unit::TestCase
assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-1.0) }
assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-Float::EPSILON) }
assert_nothing_raised { assert_nan(Math.log2(Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log2(0)) }
end
def test_log10
@@ -193,6 +198,7 @@ class TestMath < Test::Unit::TestCase
assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-1.0) }
assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-Float::EPSILON) }
assert_nothing_raised { assert_nan(Math.log10(Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log10(0)) }
end
def test_sqrt
@@ -277,8 +283,7 @@ class TestMath < Test::Unit::TestCase
assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-1.0) }
x = Math.gamma(-0.0)
mesg = "Math.gamma(-0.0) should be -INF"
- assert_infinity(x, mesg)
- assert_predicate(x, :negative?, mesg)
+ assert_infinity(-x, mesg)
assert_nan(Math.gamma(Float::NAN))
end
@@ -299,7 +304,6 @@ class TestMath < Test::Unit::TestCase
x, sign = Math.lgamma(-0.0)
mesg = "Math.lgamma(-0.0) should be [INF, -1]"
assert_infinity(x, mesg)
- assert_predicate(x, :positive?, mesg)
assert_equal(-1, sign, mesg)
x, sign = Math.lgamma(Float::NAN)
assert_nan(x)
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index 83e499913a..5301b51650 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -199,11 +199,6 @@ class TestMethod < Test::Unit::TestCase
assert_equal(o.method(:foo), o.method(:foo))
assert_equal(o.method(:foo), o.method(:bar))
assert_not_equal(o.method(:foo), o.method(:baz))
-
- class << o
- private :bar
- end
- assert_not_equal(o.method(:foo), o.method(:bar))
end
def test_hash
@@ -330,8 +325,8 @@ class TestMethod < Test::Unit::TestCase
def PUBLIC_SINGLETON_TEST.def; end
end
def test_define_singleton_method_public
- assert_equal(true, PUBLIC_SINGLETON_TEST.method(:dsm).public?)
- assert_equal(true, PUBLIC_SINGLETON_TEST.method(:def).public?)
+ assert_nil(PUBLIC_SINGLETON_TEST.dsm)
+ assert_nil(PUBLIC_SINGLETON_TEST.def)
end
def test_define_singleton_method_no_proc
@@ -455,6 +450,18 @@ class TestMethod < Test::Unit::TestCase
assert_equal(:bar, m.clone.bar)
end
+ def test_clone_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ o = Object.new
+ def o.foo; :foo; end
+ m = o.method(:foo)
+ def m.bar; :bar; end
+ assert_equal(:foo, m.clone.call)
+ assert_equal(:bar, m.clone.bar)
+ end
+ end
+
def test_inspect
o = Object.new
def o.foo; end; line_no = __LINE__
@@ -776,6 +783,14 @@ class TestMethod < Test::Unit::TestCase
assert_raise(NoMethodError) { (self).mv2 }
assert_nothing_raised { self.mv3 }
+ class << (obj = Object.new)
+ private def [](x) x end
+ def mv1(x) self[x] end
+ def mv2(x) (self)[x] end
+ end
+ assert_nothing_raised { obj.mv1(0) }
+ assert_raise(NoMethodError) { obj.mv2(0) }
+
v = Visibility.new
assert_equal('method', defined?(v.mv1))
@@ -1061,7 +1076,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal(sm, im.clone.bind(o).super_method)
end
- def test_super_method_removed
+ def test_super_method_removed_public
c1 = Class.new {private def foo; end}
c2 = Class.new(c1) {public :foo}
c3 = Class.new(c2) {def foo; end}
@@ -1071,20 +1086,35 @@ class TestMethod < Test::Unit::TestCase
assert_nil(m, Feature9781)
end
+ def test_super_method_removed_regular
+ c1 = Class.new { def foo; end }
+ c2 = Class.new(c1) { def foo; end }
+ assert_equal c1.instance_method(:foo), c2.instance_method(:foo).super_method
+ c1.remove_method :foo
+ assert_equal nil, c2.instance_method(:foo).super_method
+ end
+
def test_prepended_public_zsuper
- mod = EnvUtil.labeled_module("Mod") {private def foo; :ok end}
- mods = [mod]
+ mod = EnvUtil.labeled_module("Mod") {private def foo; [:ok] end}
obj = Object.new.extend(mod)
+
class << obj
public :foo
end
- 2.times do |i|
- mods.unshift(mod = EnvUtil.labeled_module("Mod#{i}") {def foo; end})
- obj.singleton_class.prepend(mod)
- end
+
+ mod1 = EnvUtil.labeled_module("Mod1") {def foo; [:mod1] + super end}
+ obj.singleton_class.prepend(mod1)
+
+ mod2 = EnvUtil.labeled_module("Mod2") {def foo; [:mod2] + super end}
+ obj.singleton_class.prepend(mod2)
+
m = obj.method(:foo)
- assert_equal(mods, mods.map {m.owner.tap {m = m.super_method}})
- assert_nil(m)
+ assert_equal mod2, m.owner
+ assert_equal mod1, m.super_method.owner
+ assert_equal obj.singleton_class, m.super_method.super_method.owner
+ assert_equal nil, m.super_method.super_method.super_method
+
+ assert_equal [:mod2, :mod1, :ok], obj.foo
end
def test_super_method_with_prepended_module
@@ -1197,48 +1227,145 @@ class TestMethod < Test::Unit::TestCase
assert_nil(super_method)
end
- def test_method_visibility_predicates
- v = Visibility.new
- assert_equal(true, v.method(:mv1).public?)
- assert_equal(true, v.method(:mv2).private?)
- assert_equal(true, v.method(:mv3).protected?)
- assert_equal(false, v.method(:mv2).public?)
- assert_equal(false, v.method(:mv3).private?)
- assert_equal(false, v.method(:mv1).protected?)
+ # Bug 18435
+ def test_instance_methods_owner_consistency
+ a = Module.new { def method1; end }
+
+ b = Class.new do
+ include a
+ protected :method1
+ end
+
+ assert_equal [:method1], b.instance_methods(false)
+ assert_equal b, b.instance_method(:method1).owner
end
- def test_unbound_method_visibility_predicates
- assert_equal(true, Visibility.instance_method(:mv1).public?)
- assert_equal(true, Visibility.instance_method(:mv2).private?)
- assert_equal(true, Visibility.instance_method(:mv3).protected?)
- assert_equal(false, Visibility.instance_method(:mv2).public?)
- assert_equal(false, Visibility.instance_method(:mv3).private?)
- assert_equal(false, Visibility.instance_method(:mv1).protected?)
+ def test_zsuper_method_removed
+ a = EnvUtil.labeled_class('A') do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal unbound, b.public_instance_method(:foo)
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088'
+ assert_raise(NameError, link) { b.instance_method(:foo) }
+ # For #test_method_list below, otherwise we get the same error as just above
+ b.remove_method(:foo)
end
- class VisibilitySub < Visibility
- protected :mv1
- public :mv2
- private :mv3
+ def test_zsuper_method_removed_higher_method
+ a0 = EnvUtil.labeled_class('A0') do
+ def foo(arg1 = nil, arg2 = nil)
+ 0
+ end
+ end
+ line0 = __LINE__ - 4
+ a0_foo = a0.instance_method(:foo)
+
+ a = EnvUtil.labeled_class('A', a0) do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal a0_foo, unbound.super_method
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+ assert_equal a0_foo, unbound.super_method
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect
end
- def test_method_visibility_predicates_with_subclass_visbility_change
- v = VisibilitySub.new
- assert_equal(false, v.method(:mv1).public?)
- assert_equal(false, v.method(:mv2).private?)
- assert_equal(false, v.method(:mv3).protected?)
- assert_equal(true, v.method(:mv2).public?)
- assert_equal(true, v.method(:mv3).private?)
- assert_equal(true, v.method(:mv1).protected?)
+ def test_zsuper_method_redefined_bind_call
+ c0 = EnvUtil.labeled_class('C0') do
+ def foo
+ [:foo]
+ end
+ end
+
+ c1 = EnvUtil.labeled_class('C1', c0) do
+ def foo
+ super + [:bar]
+ end
+ end
+ m1 = c1.instance_method(:foo)
+
+ c2 = EnvUtil.labeled_class('C2', c1) do
+ private :foo
+ end
+
+ assert_equal [:foo], c2.private_instance_methods(false)
+ m2 = c2.instance_method(:foo)
+
+ c1.class_exec do
+ remove_method :foo
+ def foo
+ [:bar2]
+ end
+ end
+
+ m3 = c2.instance_method(:foo)
+ c = c2.new
+ assert_equal [:foo, :bar], m1.bind_call(c)
+ assert_equal c1, m1.owner
+ assert_equal [:foo, :bar], m2.bind_call(c)
+ assert_equal c2, m2.owner
+ assert_equal [:bar2], m3.bind_call(c)
+ assert_equal c2, m3.owner
end
- def test_unbound_method_visibility_predicates_with_subclass_visbility_change
- assert_equal(false, VisibilitySub.instance_method(:mv1).public?)
- assert_equal(false, VisibilitySub.instance_method(:mv2).private?)
- assert_equal(false, VisibilitySub.instance_method(:mv3).protected?)
- assert_equal(true, VisibilitySub.instance_method(:mv2).public?)
- assert_equal(true, VisibilitySub.instance_method(:mv3).private?)
- assert_equal(true, VisibilitySub.instance_method(:mv1).protected?)
+ # Bug #18751
+ def method_equality_visbility_alias
+ c = Class.new do
+ class << self
+ alias_method :n, :new
+ private :new
+ end
+ end
+
+ assert_equal c.method(:n), c.method(:new)
+
+ assert_not_equal c.method(:n), Class.method(:new)
+ assert_equal c.method(:n) == Class.instance_method(:new).bind(c)
+
+ assert_not_equal c.method(:new), Class.method(:new)
+ assert_equal c.method(:new), Class.instance_method(:new).bind(c)
end
def rest_parameter(*rest)
@@ -1316,25 +1443,25 @@ class TestMethod < Test::Unit::TestCase
end
def test_argument_error_location
- body = <<-'END_OF_BODY'
- eval <<-'EOS'
- $line_lambda = __LINE__; $f = lambda do
- _x = 1
- end
- $line_method = __LINE__; def foo
- _x = 1
- end
- begin
- $f.call(1)
- rescue ArgumentError => e
- assert_equal "(eval):#{$line_lambda.to_s}:in `block in <main>'", e.backtrace.first
- end
- begin
- foo(1)
- rescue ArgumentError => e
- assert_equal "(eval):#{$line_method}:in `foo'", e.backtrace.first
- end
- EOS
+ body = <<~'END_OF_BODY'
+ eval <<~'EOS', nil, "main.rb"
+ $line_lambda = __LINE__; $f = lambda do
+ _x = 1
+ end
+ $line_method = __LINE__; def foo
+ _x = 1
+ end
+ begin
+ $f.call(1)
+ rescue ArgumentError => e
+ assert_equal "main.rb:#{$line_lambda}:in 'block in <main>'", e.backtrace.first
+ end
+ begin
+ foo(1)
+ rescue ArgumentError => e
+ assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first
+ end
+ EOS
END_OF_BODY
assert_separately [], body
@@ -1343,7 +1470,7 @@ class TestMethod < Test::Unit::TestCase
end
def test_zsuper_private_override_instance_method
- assert_separately(%w(--disable-gems), <<-'end;', timeout: 30)
+ assert_separately([], <<-'end;', timeout: 30)
# Bug #16942 [ruby-core:98691]
module M
def x
@@ -1359,12 +1486,12 @@ class TestMethod < Test::Unit::TestCase
::Object.prepend(M2)
m = Object.instance_method(:x)
- assert_equal M, m.owner
+ assert_equal M2, m.owner
end;
end
def test_override_optimized_method_on_class_using_prepend
- assert_separately(%w(--disable-gems), <<-'end;', timeout: 30)
+ assert_separately([], <<-'end;', timeout: 30)
# Bug #17725 [ruby-core:102884]
$VERBOSE = nil
String.prepend(Module.new)
@@ -1488,4 +1615,96 @@ class TestMethod < Test::Unit::TestCase
def test_invalidating_CC_ASAN
assert_ruby_status(['-e', 'using Module.new'])
end
+
+ def test_kwarg_eval_memory_leak
+ assert_no_memory_leak([], "", <<~RUBY, rss: true, limit: 1.2)
+ 100_000.times do
+ eval("Hash.new(foo: 123)")
+ end
+ RUBY
+ end
+
+ def test_warn_unused_block
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil
+ foo{} # warn
+ send(:foo){} # warn
+ b = Proc.new{}
+ foo(&b) # warn
+ RUBY
+ assert_equal 3, err.size
+ err = err.join
+ assert_match(/-:2: warning/, err)
+ assert_match(/-:3: warning/, err)
+ assert_match(/-:5: warning/, err)
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil
+ 10.times{foo{}} # warn once
+ RUBY
+ assert_equal 1, err.size
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ def foo = nil; b = nil
+ foo(&b) # no warning
+ 1.object_id{} # no warning because it is written in C
+
+ class C
+ def initialize
+ end
+ end
+ C.new{} # no warning
+
+ RUBY
+ assert_equal 0, err.size
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ class C0
+ def f1 = nil
+ def f2 = nil
+ def f3 = nil
+ def f4 = nil
+ def f5 = nil
+ def f6 = nil
+ end
+
+ class C1 < C0
+ def f1 = super # zsuper / use
+ def f2 = super() # super / use
+ def f3(&_) = super(&_) # super / use
+ def f4 = super(&nil) # super / unuse
+ def f5 = super(){} # super / unuse
+ def f6 = super{} # zsuper / unuse
+ end
+
+ C1.new.f1{} # no warning
+ C1.new.f2{} # no warning
+ C1.new.f3{} # no warning
+ C1.new.f4{} # warning
+ C1.new.f5{} # warning
+ C1.new.f6{} # warning
+ RUBY
+ assert_equal 3, err.size, err.join("\n")
+ assert_match(/-:22: warning.+f4/, err.join)
+ assert_match(/-:23: warning.+f5/, err.join)
+ assert_match(/-:24: warning.+f6/, err.join)
+ end
+
+ assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
+ class C0
+ def f = yield
+ end
+
+ class C1 < C0
+ def f = nil
+ end
+
+ C1.new.f{} # do not warn on duck typing
+ RUBY
+ assert_equal 0, err.size, err.join("\n")
+ end
+ end
end
diff --git a/test/ruby/test_mjit.rb b/test/ruby/test_mjit.rb
deleted file mode 100644
index 30c5659c14..0000000000
--- a/test/ruby/test_mjit.rb
+++ /dev/null
@@ -1,1273 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'tmpdir'
-require_relative '../lib/jit_support'
-
-# Test for --mjit option
-class TestMJIT < Test::Unit::TestCase
- include JITSupport
-
- IGNORABLE_PATTERNS = [
- /\AJIT recompile: .+\n\z/,
- /\AJIT inline: .+\n\z/,
- /\AJIT cancel: .+\n\z/,
- /\ASuccessful MJIT finish\n\z/,
- ]
- MAX_CACHE_PATTERNS = [
- /\AJIT compaction \([^)]+\): .+\n\z/,
- /\AToo many JIT code, but skipped unloading units for JIT compaction\n\z/,
- /\ANo units can be unloaded -- .+\n\z/,
- ]
-
- # trace_* insns are not compiled for now...
- TEST_PENDING_INSNS = RubyVM::INSTRUCTION_NAMES.select { |n| n.start_with?('trace_') }.map(&:to_sym) + [
- # not supported yet
- :defineclass,
-
- # to be tested
- :invokebuiltin,
-
- # never used
- :opt_invokebuiltin_delegate,
- ].each do |insn|
- if !RubyVM::INSTRUCTION_NAMES.include?(insn.to_s)
- warn "instruction #{insn.inspect} is not defined but included in TestMJIT::TEST_PENDING_INSNS"
- end
- end
-
- def self.untested_insns
- @untested_insns ||= (RubyVM::INSTRUCTION_NAMES.map(&:to_sym) - TEST_PENDING_INSNS)
- end
-
- def self.setup
- return if defined?(@setup_hooked)
- @setup_hooked = true
-
- # ci.rvm.jp caches its build environment. Clean up temporary files left by SEGV.
- if ENV['RUBY_DEBUG']&.include?('ci')
- Dir.glob("#{ENV.fetch('TMPDIR', '/tmp')}/_ruby_mjit_p*u*.*").each do |file|
- puts "test/ruby/test_mjit.rb: removing #{file}"
- File.unlink(file)
- end
- end
-
- # ruby -w -Itest/lib test/ruby/test_mjit.rb
- if $VERBOSE
- pid = $$
- at_exit do
- if pid == $$ && !TestMJIT.untested_insns.empty?
- warn "you may want to add tests for following insns, when you have a chance: #{TestMJIT.untested_insns.join(' ')}"
- end
- end
- end
- end
-
- def setup
- unless JITSupport.supported?
- omit 'JIT seems not supported on this platform'
- end
- self.class.setup
- end
-
- def test_compile_insn_nop
- assert_compile_once('nil rescue true', result_inspect: 'nil', insns: %i[nop])
- end
-
- def test_compile_insn_local
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0])
- begin;
- foo = 1
- foo
- end;
-
- insns = %i[setlocal getlocal setlocal_WC_0 getlocal_WC_0 setlocal_WC_1 getlocal_WC_1]
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, stdout: '168', insns: insns)
- begin;
- def foo
- a = 0
- [1, 2].each do |i|
- a += i
- [3, 4].each do |j|
- a *= j
- end
- end
- a
- end
-
- print foo
- end;
- end
-
- def test_compile_insn_blockparam
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2, insns: %i[getblockparam setblockparam])
- begin;
- def foo(&b)
- a = b
- b = 2
- a.call + 2
- end
-
- print foo { 1 }
- end;
- end
-
- def test_compile_insn_getblockparamproxy
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 3, insns: %i[getblockparamproxy])
- begin;
- def bar(&b)
- b.call
- end
-
- def foo(&b)
- bar(&b) * bar(&b)
- end
-
- print foo { 2 }
- end;
- end
-
- def test_compile_insn_getspecial
- assert_compile_once('$1', result_inspect: 'nil', insns: %i[getspecial])
- end
-
- def test_compile_insn_setspecial
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial])
- begin;
- true if nil.nil?..nil.nil?
- end;
- end
-
- def test_compile_insn_instancevariable
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable])
- begin;
- @foo = 1
- @foo
- end;
-
- # optimized getinstancevariable call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 1, min_calls: 2)
- begin;
- class A
- def initialize
- @a = 1
- @b = 2
- end
-
- def three
- @a + @b
- end
- end
-
- a = A.new
- print(a.three) # set ic
- print(a.three) # inlined ic
- end;
- end
-
- def test_compile_insn_classvariable
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 1, insns: %i[getclassvariable setclassvariable])
- begin;
- class Foo
- def self.foo
- @@foo = 1
- @@foo
- end
- end
-
- print Foo.foo
- end;
- end
-
- def test_compile_insn_constant
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getconstant setconstant])
- begin;
- FOO = 1
- FOO
- end;
- end
-
- def test_compile_insn_global
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal])
- begin;
- $foo = 1
- $foo
- end;
- end
-
- def test_compile_insn_putnil
- assert_compile_once('nil', result_inspect: 'nil', insns: %i[putnil])
- end
-
- def test_compile_insn_putself
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1, insns: %i[putself])
- begin;
- proc { print "hello" }.call
- end;
- end
-
- def test_compile_insn_putobject
- assert_compile_once('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_])
- assert_compile_once('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_])
- assert_compile_once('2', result_inspect: '2', insns: %i[putobject])
- end
-
- def test_compile_insn_definemethod_definesmethod
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworld', success_count: 3, insns: %i[definemethod definesmethod])
- begin;
- print 1.times.map {
- def method_definition
- 'hello'
- end
-
- def self.smethod_definition
- 'world'
- end
-
- method_definition + smethod_definition
- }.join
- end;
- end
-
- def test_compile_insn_putspecialobject
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'a', success_count: 2, insns: %i[putspecialobject])
- begin;
- print 1.times.map {
- def a
- 'a'
- end
-
- alias :b :a
-
- b
- }.join
- end;
- end
-
- def test_compile_insn_putstring_concatstrings_objtostring
- assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring])
- end
-
- def test_compile_insn_toregexp
- assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp])
- end
-
- def test_compile_insn_newarray
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray])
- begin;
- a, b, c = 1, 2, 3
- [a, b, c]
- end;
- end
-
- def test_compile_insn_newarraykwsplat
- assert_compile_once('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat])
- end
-
- def test_compile_insn_intern_duparray
- assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray])
- end
-
- def test_compile_insn_expandarray
- assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray])
- end
-
- def test_compile_insn_concatarray
- assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray])
- end
-
- def test_compile_insn_splatarray
- assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray])
- end
-
- def test_compile_insn_newhash
- assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash])
- end
-
- def test_compile_insn_duphash
- assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash])
- end
-
- def test_compile_insn_newrange
- assert_compile_once('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange])
- end
-
- def test_compile_insn_pop
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop])
- begin;
- a = false
- b = 1
- a || b
- end;
- end
-
- def test_compile_insn_dup
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup])
- begin;
- a = 1
- a&.+(2)
- end;
- end
-
- def test_compile_insn_dupn
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn])
- begin;
- klass = Class.new
- klass::X ||= true
- end;
- end
-
- def test_compile_insn_swap_topn
- assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn])
- end
-
- def test_compile_insn_reput
- omit "write test"
- end
-
- def test_compile_insn_setn
- assert_compile_once('[nil][0] = 1', result_inspect: '1', insns: %i[setn])
- end
-
- def test_compile_insn_adjuststack
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack])
- begin;
- x = [true]
- x[0] ||= nil
- x[0]
- end;
- end
-
- def test_compile_insn_defined
- assert_compile_once('defined?(a)', result_inspect: 'nil', insns: %i[defined])
- end
-
- def test_compile_insn_checkkeyword
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1, insns: %i[checkkeyword])
- begin;
- def test(x: rand)
- x
- end
- print test(x: true)
- end;
- end
-
- def test_compile_insn_tracecoverage
- omit "write test"
- end
-
- def test_compile_insn_defineclass
- omit "support this in mjit_compile (low priority)"
- end
-
- def test_compile_insn_send
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 3, insns: %i[send])
- begin;
- print proc { yield_self { 1 } }.call
- end;
- end
-
- def test_compile_insn_opt_str_freeze
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze])
- begin;
- 'foo'.freeze
- end;
- end
-
- def test_compile_insn_opt_nil_p
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p])
- begin;
- nil.nil?.nil?
- end;
- end
-
- def test_compile_insn_opt_str_uminus
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus])
- begin;
- -'bar'
- end;
- end
-
- def test_compile_insn_opt_newarray_max
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max])
- begin;
- a = 1
- b = 2
- [a, b].max
- end;
- end
-
- def test_compile_insn_opt_newarray_min
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min])
- begin;
- a = 1
- b = 2
- [a, b].min
- end;
- end
-
- def test_compile_insn_opt_send_without_block
- assert_compile_once('print', result_inspect: 'nil', insns: %i[opt_send_without_block])
- end
-
- def test_compile_insn_invokesuper
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4, insns: %i[invokesuper])
- begin;
- mod = Module.new {
- def test
- super + 2
- end
- }
- klass = Class.new {
- prepend mod
- def test
- 1
- end
- }
- print klass.new.test
- end;
- end
-
- def test_compile_insn_invokeblock_leave
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2, insns: %i[invokeblock leave])
- begin;
- def foo
- yield
- end
- print foo { 2 }
- end;
- end
-
- def test_compile_insn_throw
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2, insns: %i[throw])
- begin;
- def test
- proc do
- if 1+1 == 1
- return 3
- else
- return 4
- end
- 5
- end.call
- end
- print test
- end;
- end
-
- def test_compile_insn_jump_branchif
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif])
- begin;
- a = false
- 1 + 1 while a
- end;
- end
-
- def test_compile_insn_branchunless
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless])
- begin;
- a = true
- if a
- 1
- else
- 2
- end
- end;
- end
-
- def test_compile_insn_branchnil
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil])
- begin;
- a = 2
- a&.+(1)
- end;
- end
-
- def test_compile_insn_objtostring
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring])
- begin;
- a = '2'
- "4#{a}"
- end;
- end
-
- def test_compile_insn_inlinecache
- assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getinlinecache opt_setinlinecache])
- end
-
- def test_compile_insn_once
- assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once])
- end
-
- def test_compile_insn_checkmatch_opt_case_dispatch
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
- begin;
- case 'hello'
- when 'hello'
- 'world'
- end
- end;
- end
-
- def test_compile_insn_opt_calc
- assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
- assert_compile_once('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
- assert_compile_once('4 + 2', result_inspect: '6')
- end
-
- def test_compile_insn_opt_cmp
- assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq])
- end
-
- def test_compile_insn_opt_rel
- assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge])
- end
-
- def test_compile_insn_opt_ltlt
- assert_compile_once('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt])
- end
-
- def test_compile_insn_opt_and
- assert_compile_once('1 & 3', result_inspect: '1', insns: %i[opt_and])
- end
-
- def test_compile_insn_opt_or
- assert_compile_once('1 | 3', result_inspect: '3', insns: %i[opt_or])
- end
-
- def test_compile_insn_opt_aref
- # optimized call (optimized JIT) -> send call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, min_calls: 1, insns: %i[opt_aref])
- begin;
- obj = Object.new
- def obj.[](h)
- h
- end
-
- block = proc { |h| h[1] }
- print block.call({ 1 => 2 })
- print block.call(obj)
- end;
-
- # send call -> optimized call (send JIT) -> optimized call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 2, min_calls: 2)
- begin;
- obj = Object.new
- def obj.[](h)
- h
- end
-
- block = proc { |h| h[1] }
- print block.call(obj)
- print block.call({ 1 => 2 })
- print block.call({ 1 => 2 })
- end;
- end
-
- def test_compile_insn_opt_aref_with
- assert_compile_once("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with])
- end
-
- def test_compile_insn_opt_aset
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with])
- begin;
- hash = { '1' => 2 }
- (hash['2'] = 2) + (hash[1.to_s] = 3)
- end;
- end
-
- def test_compile_insn_opt_length_size
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size])
- begin;
- array = [1, 2]
- array.length + array.size
- end;
- end
-
- def test_compile_insn_opt_empty_p
- assert_compile_once('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p])
- end
-
- def test_compile_insn_opt_succ
- assert_compile_once('1.succ', result_inspect: '2', insns: %i[opt_succ])
- end
-
- def test_compile_insn_opt_not
- assert_compile_once('!!true', result_inspect: 'true', insns: %i[opt_not])
- end
-
- def test_compile_insn_opt_regexpmatch2
- assert_compile_once("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2])
- assert_compile_once("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2])
- end
-
- def test_compile_insn_opt_invokebuiltin_delegate_leave
- iseq = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first)
- p RubyVM::InstructionSequence.of("\x00".method(:unpack)).to_a
- EOS
- insns = collect_insns(iseq)
- mark_tested_insn(:opt_invokebuiltin_delegate_leave, used_insns: insns)
- assert_eval_with_jit('print "\x00".unpack("c")', stdout: '[0]', success_count: 1)
- end
-
- def test_compile_insn_checkmatch
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch])
- begin;
- ary = %w(hello good-bye)
- case 'hello'
- when *ary
- 'world'
- end
- end;
- end
-
- def test_mjit_output
- out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, min_calls: 5)
- assert_equal("MJIT\n" * 5, out)
- assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err)
- assert_match(/^Successful MJIT finish$/, err)
- end
-
- def test_nothing_to_unload_with_jit_wait
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
- begin;
- def a1() a2() end
- def a2() a3() end
- def a3() a4() end
- def a4() a5() end
- def a5() a6() end
- def a6() a7() end
- def a7() a8() end
- def a8() a9() end
- def a9() a10() end
- def a10() a11() end
- def a11() print('hello') end
- a1
- end;
- end
-
- def test_unload_units_on_fiber
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
- begin;
- def a1() a2(false); a2(true) end
- def a2(a) a3(a) end
- def a3(a) a4(a) end
- def a4(a) a5(a) end
- def a5(a) a6(a) end
- def a6(a) a7(a) end
- def a7(a) a8(a) end
- def a8(a) a9(a) end
- def a9(a) a10(a) end
- def a10(a)
- if a
- Fiber.new { a11 }.resume
- end
- end
- def a11() print('hello') end
- a1
- end;
- end
-
- def test_unload_units_and_compaction
- Dir.mktmpdir("jit_test_unload_units_") do |dir|
- # MIN_CACHE_SIZE is 10
- out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, min_calls: 1, max_cache: 10)
- begin;
- i = 0
- while i < 11
- eval(<<-EOS)
- def mjit#{i}
- print #{i}
- end
- mjit#{i}
- EOS
- i += 1
- end
-
- if defined?(fork)
- # test the child does not try to delete files which are deleted by parent,
- # and test possible deadlock on fork during MJIT unload and JIT compaction on child
- Process.waitpid(Process.fork {})
- end
- end;
-
- debug_info = %Q[stdout:\n"""\n#{out}\n"""\n\nstderr:\n"""\n#{err}"""\n]
- assert_equal('012345678910', out, debug_info)
- compactions, errs = err.lines.partition do |l|
- l.match?(/\AJIT compaction \(\d+\.\dms\): Compacted \d+ methods /)
- end
- 10.times do |i|
- assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit#{i}@\(eval\):/, errs[i], debug_info)
- end
-
- assert_equal("No units can be unloaded -- incremented max-cache-size to 11 for --jit-wait\n", errs[10], debug_info)
- assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit10@\(eval\):/, errs[11], debug_info)
- # On --jit-wait, when the number of JIT-ed code reaches --jit-max-cache,
- # it should trigger compaction.
- unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet
- assert_equal(1, compactions.size, debug_info)
- end
-
- if RUBY_PLATFORM.match?(/mswin/)
- # "Permission Denied" error is preventing to remove so file on AppVeyor/RubyCI.
- omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
- else
- # verify .c files are deleted on unload_units
- assert_send([Dir, :empty?, dir], debug_info)
- end
- end
- end
-
- def test_newarraykwsplat_on_stack
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "[nil, [{:type=>:development}]]\n", success_count: 1, insns: %i[newarraykwsplat])
- begin;
- def arr
- [nil, [:type => :development]]
- end
- p arr
- end;
- end
-
- def test_local_stack_on_exception
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
- begin;
- def b
- raise
- rescue
- 2
- end
-
- def a
- # Calling #b should be vm_exec, not direct mjit_exec.
- # Otherwise `1` on local variable would be purged.
- 1 + b
- end
-
- print a
- end;
- end
-
- def test_local_stack_with_sp_motion_by_blockargs
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
- begin;
- def b(base)
- 1
- end
-
- # This method is simple enough to have false in catch_except_p.
- # So local_stack_p would be true in JIT compiler.
- def a
- m = method(:b)
-
- # ci->flag has VM_CALL_ARGS_BLOCKARG and cfp->sp is moved in vm_caller_setup_arg_block.
- # So, for this send insn, JIT-ed code should use cfp->sp instead of local variables for stack.
- Module.module_eval(&m)
- end
-
- print a
- end;
- end
-
- def test_catching_deep_exception
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 4)
- begin;
- def catch_true(paths, prefixes) # catch_except_p: TRUE
- prefixes.each do |prefix| # catch_except_p: TRUE
- paths.each do |path| # catch_except_p: FALSE
- return path
- end
- end
- end
-
- def wrapper(paths, prefixes)
- catch_true(paths, prefixes)
- end
-
- print wrapper(['1'], ['2'])
- end;
- end
-
- def test_inlined_builtin_methods
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 1, min_calls: 2)
- begin;
- def test
- float = 0.0
- float.abs
- float.-@
- float.zero?
- end
- test
- test
- end;
- end
-
- def test_inlined_c_method
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 2, recompile_count: 1, min_calls: 2)
- begin;
- def test(obj, recursive: nil)
- if recursive
- test(recursive)
- end
- obj.to_s
- end
-
- print(test('a')) # set #to_s cc to String#to_s (expecting C method)
- print(test('a')) # JIT with #to_s cc: String#to_s
- # update #to_s cd->cc to Symbol#to_s, then go through the Symbol#to_s cd->cc
- # after checking receiver class using inlined #to_s cc with String#to_s.
- print(test('a', recursive: :foo))
- end;
- end
-
- def test_inlined_exivar
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 3, recompile_count: 1, min_calls: 2)
- begin;
- class Foo < Hash
- def initialize
- @a = :a
- end
-
- def bar
- @a
- end
- end
-
- print(Foo.new.bar)
- print(Foo.new.bar) # compile #initialize, #bar -> recompile #bar
- print(Foo.new.bar) # compile #bar with exivar
- end;
- end
-
- def test_inlined_undefined_ivar
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 3, min_calls: 3)
- begin;
- class Foo
- def initialize
- @a = :a
- end
-
- def bar
- if @b.nil?
- @b = :b
- end
- end
- end
-
- print(Foo.new.bar)
- print(Foo.new.bar)
- print(Foo.new.bar)
- end;
- end
-
- def test_inlined_setivar_frozen
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 2, min_calls: 3)
- begin;
- class A
- def a
- @a = 1
- end
- end
-
- a = A.new
- a.a
- a.a
- a.a
- a.freeze
- begin
- a.a
- rescue FrozenError => e
- p e.class
- end
- end;
- end
-
- def test_inlined_getconstant
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, min_calls: 2)
- begin;
- FOO = 1
- def const
- FOO
- end
- print const
- print const
- end;
- end
-
- def test_attr_reader
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2)
- begin;
- class A
- attr_reader :a, :b
-
- def initialize
- @a = 2
- end
-
- def test
- a
- end
-
- def undefined
- b
- end
- end
-
- a = A.new
- print(a.test * a.test)
- p(a.undefined)
- p(a.undefined)
-
- # redefinition
- def a.test
- 3
- end
-
- print(2 * a.test)
- end;
-
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 1, min_calls: 2)
- begin;
- class Hoge
- attr_reader :foo
-
- def initialize
- @foo = []
- @bar = nil
- end
- end
-
- class Fuga < Hoge
- def initialize
- @bar = nil
- @foo = []
- end
- end
-
- def test(recv)
- recv.foo.empty?
- end
-
- hoge = Hoge.new
- fuga = Fuga.new
-
- test(hoge) # VM: cc set index=1
- test(hoge) # JIT: compile with index=1
- test(fuga) # JIT -> VM: cc set index=2
- print test(hoge) # JIT: should use index=1, not index=2 in cc
- end;
- end
-
- def test_heap_promotion_of_ivar_in_the_middle_of_jit
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\n", success_count: 2, min_calls: 2)
- begin;
- class A
- def initialize
- @iv0 = nil
- @iv1 = []
- @iv2 = nil
- end
-
- def test(add)
- @iv0.nil?
- @iv2.nil?
- add_ivar if add
- @iv1.empty?
- end
-
- def add_ivar
- @iv3 = nil
- end
- end
-
- a = A.new
- p a.test(false)
- p a.test(true)
- end;
- end
-
- def test_jump_to_precompiled_branch
- assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0", success_count: 1, min_calls: 1)
- begin;
- def test(foo)
- ".#{foo unless foo == 1}" if true
- end
- print test(0)
- end;
- end
-
- def test_clean_so
- if RUBY_PLATFORM.match?(/mswin/)
- omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
- end
- Dir.mktmpdir("jit_test_clean_so_") do |dir|
- code = "x = 0; 10.times {|i|x+=i}"
- eval_with_jit({"TMPDIR"=>dir}, code)
- assert_send([Dir, :empty?, dir])
- eval_with_jit({"TMPDIR"=>dir}, code, save_temps: true)
- assert_not_send([Dir, :empty?, dir])
- end
- end
-
- def test_clean_objects_on_exec
- if /mswin|mingw/ =~ RUBY_PLATFORM
- # TODO: check call stack and close handle of code which is not on stack, and remove objects on best-effort basis
- omit 'Removing so file being used does not work on Windows'
- end
- Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir|
- eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1)
- begin;
- def a; end; a
- exec "true"
- end;
- error_message = "Undeleted files:\n #{Dir.glob("#{dir}/*").join("\n ")}\n"
- assert_send([Dir, :empty?, dir], error_message)
- end
- end
-
- def test_lambda_longjmp
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '5', success_count: 1)
- begin;
- fib = lambda do |x|
- return x if x == 0 || x == 1
- fib.call(x-1) + fib.call(x-2)
- end
- print fib.call(5)
- end;
- end
-
- def test_stack_pointer_with_assignment
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1)
- begin;
- 2.times do
- a, _ = nil
- p a
- end
- end;
- end
-
- def test_frame_omitted_inlining
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\n", success_count: 1, min_calls: 2)
- begin;
- class Integer
- remove_method :zero?
- def zero?
- self == 0
- end
- end
-
- 3.times do
- p 0.zero?
- end
- end;
- end
-
- def test_block_handler_with_possible_frame_omitted_inlining
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n", success_count: 2, min_calls: 2)
- begin;
- def multiply(a, b)
- a *= b
- end
-
- 3.times do
- p multiply(7.0, 10.0)
- end
- end;
- end
-
- def test_builtin_frame_omitted_inlining
- assert_eval_with_jit('0.zero?; 0.zero?; 3.times { p 0.zero? }', stdout: "true\ntrue\ntrue\n", success_count: 1, min_calls: 2)
- end
-
- def test_program_counter_with_regexpmatch
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aa", success_count: 1)
- begin;
- 2.times do
- break if /a/ =~ "ab" && !$~[0]
- print $~[0]
- end
- end;
- end
-
- def test_pushed_values_with_opt_aset_with
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "{}{}", success_count: 1)
- begin;
- 2.times do
- print(Thread.current["a"] = {})
- end
- end;
- end
-
- def test_pushed_values_with_opt_aref_with
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1)
- begin;
- 2.times do
- p(Thread.current["a"])
- end
- end;
- end
-
- def test_mjit_pause_wait
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, min_calls: 1)
- begin;
- RubyVM::MJIT.pause
- proc {}.call
- end;
- end
-
- def test_not_cancel_by_tracepoint_class
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 1, min_calls: 2)
- begin;
- TracePoint.new(:class) {}.enable
- 2.times {}
- end;
- end
-
- def test_cancel_by_tracepoint
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, min_calls: 2)
- begin;
- TracePoint.new(:line) {}.enable
- 2.times {}
- end;
- end
-
- def test_caller_locations_without_catch_table
- out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1)
- begin;
- def b # 2
- caller_locations.first # 3
- end # 4
- # 5
- def a # 6
- print # <-- don't leave PC here # 7
- b # 8
- end
- puts a
- puts a
- end;
- lines = out.lines
- assert_equal("-e:8:in `a'\n", lines[0])
- assert_equal("-e:8:in `a'\n", lines[1])
- end
-
- def test_fork_with_mjit_worker_thread
- Dir.mktmpdir("jit_test_fork_with_mjit_worker_thread_") do |dir|
- # min_calls: 2 to skip fork block
- out, err = eval_with_jit({ "TMPDIR" => dir }, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 2, verbose: 1)
- begin;
- def before_fork; end
- def after_fork; end
-
- before_fork; before_fork # the child should not delete this .o file
- pid = Process.fork do # this child should not delete shared .pch file
- sleep 2.0 # to prevent mixing outputs on Solaris
- after_fork; after_fork # this child does not share JIT-ed after_fork with parent
- end
- after_fork; after_fork # this parent does not share JIT-ed after_fork with child
-
- Process.waitpid(pid)
- end;
- success_count = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
- debug_info = "stdout:\n```\n#{out}\n```\n\nstderr:\n```\n#{err}```\n"
- assert_equal(3, success_count, debug_info)
-
- # assert no remove error
- assert_equal("Successful MJIT finish\n" * 2, err.gsub(/^#{JIT_SUCCESS_PREFIX}:[^\n]+\n/, ''), debug_info)
-
- # ensure objects are deleted
- assert_send([Dir, :empty?, dir], debug_info)
- end
- end if defined?(fork)
-
- private
-
- # The shortest way to test one proc
- def assert_compile_once(script, result_inspect:, insns: [], uplevel: 1)
- if script.match?(/\A\n.+\n\z/m)
- script = script.gsub(/^/, ' ')
- else
- script = " #{script} "
- end
- assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1)
- end
-
- # Shorthand for normal test cases
- def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, min_calls: 1, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: [])
- out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls, max_cache: max_cache)
- success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
- recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size
- # Add --mjit-verbose=2 logs for cl.exe because compiler's error message is suppressed
- # for cl.exe with --mjit-verbose=1. See `start_process` in mjit_worker.c.
- if RUBY_PLATFORM.match?(/mswin/) && success_count != success_actual
- out2, err2 = eval_with_jit(script, verbose: 2, min_calls: min_calls, max_cache: max_cache)
- end
-
- # Make sure that the script has insns expected to be tested
- used_insns = method_insns(script)
- insns.each do |insn|
- mark_tested_insn(insn, used_insns: used_insns, uplevel: uplevel + 3)
- end
-
- suffix = "script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}#{(
- "\nstdout(verbose=2 retry):\n#{code_block(out2)}\nstderr(verbose=2 retry):\n#{code_block(err2)}" if out2 || err2
- )}"
- assert_equal(
- success_count, success_actual,
- "Expected #{success_count} times of JIT success, but succeeded #{success_actual} times.\n\n#{suffix}",
- )
- if recompile_count
- assert_equal(
- recompile_count, recompile_actual,
- "Expected #{success_count} times of JIT recompile, but recompiled #{success_actual} times.\n\n#{suffix}",
- )
- end
- if stdout
- assert_equal(stdout, out, "Expected stdout #{out.inspect} to match #{stdout.inspect} with script:\n#{code_block(script)}")
- end
- err_lines = err.lines.reject! do |l|
- l.chomp.empty? || l.match?(/\A#{JIT_SUCCESS_PREFIX}/) || (IGNORABLE_PATTERNS + ignorable_patterns).any? { |pat| pat.match?(l) }
- end
- unless err_lines.empty?
- warn err_lines.join(''), uplevel: uplevel
- end
- end
-
- def mark_tested_insn(insn, used_insns:, uplevel: 1)
- # Currently, this check emits a false-positive warning against opt_regexpmatch2,
- # so the insn is excluded explicitly. See https://bugs.ruby-lang.org/issues/18269
- if !used_insns.include?(insn) && insn != :opt_regexpmatch2
- $stderr.puts
- warn "'#{insn}' insn is not included in the script. Actual insns are: #{used_insns.join(' ')}\n", uplevel: uplevel
- end
- TestMJIT.untested_insns.delete(insn)
- end
-
- # Collect block's insns or defined method's insns, which are expected to be JIT-ed.
- # Note that this intentionally excludes insns in script's toplevel because they are not JIT-ed.
- def method_insns(script)
- insns = []
- RubyVM::InstructionSequence.compile(script).to_a.last.each do |(insn, *args)|
- case insn
- when :send
- insns += collect_insns(args.last)
- when :definemethod, :definesmethod
- insns += collect_insns(args[1])
- when :defineclass
- insns += collect_insns(args[1])
- end
- end
- insns.uniq
- end
-
- # Recursively collect insns in iseq_array
- def collect_insns(iseq_array)
- return [] if iseq_array.nil?
-
- insns = iseq_array.last.select { |x| x.is_a?(Array) }.map(&:first)
- iseq_array.last.each do |(insn, *args)|
- case insn
- when :definemethod, :definesmethod, :send
- insns += collect_insns(args.last)
- end
- end
- insns
- end
-end
diff --git a/test/ruby/test_mjit_debug.rb b/test/ruby/test_mjit_debug.rb
deleted file mode 100644
index 0b50acc68d..0000000000
--- a/test/ruby/test_mjit_debug.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require_relative 'test_mjit'
-
-return unless defined?(TestMJIT)
-return if ENV.key?('APPVEYOR')
-return if ENV.key?('RUBYCI_NICKNAME')
-return if ENV['RUBY_DEBUG']&.include?('ci') # ci.rvm.jp
-return if /mswin/ =~ RUBY_PLATFORM
-
-class TestMJITDebug < TestMJIT
- @@test_suites.delete TestMJIT if self.respond_to? :on_parallel_worker?
-
- def setup
- super
- # let `#eval_with_jit` use --mjit-debug
- @mjit_debug = true
- end
-end
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index c7245ab2db..75d8d909d7 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -253,6 +253,14 @@ class TestModule < Test::Unit::TestCase
assert_operator(Math, :const_defined?, "PI")
assert_not_operator(Math, :const_defined?, :IP)
assert_not_operator(Math, :const_defined?, "IP")
+
+ # Test invalid symbol name
+ # [Bug #20245]
+ EnvUtil.under_gc_stress do
+ assert_raise(EncodingError) do
+ Math.const_defined?("\xC3")
+ end
+ end
end
def each_bad_constants(m, &b)
@@ -994,6 +1002,15 @@ class TestModule < Test::Unit::TestCase
assert_equal([:bClass1], BClass.public_instance_methods(false))
end
+ def test_undefined_instance_methods
+ assert_equal([], AClass.undefined_instance_methods)
+ assert_equal([], BClass.undefined_instance_methods)
+ c = Class.new(AClass) {undef aClass}
+ assert_equal([:aClass], c.undefined_instance_methods)
+ c = Class.new(c)
+ assert_equal([], c.undefined_instance_methods)
+ end
+
def test_s_public
o = (c = Class.new(AClass)).new
assert_raise(NoMethodError, /private method/) {o.aClass1}
@@ -1319,8 +1336,6 @@ class TestModule < Test::Unit::TestCase
end
end
include LangModuleSpecInObject
- module LangModuleTop
- end
puts "ok" if LangModuleSpecInObject::LangModuleTop == LangModuleTop
INPUT
@@ -1462,7 +1477,7 @@ class TestModule < Test::Unit::TestCase
end
%w(object_id __send__ initialize).each do |n|
- assert_in_out_err([], <<-INPUT, [], %r"warning: undefining `#{n}' may cause serious problems$")
+ assert_in_out_err([], <<-INPUT, [], %r"warning: undefining '#{n}' may cause serious problems$")
$VERBOSE = false
Class.new.instance_eval { undef_method(:#{n}) }
INPUT
@@ -1721,6 +1736,8 @@ class TestModule < Test::Unit::TestCase
assert_equal("TestModule::C\u{df}", c.name, '[ruby-core:24600]')
c = Module.new.module_eval("class X\u{df} < Module; self; end")
assert_match(/::X\u{df}:/, c.new.to_s)
+ ensure
+ Object.send(:remove_const, "C\u{df}")
end
@@ -2343,6 +2360,18 @@ class TestModule < Test::Unit::TestCase
assert_equal(:foo, removed)
end
+ def test_frozen_prepend_remove_method
+ [Module, Class].each do |klass|
+ mod = klass.new do
+ prepend(Module.new)
+ def foo; end
+ end
+ mod.freeze
+ assert_raise(FrozenError, '[Bug #19166]') { mod.send(:remove_method, :foo) }
+ assert_equal([:foo], mod.instance_methods(false))
+ end
+ end
+
def test_prepend_class_ancestors
bug6658 = '[ruby-core:45919]'
m = labeled_module("m")
@@ -2849,6 +2878,7 @@ class TestModule < Test::Unit::TestCase
def test_invalid_attr
%W[
+ foo=
foo?
@foo
@@foo
@@ -3153,6 +3183,7 @@ class TestModule < Test::Unit::TestCase
end
def test_redefinition_mismatch
+ omit "Investigating trunk-rjit failure on ci.rvm.jp" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
m = Module.new
m.module_eval "A = 1", __FILE__, line = __LINE__
e = assert_raise_with_message(TypeError, /is not a module/) {
@@ -3254,6 +3285,62 @@ class TestModule < Test::Unit::TestCase
assert_match(/::Foo$/, mod.name, '[Bug #14895]')
end
+ def test_iclass_memory_leak
+ # [Bug #19550]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ mod = Module.new
+ Class.new do
+ include mod
+ end
+ end
+ 1_000.times(&code)
+ PREP
+ 3_000_000.times(&code)
+ CODE
+ end
+
+ def test_complemented_method_entry_memory_leak
+ # [Bug #19894] [Bug #19896]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ $c = Class.new do
+ def foo; end
+ end
+
+ $m = Module.new do
+ refine $c do
+ def foo; end
+ end
+ end
+
+ Class.new do
+ using $m
+
+ def initialize
+ o = $c.new
+ o.method(:foo).unbind
+ end
+ end.new
+ end
+ 1_000.times(&code)
+ PREP
+ 300_000.times(&code)
+ CODE
+ end
+
+ def test_module_clone_memory_leak
+ # [Bug #19901]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Module.new.clone
+ end
+ 1_000.times(&code)
+ PREP
+ 1_000_000.times(&code)
+ CODE
+ end
+
private
def assert_top_method_is_private(method)
@@ -3261,7 +3348,7 @@ class TestModule < Test::Unit::TestCase
methods = singleton_class.private_instance_methods(false)
assert_include(methods, :#{method}, ":#{method} should be private")
- assert_raise_with_message(NoMethodError, "private method `#{method}' called for main:Object") {
+ assert_raise_with_message(NoMethodError, /^private method '#{method}' called for /) {
recv = self
recv.#{method}
}
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb
index 321b7ccab2..6d413e6391 100644
--- a/test/ruby/test_nomethod_error.rb
+++ b/test/ruby/test_nomethod_error.rb
@@ -85,7 +85,7 @@ class TestNoMethodError < Test::Unit::TestCase
bug3237 = '[ruby-core:29948]'
str = "\u2600"
id = :"\u2604"
- msg = "undefined method `#{id}' for \"#{str}\":String"
+ msg = "undefined method '#{id}' for an instance of String"
assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do
str.__send__(id)
end
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index 0593cb535d..ab492743f6 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -324,6 +324,9 @@ class TestNumeric < Test::Unit::TestCase
e = 1.step(10, {by: "1"})
assert_raise(TypeError) {e.next}
assert_raise(TypeError) {e.size}
+ e = 1.step(to: "10")
+ assert_raise(ArgumentError) {e.next}
+ assert_raise(ArgumentError) {e.size}
assert_equal(bignum*2+1, (-bignum).step(bignum, 1).size)
assert_equal(bignum*2, (-bignum).step(bignum-1, 1).size)
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 83208bbcdb..37c09596a2 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -355,6 +355,41 @@ class TestObject < Test::Unit::TestCase
end
end
+ def test_remove_instance_variable_re_embed
+ require "objspace"
+
+ c = Class.new do
+ def a = @a
+
+ def b = @b
+
+ def c = @c
+ end
+
+ o1 = c.new
+ o2 = c.new
+
+ o1.instance_variable_set(:@foo, 5)
+ o1.instance_variable_set(:@a, 0)
+ o1.instance_variable_set(:@b, 1)
+ o1.instance_variable_set(:@c, 2)
+ refute_includes ObjectSpace.dump(o1), '"embedded":true'
+ o1.remove_instance_variable(:@foo)
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ o2.instance_variable_set(:@a, 0)
+ o2.instance_variable_set(:@b, 1)
+ o2.instance_variable_set(:@c, 2)
+ assert_includes ObjectSpace.dump(o2), '"embedded":true'
+
+ assert_equal(0, o1.a)
+ assert_equal(1, o1.b)
+ assert_equal(2, o1.c)
+ assert_equal(0, o2.a)
+ assert_equal(1, o2.b)
+ assert_equal(2, o2.c)
+ end
+
def test_convert_string
o = Object.new
def o.to_s; 1; end
@@ -422,6 +457,18 @@ class TestObject < Test::Unit::TestCase
assert_equal(1+3+5+7+9, n)
end
+ def test_max_shape_variation_with_performance_warnings
+ assert_in_out_err([], <<-INPUT, %w(), /The class Foo reached 8 shape variations, instance variables accesses will be slower and memory usage increased/)
+ $VERBOSE = false
+ Warning[:performance] = true
+
+ class Foo; end
+ 10.times do |i|
+ Foo.new.instance_variable_set(:"@a\#{i}", nil)
+ end
+ INPUT
+ end
+
def test_redefine_method_under_verbose
assert_in_out_err([], <<-INPUT, %w(2), /warning: method redefined; discarding old foo$/)
$VERBOSE = true
@@ -433,12 +480,12 @@ class TestObject < Test::Unit::TestCase
end
def test_redefine_method_which_may_case_serious_problem
- assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `object_id' may cause serious problems$")
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining 'object_id' may cause serious problems$")
$VERBOSE = false
def (Object.new).object_id; end
INPUT
- assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `__send__' may cause serious problems$")
+ assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '__send__' may cause serious problems$")
$VERBOSE = false
def (Object.new).__send__; end
INPUT
@@ -481,7 +528,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NoMethodError, bug2202) {o2.meth2}
%w(object_id __send__ initialize).each do |m|
- assert_in_out_err([], <<-INPUT, %w(:ok), %r"warning: removing `#{m}' may cause serious problems$")
+ assert_in_out_err([], <<-INPUT, %w(:ok), %r"warning: removing '#{m}' may cause serious problems$")
$VERBOSE = false
begin
Class.new.instance_eval { remove_method(:#{m}) }
@@ -853,6 +900,15 @@ class TestObject < Test::Unit::TestCase
x.instance_variable_set(:@bar, 42)
assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect)
+ # Bug: [ruby-core:19167]
+ x = Object.new
+ x.instance_variable_set(:@foo, NilClass)
+ assert_match(/\A#<Object:0x\h+ @foo=NilClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, TrueClass)
+ assert_match(/\A#<Object:0x\h+ @foo=TrueClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, FalseClass)
+ assert_match(/\A#<Object:0x\h+ @foo=FalseClass>\z/, x.inspect)
+
# #inspect does not call #to_s anymore
feature6130 = '[ruby-core:43238]'
x = Object.new
@@ -925,6 +981,19 @@ class TestObject < Test::Unit::TestCase
end
end
+ def test_singleton_class_freeze
+ x = Object.new
+ xs = x.singleton_class
+ x.freeze
+ assert_predicate(xs, :frozen?)
+
+ y = Object.new
+ ys = y.singleton_class
+ ys.prepend(Module.new)
+ y.freeze
+ assert_predicate(ys, :frozen?, '[Bug #19169]')
+ end
+
def test_redef_method_missing
bug5473 = '[ruby-core:40287]'
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|
@@ -993,4 +1062,13 @@ class TestObject < Test::Unit::TestCase
end
EOS
end
+
+ def test_frozen_inspect
+ obj = Object.new
+ obj.instance_variable_set(:@a, "a")
+ ins = obj.inspect
+ obj.freeze
+
+ assert_equal(ins, obj.inspect)
+ end
end
diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb
index e0f9eecd11..1c97bd517e 100644
--- a/test/ruby/test_objectspace.rb
+++ b/test/ruby/test_objectspace.rb
@@ -65,6 +65,14 @@ End
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)}
end
+ def test_id2ref_invalid_symbol_id
+ # RB_STATIC_SYM_P checks for static symbols by checking that the bottom
+ # 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make
+ # sure that the bottom 8 bits remain unchanged.
+ msg = /is not symbol id value/
+ assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + 256) }
+ end
+
def test_count_objects
h = {}
ObjectSpace.count_objects(h)
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 80c3c3860b..a7a0582dbb 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -16,6 +16,18 @@ class TestRubyOptimization < Test::Unit::TestCase
end;
end
+ def assert_performance_warning(klass, method)
+ assert_in_out_err([], "#{<<-"begin;"}\n#{<<~"end;"}", [], ["-:4: warning: Redefining '#{klass}##{method}' disables interpreter and JIT optimizations"])
+ begin;
+ Warning[:performance] = true
+ class #{klass}
+ undef #{method}
+ def #{method}
+ end
+ end
+ end;
+ end
+
def disasm(name)
RubyVM::InstructionSequence.of(method(name)).disasm
end
@@ -23,102 +35,122 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_fixnum_plus
assert_equal 21, 10 + 11
assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11')
+ assert_performance_warning('Integer', '+')
end
def test_fixnum_minus
assert_equal 5, 8 - 3
assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3')
+ assert_performance_warning('Integer', '-')
end
def test_fixnum_mul
assert_equal 15, 3 * 5
assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5')
+ assert_performance_warning('Integer', '*')
end
def test_fixnum_div
assert_equal 3, 15 / 5
assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5')
+ assert_performance_warning('Integer', '/')
end
def test_fixnum_mod
assert_equal 1, 8 % 7
assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7')
+ assert_performance_warning('Integer', '%')
end
def test_fixnum_lt
assert_equal true, 1 < 2
assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2')
+ assert_performance_warning('Integer', '<')
end
def test_fixnum_le
assert_equal true, 1 <= 2
assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2')
+ assert_performance_warning('Integer', '<=')
end
def test_fixnum_gt
assert_equal false, 1 > 2
assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2')
+ assert_performance_warning('Integer', '>')
end
def test_fixnum_ge
assert_equal false, 1 >= 2
assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2')
+ assert_performance_warning('Integer', '>=')
end
def test_float_plus
assert_equal 4.0, 2.0 + 2.0
assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0')
+ assert_performance_warning('Float', '+')
end
def test_float_minus
assert_equal 4.0, 2.0 + 2.0
- assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0')
+ assert_redefine_method('Float', '-', 'assert_equal 2.0, 4.0 - 2.0')
+ assert_performance_warning('Float', '-')
end
def test_float_mul
assert_equal 29.25, 4.5 * 6.5
assert_redefine_method('Float', '*', 'assert_equal 6.5, 4.5 * 6.5')
+ assert_performance_warning('Float', '*')
end
def test_float_div
assert_in_delta 0.63063063063063063, 4.2 / 6.66
assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]")
+ assert_performance_warning('Float', '/')
end
def test_float_lt
assert_equal true, 1.1 < 2.2
assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2')
+ assert_performance_warning('Float', '<')
end
def test_float_le
assert_equal true, 1.1 <= 2.2
assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2')
+ assert_performance_warning('Float', '<=')
end
def test_float_gt
assert_equal false, 1.1 > 2.2
assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2')
+ assert_performance_warning('Float', '>')
end
def test_float_ge
assert_equal false, 1.1 >= 2.2
assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2')
+ assert_performance_warning('Float', '>=')
end
def test_string_length
assert_equal 6, "string".length
assert_redefine_method('String', 'length', 'assert_nil "string".length')
+ assert_performance_warning('String', 'length')
end
def test_string_size
assert_equal 6, "string".size
assert_redefine_method('String', 'size', 'assert_nil "string".size')
+ assert_performance_warning('String', 'size')
end
def test_string_empty?
assert_equal true, "".empty?
assert_equal false, "string".empty?
assert_redefine_method('String', 'empty?', 'assert_nil "string".empty?')
+ assert_performance_warning('String', 'empty?')
end
def test_string_plus
@@ -127,39 +159,50 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal "x", "" + "x"
assert_equal "ab", "a" + "b"
assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"')
+ assert_performance_warning('String', '+')
end
def test_string_succ
assert_equal 'b', 'a'.succ
assert_equal 'B', 'A'.succ
+ assert_performance_warning('String', 'succ')
end
def test_string_format
assert_equal '2', '%d' % 2
assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2')
+ assert_performance_warning('String', '%')
end
def test_string_freeze
assert_equal "foo", "foo".freeze
assert_equal "foo".freeze.object_id, "foo".freeze.object_id
assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze')
+ assert_performance_warning('String', 'freeze')
end
def test_string_uminus
assert_same "foo".freeze, -"foo"
assert_redefine_method('String', '-@', 'assert_nil(-"foo")')
+ assert_performance_warning('String', '-@')
end
def test_array_min
assert_equal 1, [1, 2, 4].min
assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)')
assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)')
+ assert_performance_warning('Array', 'min')
end
def test_array_max
assert_equal 4, [1, 2, 4].max
assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)')
assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)')
+ assert_performance_warning('Array', 'max')
+ end
+
+ def test_array_hash
+ assert_performance_warning('Array', 'hash')
end
def test_trace_optimized_methods
@@ -235,6 +278,8 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal :b, (b #{m} "b").to_sym
end
end
+
+ assert_performance_warning('String', '==')
end
def test_string_ltlt
@@ -243,50 +288,59 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal "x", "" << "x"
assert_equal "ab", "a" << "b"
assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"')
+ assert_performance_warning('String', '<<')
end
def test_fixnum_and
assert_equal 1, 1&3
assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3')
+ assert_performance_warning('Integer', '&')
end
def test_fixnum_or
assert_equal 3, 1|3
assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1')
+ assert_performance_warning('Integer', '|')
end
def test_array_plus
assert_equal [1,2], [1]+[2]
assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]')
+ assert_performance_warning('Array', '+')
end
def test_array_minus
assert_equal [2], [1,2] - [1]
assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]')
+ assert_performance_warning('Array', '-')
end
def test_array_length
assert_equal 0, [].length
assert_equal 3, [1,2,3].length
assert_redefine_method('Array', 'length', 'assert_nil([].length); assert_nil([1,2,3].length)')
+ assert_performance_warning('Array', 'length')
end
def test_array_empty?
assert_equal true, [].empty?
assert_equal false, [1,2,3].empty?
assert_redefine_method('Array', 'empty?', 'assert_nil([].empty?); assert_nil([1,2,3].empty?)')
+ assert_performance_warning('Array', 'empty?')
end
def test_hash_length
assert_equal 0, {}.length
assert_equal 1, {1=>1}.length
assert_redefine_method('Hash', 'length', 'assert_nil({}.length); assert_nil({1=>1}.length)')
+ assert_performance_warning('Hash', 'length')
end
def test_hash_empty?
assert_equal true, {}.empty?
assert_equal false, {1=>1}.empty?
assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)')
+ assert_performance_warning('Hash', 'empty?')
end
def test_hash_aref_with
@@ -297,6 +351,7 @@ class TestRubyOptimization < Test::Unit::TestCase
h = { "foo" => 1 }
assert_equal "foo", h["foo"]
end;
+ assert_performance_warning('Hash', '[]')
end
def test_hash_aset_with
@@ -308,6 +363,7 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_equal 1, h["foo"] = 1, "assignment always returns value set"
assert_nil h["foo"]
end;
+ assert_performance_warning('Hash', '[]=')
end
class MyObj
@@ -437,6 +493,31 @@ class TestRubyOptimization < Test::Unit::TestCase
message(bug12565) {disasm(:add_one_and_two)})
end
+ def test_c_func_with_sp_offset_under_tailcall
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ def calc_one_plus_two
+ 1 + 2.abs
+ end
+
+ def one_plus_two
+ calc_one_plus_two
+ end
+ end;
+ assert_equal(3, one_plus_two)
+ end
+
+ def test_tailcall_and_post_arg
+ tailcall(<<~RUBY)
+ def ret_const = :ok
+
+ def post_arg(_a = 1, _b) = ret_const
+ RUBY
+
+ # YJIT probably uses a fallback on the call to post_arg
+ assert_equal(:ok, post_arg(0))
+ end
+
def test_tailcall_interrupted_by_sigint
bug12576 = 'ruby-core:76327'
script = "#{<<-"begin;"}\n#{<<~'end;'}"
@@ -510,7 +591,7 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_tailcall_not_to_grow_stack
- omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
bug16161 = '[ruby-core:94881]'
tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
@@ -552,7 +633,7 @@ class TestRubyOptimization < Test::Unit::TestCase
begin;
class String
undef freeze
- def freeze
+ def freeze(&)
block_given?
end
end
@@ -614,6 +695,7 @@ class TestRubyOptimization < Test::Unit::TestCase
[ nil, true, false, 0.1, :sym, 'str', 0xffffffffffffffff ].each do |v|
k = v.class.to_s
assert_redefine_method(k, '===', "assert_equal(#{v.inspect} === 0, 0)")
+ assert_performance_warning(k, '===')
end
end
diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb
index 9738f82b7e..1ce46e8916 100644
--- a/test/ruby/test_pack.rb
+++ b/test/ruby/test_pack.rb
@@ -1,25 +1,44 @@
# coding: US-ASCII
# frozen_string_literal: false
require 'test/unit'
+require 'rbconfig'
+require 'rbconfig/sizeof'
class TestPack < Test::Unit::TestCase
+ # Note: the size of intptr_t and uintptr_t should be equal.
+ J_SIZE = RbConfig::SIZEOF['uintptr_t']
+
def test_pack
- $format = "c2x5CCxsdils_l_a6";
+ format = "c2x5CCxsdils_l_a6";
# Need the expression in here to force ary[5] to be numeric. This avoids
# test2 failing because ary2 goes str->numeric->str and ary does not.
ary = [1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,-32767,-123456,"abcdef"]
- $x = ary.pack($format)
- ary2 = $x.unpack($format)
+ x = ary.pack(format)
+ ary2 = x.unpack(format)
assert_equal(ary.length, ary2.length)
assert_equal(ary.join(':'), ary2.join(':'))
- assert_match(/def/, $x)
+ assert_match(/def/, x)
+
+ x = [-1073741825]
+ assert_equal(x, x.pack("q").unpack("q"))
+
+ x = [-1]
+ assert_equal(x, x.pack("l").unpack("l"))
+ end
+
+ def test_ascii_incompatible
+ assert_raise(Encoding::CompatibilityError) do
+ ["foo"].pack("u".encode("UTF-32BE"))
+ end
- $x = [-1073741825]
- assert_equal($x, $x.pack("q").unpack("q"))
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack("C".encode("UTF-32BE"))
+ end
- $x = [-1]
- assert_equal($x, $x.pack("l").unpack("l"))
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack1("C".encode("UTF-32BE"))
+ end
end
def test_pack_n
@@ -79,11 +98,11 @@ class TestPack < Test::Unit::TestCase
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("L"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("q"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("Q"+mod))
- psize = [nil].pack('p').bytesize
- if psize == 4
+ case J_SIZE
+ when 4
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("j"+mod))
assert_equal("\x01\x02\x03\x04", [0x01020304].pack("J"+mod))
- elsif psize == 8
+ when 8
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("j"+mod))
assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("J"+mod))
end
@@ -95,10 +114,11 @@ class TestPack < Test::Unit::TestCase
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("l!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("L!"+mod))
- if psize == 4
+ case J_SIZE
+ when 4
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("j!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("J!"+mod))
- elsif psize == 8
+ when 8
assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("j!"+mod))
assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("J!"+mod))
end
@@ -127,11 +147,11 @@ class TestPack < Test::Unit::TestCase
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("L"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("q"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("Q"+mod))
- psize = [nil].pack('p').bytesize
- if psize == 4
+ case J_SIZE
+ when 4
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("j"+mod))
assert_equal("\x04\x03\x02\x01", [0x01020304].pack("J"+mod))
- elsif psize == 8
+ when 8
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("j"+mod))
assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("J"+mod))
end
@@ -143,10 +163,11 @@ class TestPack < Test::Unit::TestCase
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("l!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("L!"+mod))
- if psize == 4
+ case J_SIZE
+ when 4
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("j!"+mod))
assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("J!"+mod))
- elsif psize == 8
+ when 8
assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("j!"+mod))
assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("J!"+mod))
end
@@ -182,8 +203,8 @@ class TestPack < Test::Unit::TestCase
end
def test_integer_endian_explicit
- _integer_big_endian('>')
- _integer_little_endian('<')
+ _integer_big_endian('>')
+ _integer_little_endian('<')
end
def test_pack_U
@@ -428,7 +449,6 @@ class TestPack < Test::Unit::TestCase
assert_operator(4, :<=, [1].pack("L!").bytesize)
end
- require 'rbconfig'
def test_pack_unpack_qQ
s1 = [578437695752307201, -506097522914230529].pack("q*")
s2 = [578437695752307201, 17940646550795321087].pack("Q*")
@@ -451,10 +471,8 @@ class TestPack < Test::Unit::TestCase
end if RbConfig::CONFIG['HAVE_LONG_LONG']
def test_pack_unpack_jJ
- # Note: we assume that the size of intptr_t and uintptr_t equals to the size
- # of real pointer.
- psize = [nil].pack("p").bytesize
- if psize == 4
+ case J_SIZE
+ when 4
s1 = [67305985, -50462977].pack("j*")
s2 = [67305985, 4244504319].pack("J*")
assert_equal(s1, s2)
@@ -468,7 +486,7 @@ class TestPack < Test::Unit::TestCase
assert_equal(4, [1].pack("j").bytesize)
assert_equal(4, [1].pack("J").bytesize)
- elsif psize == 8
+ when 8
s1 = [578437695752307201, -506097522914230529].pack("j*")
s2 = [578437695752307201, 17940646550795321087].pack("J*")
assert_equal(s1, s2)
@@ -763,58 +781,32 @@ EXPECTED
end
def test_pack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
- assert_equal "\000", [0].pack("*U")
- end
-
- $VERBOSE = true
-
- _, err = capture_output do
+ assert_raise(ArgumentError, %r%unknown pack directive '\*' in '\*U'$%) do
assert_equal "\000", [0].pack("*U")
end
-
- assert_match %r%unknown pack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_unpack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
+ assert_raise(ArgumentError, %r%unknown unpack directive '\*' in '\*U'$%) do
assert_equal [0], "\000".unpack("*U")
end
-
- $VERBOSE = true
-
- _, err = capture_output do
- assert_equal [0], "\000".unpack("*U")
- end
-
- assert_match %r%unknown unpack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_invalid_warning
- assert_warning(/unknown pack directive ',' in ','/) {
+ assert_raise(ArgumentError, /unknown pack directive ',' in ','/) {
[].pack(",")
}
- assert_warning(/\A[ -~]+\Z/) {
+ assert_raise(ArgumentError, /\A[ -~]+\Z/) {
[].pack("\x7f")
}
- assert_warning(/\A(.* in '\u{3042}'\n)+\z/) {
+ assert_raise(ArgumentError, /\A(.* in '\u{3042}'\n)+\z/) {
[].pack("\u{3042}")
}
- assert_warning(/\A.* in '.*U'\Z/) {
+ assert_raise(ArgumentError, /\A.* in '.*U'\Z/) {
assert_equal "\000", [0].pack("\0U")
}
- assert_warning(/\A.* in '.*U'\Z/) {
+ assert_raise(ArgumentError, /\A.* in '.*U'\Z/) {
"\000".unpack("\0U")
}
end
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index 2841e20f6d..fe649cddb9 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -14,6 +14,7 @@ class TestParse < Test::Unit::TestCase
def test_error_line
assert_syntax_error('------,,', /\n\z/, 'Message to pipe should end with a newline')
+ assert_syntax_error("{hello\n world}", /hello/)
end
def test_else_without_rescue
@@ -377,10 +378,10 @@ class TestParse < Test::Unit::TestCase
def assert_disallowed_variable(type, noname, invalid)
noname.each do |name|
- assert_syntax_error("proc{a = #{name} }", "`#{noname[0]}' without identifiers is not allowed as #{type} variable name")
+ assert_syntax_error("proc{a = #{name} }", "'#{noname[0]}' without identifiers is not allowed as #{type} variable name")
end
invalid.each do |name|
- assert_syntax_error("proc {a = #{name} }", "`#{name}' is not allowed as #{type} variable name")
+ assert_syntax_error("proc {a = #{name} }", "'#{name}' is not allowed as #{type} variable name")
end
end
@@ -452,10 +453,44 @@ class TestParse < Test::Unit::TestCase
end
def test_define_singleton_error
- assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /singleton method for literals/) do
- begin;
- def ("foo").foo; end
- end;
+ msg = /singleton method for literals/
+ assert_parse_error(%q[def ("foo").foo; end], msg)
+ assert_parse_error(%q[def (1).foo; end], msg)
+ assert_parse_error(%q[def ((1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((;1)).foo; end], msg)
+ assert_parse_error(%q[def ((1+1;1)).foo; end], msg)
+ assert_parse_error(%q[def ((%s();1)).foo; end], msg)
+ assert_parse_error(%q[def ((%w();1)).foo; end], msg)
+ assert_parse_error(%q[def ("#{42}").foo; end], msg)
+ assert_parse_error(%q[def (:"#{42}").foo; end], msg)
+ end
+
+ def test_flip_flop
+ all_assertions_foreach(nil,
+ ['(cond1..cond2)', true],
+ ['((cond1..cond2))', true],
+
+ # '(;;;cond1..cond2)', # don't care
+
+ '(1; cond1..cond2)',
+ '(%s(); cond1..cond2)',
+ '(%w(); cond1..cond2)',
+ '(1; (2; (3; 4; cond1..cond2)))',
+ '(1+1; cond1..cond2)',
+ ) do |code, pass|
+ code = code.sub("cond1", "n==4").sub("cond2", "n==5")
+ if pass
+ assert_equal([4,5], eval("(1..9).select {|n| true if #{code}}"))
+ else
+ assert_raise_with_message(ArgumentError, /bad value for range/, code) {
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ eval("[4].each {|n| true if #{code}}")
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ }
+ end
end
end
@@ -463,6 +498,10 @@ class TestParse < Test::Unit::TestCase
t = Object.new
a = []
blk = proc {|x| a << x }
+
+ # Prevent an "assigned but unused variable" warning
+ _ = blk
+
def t.[](_)
yield(:aref)
nil
@@ -472,16 +511,16 @@ class TestParse < Test::Unit::TestCase
end
def t.dummy(_)
end
- eval <<-END, nil, __FILE__, __LINE__+1
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index/)
+ begin;
t[42, &blk] ||= 42
- END
- assert_equal([:aref, :aset], a)
- a.clear
- eval <<-END, nil, __FILE__, __LINE__+1
- t[42, &blk] ||= t.dummy 42 # command_asgn test
- END
- assert_equal([:aref, :aset], a)
- blk
+ end;
+
+ assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index/)
+ begin;
+ t[42, &blk] ||= t.dummy 42 # command_asgn test
+ end;
end
def test_backquote
@@ -492,7 +531,7 @@ class TestParse < Test::Unit::TestCase
def t.`(x); "foo" + x + "bar"; end
END
end
- a = b = nil
+ a = b = c = nil
assert_nothing_raised do
eval <<-END, nil, __FILE__, __LINE__+1
a = t.` "zzz"
@@ -500,10 +539,12 @@ class TestParse < Test::Unit::TestCase
END
t.instance_eval <<-END, __FILE__, __LINE__+1
b = `zzz`
+ c = %x(ccc)
END
end
assert_equal("foozzzbar", a)
assert_equal("foozzzbar", b)
+ assert_equal("foocccbar", c)
end
def test_carrige_return
@@ -577,6 +618,10 @@ class TestParse < Test::Unit::TestCase
assert_equal(' ^~~~~'"\n", e.message.lines.last)
e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax')
assert_equal(' ^~~~~'"\n", e.message.lines.last)
+
+ e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax')
+ assert_match(/^\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )$/x, e.message.lines.last)
+ assert_not_include(e.message, "invalid multibyte char")
end
def test_question
@@ -607,6 +652,8 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax')
+
+ assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF"))
end
def test_percent
@@ -640,6 +687,7 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error(':@@1', /is not allowed/)
assert_syntax_error(':@', /is not allowed/)
assert_syntax_error(':@1', /is not allowed/)
+ assert_syntax_error(':$01234', /is not allowed/)
end
def test_parse_string
@@ -680,6 +728,16 @@ FOO
eval "x = <<""FOO\r\n1\r\nFOO"
end
assert_equal("1\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
+
+ assert_nothing_raised do
+ x = eval "<<-' FOO'\n""[Bug #19539]\n"" FOO\n"
+ end
+ assert_equal("[Bug #19539]\n", x)
end
def test_magic_comment
@@ -715,6 +773,54 @@ x = __ENCODING__
END
end
assert_equal(__ENCODING__, x)
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_external(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = external
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ EnvUtil.with_default_internal(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1}
+# coding = internal
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = filesystem
+x = __ENCODING__
+ END
+ end
+
+ assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = locale
+x = __ENCODING__
+ END
+ end
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding: foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding: foo\n")
+ assert_include(message, " ^")
+
+ e = assert_raise(ArgumentError) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+# coding = foo
+ END
+ end
+
+ message = e.message.gsub(/\033\[.*?m/, "")
+ assert_include(message, "# coding = foo\n")
+ assert_include(message, " ^")
end
def test_utf8_bom
@@ -758,7 +864,7 @@ x = __ENCODING__
def test_float
assert_predicate(assert_warning(/out of range/) {eval("1e10000")}, :infinite?)
- assert_syntax_error('1_E', /trailing `_'/)
+ assert_syntax_error('1_E', /trailing '_'/)
assert_syntax_error('1E1E1', /unexpected constant/)
end
@@ -786,7 +892,7 @@ x = __ENCODING__
def test_invalid_char
bug10117 = '[ruby-core:64243] [Bug #10117]'
- invalid_char = /Invalid char `\\x01'/
+ invalid_char = /Invalid char '\\x01'/
x = 1
assert_in_out_err(%W"-e \x01x", "", [], invalid_char, bug10117)
assert_syntax_error("\x01x", invalid_char, bug10117)
@@ -820,24 +926,9 @@ x = __ENCODING__
assert_syntax_error("$& = 1", /Can't set variable/)
end
- def test_arg_concat
- o = Object.new
- class << o; self; end.instance_eval do
- define_method(:[]=) {|*r, &b| b.call(r) }
- end
- r = nil
- assert_nothing_raised do
- eval <<-END, nil, __FILE__, __LINE__+1
- o[&proc{|x| r = x }] = 1
- END
- end
- assert_equal([1], r)
- end
-
def test_void_expr_stmts_value
x = 1
useless_use = /useless use/
- unused = /unused/
assert_nil assert_warning(useless_use) {eval("x; nil")}
assert_nil assert_warning(useless_use) {eval("1+1; nil")}
assert_nil assert_warning('') {eval("1.+(1); nil")}
@@ -845,10 +936,10 @@ x = __ENCODING__
assert_nil assert_warning(useless_use) {eval("::TestParse; nil")}
assert_nil assert_warning(useless_use) {eval("x..x; nil")}
assert_nil assert_warning(useless_use) {eval("x...x; nil")}
- assert_nil assert_warning(unused) {eval("self; nil")}
- assert_nil assert_warning(unused) {eval("nil; nil")}
- assert_nil assert_warning(unused) {eval("true; nil")}
- assert_nil assert_warning(unused) {eval("false; nil")}
+ assert_nil assert_warning(useless_use) {eval("self; nil")}
+ assert_nil assert_warning(useless_use) {eval("nil; nil")}
+ assert_nil assert_warning(useless_use) {eval("true; nil")}
+ assert_nil assert_warning(useless_use) {eval("false; nil")}
assert_nil assert_warning(useless_use) {eval("defined?(1); nil")}
assert_equal 1, x
@@ -856,13 +947,15 @@ x = __ENCODING__
end
def test_assign_in_conditional
- assert_warning(/`= literal' in conditional/) do
+ # multiple assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
(x, y = 1, 2) ? 1 : 2
END
end
- assert_warning(/`= literal' in conditional/) do
+ # instance variable assignment
+ assert_warning(/'= literal' in conditional/) do
eval <<-END, nil, __FILE__, __LINE__+1
if @x = true
1
@@ -871,6 +964,71 @@ x = __ENCODING__
end
END
end
+
+ # local variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ def m
+ if x = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # global variable assignment
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if $x = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
+
+ # dynamic variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ y = 1
+
+ 1.times do
+ if y = true
+ 1
+ else
+ 2
+ end
+ end
+ END
+ end
+
+ # class variable assignment
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ c = Class.new
+ class << c
+ if @@a = 1
+ end
+ end
+ END
+ end
+
+ # constant declaration
+ assert_separately([], <<-RUBY)
+ assert_warning(/'= literal' in conditional/) do
+ eval <<-END, nil, __FILE__, __LINE__+1
+ if Const = true
+ 1
+ else
+ 2
+ end
+ END
+ end
+ RUBY
end
def test_literal_in_conditional
@@ -947,6 +1105,20 @@ x = __ENCODING__
assert_warning('') {o.instance_eval("def marg2((a)); nil; end")}
end
+ def test_parsing_begin_statement_inside_method_definition
+ assert_equal :bug_20234, eval("def (begin;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;ensure;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+ assert_equal :bug_20234, eval("def (begin;rescue;else;end).bug_20234; end")
+ NilClass.remove_method(:bug_20234)
+
+ assert_raise(SyntaxError) { eval("def (begin;else;end).bug_20234; end") }
+ assert_raise(SyntaxError) { eval("def (begin;ensure;else;end).bug_20234; end") }
+ end
+
def test_named_capture_conflict
a = 1
assert_warning('') {eval("a = 1; /(?<a>)/ =~ ''")}
@@ -954,6 +1126,30 @@ x = __ENCODING__
assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")}
end
+ def test_named_capture_in_block
+ all_assertions_foreach(nil,
+ '(/(?<a>.*)/)',
+ '(;/(?<a>.*)/)',
+ '(%s();/(?<a>.*)/)',
+ '(%w();/(?<a>.*)/)',
+ '(1; (2; 3; (4; /(?<a>.*)/)))',
+ '(1+1; /(?<a>.*)/)',
+ '/#{""}(?<a>.*)/',
+ ) do |code, pass|
+ token = Random.bytes(4).unpack1("H*")
+ if pass
+ assert_equal(token, eval("#{code} =~ #{token.dump}; a"))
+ else
+ verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context"
+ begin
+ assert_nil(eval("#{code} =~ #{token.dump}; defined?(a)"), code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
+ end
+
def test_rescue_in_command_assignment
bug = '[ruby-core:75621] [Bug #12402]'
all_assertions(bug) do |a|
@@ -1041,6 +1237,22 @@ x = __ENCODING__
assert_syntax_error(" 0b\n", /\^/)
end
+ def test_unclosed_unicode_escape_at_eol_bug_19750
+ assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_syntax_error("/\\u", /too short escape sequence/)
+ assert_syntax_error("/\\u{", /unterminated regexp meets end of file/)
+ assert_syntax_error("/\\u{\\n", /invalid Unicode list/)
+ assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/)
+ re = eval("/a#\\u{\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ re = eval("/a#\\u\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ end;
+ end
+
def test_error_def_in_argument
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
@@ -1084,31 +1296,40 @@ x = __ENCODING__
end;
end
- def test_heredoc_interpolation
- var = 1
+ def test_heredoc_interpolation
+ var = 1
- v1 = <<~HEREDOC
- something
- #{"/#{var}"}
- HEREDOC
+ v1 = <<~HEREDOC
+ something
+ #{"/#{var}"}
+ HEREDOC
- v2 = <<~HEREDOC
- something
- #{_other = "/#{var}"}
- HEREDOC
+ v2 = <<~HEREDOC
+ something
+ #{_other = "/#{var}"}
+ HEREDOC
- v3 = <<~HEREDOC
- something
- #{("/#{var}")}
- HEREDOC
+ v3 = <<~HEREDOC
+ something
+ #{("/#{var}")}
+ HEREDOC
- assert_equal "something\n/1\n", v1
- assert_equal "something\n/1\n", v2
- assert_equal "something\n/1\n", v3
- assert_equal v1, v2
- assert_equal v2, v3
- assert_equal v1, v3
- end
+ assert_equal "something\n/1\n", v1
+ assert_equal "something\n/1\n", v2
+ assert_equal "something\n/1\n", v3
+ assert_equal v1, v2
+ assert_equal v2, v3
+ assert_equal v1, v3
+ end
+
+ def test_heredoc_unterminated_interpolation
+ code = <<~'HEREDOC'
+ <<A+1
+ #{
+ HEREDOC
+
+ assert_syntax_error(code, /can't find string "A"/)
+ end
def test_unexpected_token_error
assert_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/)
@@ -1118,6 +1339,8 @@ x = __ENCODING__
assert_syntax_error('0000xyz', /^ \^~~\Z/)
assert_syntax_error('1.2i1.1', /^ \^~~\Z/)
assert_syntax_error('1.2.3', /^ \^~\Z/)
+ assert_syntax_error('1.', /unexpected end-of-input/)
+ assert_syntax_error('1e', /expecting end-of-input/)
end
def test_truncated_source_line
@@ -1237,10 +1460,23 @@ x = __ENCODING__
def test_void_value_in_rhs
w = "void value expression"
- ["x = return 1", "x = return, 1", "x = 1, return", "x, y = return"].each do |code|
+ [
+ "x = return 1", "x = return, 1", "x = 1, return", "x, y = return",
+ "x = begin return ensure end",
+ "x = begin ensure return end",
+ "x = begin return ensure return end",
+ "x = begin return; rescue; return end",
+ "x = begin return; rescue; return; else return end",
+ ].each do |code|
ex = assert_syntax_error(code, w)
assert_equal(1, ex.message.scan(w).size, ->{"same #{w.inspect} warning should be just once\n#{w.message}"})
end
+ [
+ "x = begin return; rescue; end",
+ "x = begin return; rescue; return; else end",
+ ].each do |code|
+ assert_valid_syntax(code)
+ end
end
def eval_separately(code)
@@ -1304,6 +1540,24 @@ x = __ENCODING__
assert_not_ractor_shareable(obj)
assert_equal obj, a
assert !obj.equal?(a)
+
+ bug_20339 = '[ruby-core:117186] [Bug #20339]'
+ bug_20341 = '[ruby-core:117197] [Bug #20341]'
+ a, b = eval_separately(<<~'end;')
+ # shareable_constant_value: literal
+ foo = 1
+ bar = 2
+ A = { foo => bar }
+ B = [foo, bar]
+ [A, B]
+ end;
+
+ assert_ractor_shareable(a)
+ assert_ractor_shareable(b)
+ assert_equal([1], a.keys, bug_20339)
+ assert_equal([2], a.values, bug_20339)
+ assert_equal(1, b[0], bug_20341)
+ assert_equal(2, b[1], bug_20341)
end
def test_shareable_constant_value_nested
@@ -1325,12 +1579,71 @@ x = __ENCODING__
end
def test_shareable_constant_value_unshareable_literal
- assert_raise_separately(Ractor::IsolationError, /unshareable/,
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to C/,
"#{<<~"begin;"}\n#{<<~'end;'}")
begin;
# shareable_constant_value: literal
C = ["Not " + "shareable"]
end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C = ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C = ["Not " + "shareable"]
+ end
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do
+ # shareable_constant_value: literal
+ ::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ B::C ||= ["Not " + "shareable"]
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do
+ # shareable_constant_value: literal
+ ::B = Class.new
+ ::B::C ||= ["Not " + "shareable"]
+ end
+ end;
+
+ assert_raise_separately(Ractor::IsolationError, /unshareable object to ...::C/,
+ "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # shareable_constant_value: literal
+ B = Class.new
+ def self.expr; B; end
+ expr::C ||= ["Not " + "shareable"]
+ end;
end
def test_shareable_constant_value_nonliteral
@@ -1359,9 +1672,75 @@ x = __ENCODING__
end;
end
+ def test_if_after_class
+ assert_valid_syntax('module if true; Object end::Kernel; end')
+ assert_valid_syntax('module while true; break Object end::Kernel; end')
+ assert_valid_syntax('class if true; Object end::Kernel; end')
+ assert_valid_syntax('class while true; break Object end::Kernel; end')
+ end
+
+ def test_escaped_space
+ assert_syntax_error('x = \ 42', /escaped space/)
+ end
+
+ def test_label
+ expected = {:foo => 1}
+
+ code = '{"foo": 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ code = '{foo: 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ class << (obj = Object.new)
+ attr_reader :arg
+ def set(arg)
+ @arg = arg
+ end
+ end
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set foo:
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set "foo":
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+ end
+
+ def test_ungettable_gvar
+ assert_syntax_error('$01234', /not allowed/)
+ assert_syntax_error('"#$01234"', /not allowed/)
+ end
+
=begin
def test_past_scope_variable
assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
end
=end
+
+ def assert_parse(code)
+ assert_kind_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.parse(code))
+ end
+
+ def assert_parse_error(code, message)
+ assert_raise_with_message(SyntaxError, message) do
+ $VERBOSE, verbose_bak = nil, $VERBOSE
+ begin
+ RubyVM::AbstractSyntaxTree.parse(code)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ end
+ end
end
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index 7531466f91..db6ad06b82 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -9,14 +9,14 @@ class TestPatternMatching < Test::Unit::TestCase
end
def setup
- if defined?(DidYouMean)
+ if defined?(DidYouMean.formatter=nil)
@original_formatter = DidYouMean.formatter
DidYouMean.formatter = NullFormatter.new
end
end
def teardown
- if defined?(DidYouMean)
+ if defined?(DidYouMean.formatter=nil)
DidYouMean.formatter = @original_formatter
end
end
@@ -109,16 +109,12 @@ class TestPatternMatching < Test::Unit::TestCase
end
assert_block do
- # suppress "warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!"
- experimental, Warning[:experimental] = Warning[:experimental], false
eval(%q{
case true
in a
a
end
})
- ensure
- Warning[:experimental] = experimental
end
assert_block do
@@ -358,6 +354,14 @@ END
end
assert_block do
+ a = "abc"
+ case 'abc'
+ in /#{a}/o
+ true
+ end
+ end
+
+ assert_block do
case 0
in ->(i) { i == 0 }
true
@@ -464,6 +468,8 @@ END
true
end
end
+
+ assert_valid_syntax("1 in ^(1\n)")
end
def test_array_pattern
@@ -798,6 +804,10 @@ END
true
end
end
+
+ assert_syntax_error(%q{
+ 0 => [a, *a]
+ }, /duplicated variable name/)
end
def test_find_pattern
@@ -866,6 +876,10 @@ END
false
end
end
+
+ assert_syntax_error(%q{
+ 0 => [*a, a, b, *b]
+ }, /duplicated variable name/)
end
def test_hash_pattern
@@ -1155,6 +1169,28 @@ END
end
end
+ bug18890 = assert_warning(/(?:.*:[47]: warning: possibly useless use of a literal in void context\n){2}/) do
+ eval("#{<<~';;;'}")
+ proc do |i|
+ case i
+ in a:
+ 0 # line 4
+ a
+ in "b":
+ 0 # line 7
+ b
+ else
+ false
+ end
+ end
+ ;;;
+ end
+ [{a: 42}, {b: 42}].each do |i|
+ assert_block('newline should be significant after pattern label') do
+ bug18890.call(i)
+ end
+ end
+
assert_syntax_error(%q{
case _
in a:, a:
@@ -1548,6 +1584,18 @@ END
assert_equal false, (1 in 2)
end
+ def test_bug18990
+ {a: 0} => a:
+ assert_equal 0, a
+ {a: 0} => a:
+ assert_equal 0, a
+
+ {a: 0} in a:
+ assert_equal 0, a
+ {a: 0} in a:
+ assert_equal 0, a
+ end
+
################################################################
def test_single_pattern_error_value_pattern
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index 05c6e03aac..2f91da8aa8 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -289,7 +289,6 @@ class TestProc < Test::Unit::TestCase
assert_equal(false, l.lambda?)
assert_equal(false, l.curry.lambda?, '[ruby-core:24127]')
assert_equal(false, proc(&l).lambda?)
- assert_equal(false, assert_deprecated_warning {lambda(&l)}.lambda?)
assert_equal(false, Proc.new(&l).lambda?)
l = lambda {}
assert_equal(true, l.lambda?)
@@ -299,47 +298,21 @@ class TestProc < Test::Unit::TestCase
assert_equal(true, Proc.new(&l).lambda?)
end
- def self.helper_test_warn_lamda_with_passed_block &b
+ def helper_test_warn_lambda_with_passed_block &b
lambda(&b)
end
- def self.def_lambda_warning name, warn
- define_method(name, proc do
- prev = Warning[:deprecated]
- assert_warn warn do
- Warning[:deprecated] = true
- yield
- end
- ensure
- Warning[:deprecated] = prev
- end)
- end
-
- def_lambda_warning 'test_lambda_warning_normal', '' do
- lambda{}
- end
-
- def_lambda_warning 'test_lambda_warning_pass_lambda', '' do
- b = lambda{}
- lambda(&b)
- end
-
- def_lambda_warning 'test_lambda_warning_pass_symbol_proc', '' do
- lambda(&:to_s)
- end
-
- def_lambda_warning 'test_lambda_warning_pass_proc', /deprecated/ do
- b = proc{}
- lambda(&b)
- end
-
- def_lambda_warning 'test_lambda_warning_pass_block', /deprecated/ do
- helper_test_warn_lamda_with_passed_block{}
+ def test_lambda_warning_pass_proc
+ assert_raise(ArgumentError) do
+ b = proc{}
+ lambda(&b)
+ end
end
- def_lambda_warning 'test_lambda_warning_pass_block_symbol_proc', '' do
- # Symbol#to_proc returns lambda
- helper_test_warn_lamda_with_passed_block(&:to_s)
+ def test_lambda_warning_pass_block
+ assert_raise(ArgumentError) do
+ helper_test_warn_lambda_with_passed_block{}
+ end
end
def test_curry_ski_fib
@@ -415,6 +388,15 @@ class TestProc < Test::Unit::TestCase
def test_dup_subclass
c1 = Class.new(Proc)
assert_equal c1, c1.new{}.dup.class, '[Bug #17545]'
+ c1 = Class.new(Proc) {def initialize_dup(*) throw :initialize_dup; end}
+ assert_throw(:initialize_dup) {c1.new{}.dup}
+ end
+
+ def test_clone_subclass
+ c1 = Class.new(Proc)
+ assert_equal c1, c1.new{}.clone.class, '[Bug #17545]'
+ c1 = Class.new(Proc) {def initialize_clone(*) throw :initialize_clone; end}
+ assert_throw(:initialize_clone) {c1.new{}.clone}
end
def test_binding
@@ -1321,6 +1303,32 @@ class TestProc < Test::Unit::TestCase
assert_empty(pr.parameters.map{|_,n|n}.compact)
end
+ def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759
+ def self.yielder_ab(splat)
+ yield([:a, :b], *splat)
+ end
+
+ res = yielder_ab([[:aa, :bb], Hash.ruby2_keywords_hash({k: :k})]) do |a, b, k:|
+ [a, b, k]
+ end
+ assert_equal([[:a, :b], [:aa, :bb], :k], res)
+
+ def self.yielder(splat)
+ yield(*splat)
+ end
+ res = yielder([ [:a, :b] ]){|a, b, **| [a, b]}
+ assert_equal([:a, :b], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({c: 1}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **nil| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+ end
+
def test_parameters_lambda
assert_equal([], proc {}.parameters(lambda: true))
assert_equal([], proc {||}.parameters(lambda: true))
@@ -1838,4 +1846,3 @@ class TestProcKeywords < Test::Unit::TestCase
assert_raise(ArgumentError) { (f >> g).call(**{})[:a] }
end
end
-
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index c4888598a8..7ef184d639 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -3,7 +3,6 @@
require 'test/unit'
require 'tempfile'
require 'timeout'
-require 'io/wait'
require 'rbconfig'
class TestProcess < Test::Unit::TestCase
@@ -24,12 +23,6 @@ class TestProcess < Test::Unit::TestCase
return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
end
- def write_file(filename, content)
- File.open(filename, "w") {|f|
- f << content
- }
- end
-
def with_tmpchdir
Dir.mktmpdir {|d|
d = File.realpath(d)
@@ -40,7 +33,7 @@ class TestProcess < Test::Unit::TestCase
end
def run_in_child(str) # should be called in a temporary directory
- write_file("test-script", str)
+ File.write("test-script", str)
Process.wait spawn(RUBY, "test-script")
$?
end
@@ -66,7 +59,7 @@ class TestProcess < Test::Unit::TestCase
def test_rlimit_nofile
return unless rlimit_exist?
with_tmpchdir {
- write_file 's', <<-"End"
+ File.write 's', <<-"End"
# Too small RLIMIT_NOFILE, such as zero, causes problems.
# [OpenBSD] Setting to zero freezes this test.
# [GNU/Linux] EINVAL on poll(). EINVAL on ruby's internal poll() ruby with "[ASYNC BUG] thread_timer: select".
@@ -206,58 +199,67 @@ class TestProcess < Test::Unit::TestCase
max = Process.getrlimit(:CORE).last
+ # When running under ASAN, we need to set disable_coredump=0 for this test; by default
+ # the ASAN runtime library sets RLIMIT_CORE to 0, "to avoid dumping a 16T+ core file", and
+ # that inteferes with this test.
+ asan_options = ENV['ASAN_OPTIONS'] || ''
+ asan_options << ':' unless asan_options.empty?
+ env = {
+ 'ASAN_OPTIONS' => "#{asan_options}disable_coredump=0"
+ }
+
n = max
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
assert_equal("#{n}\n#{n}\n", io.read)
}
n = 0
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
assert_equal("#{n}\n#{n}\n", io.read)
}
n = max
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
assert_equal("#{n}\n#{n}\n", io.read)
}
m, n = 0, max
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
assert_equal("#{m}\n#{n}\n", io.read)
}
m, n = 0, 0
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
assert_equal("#{m}\n#{n}\n", io.read)
}
n = max
- IO.popen([RUBY, "-e",
+ IO.popen([env, RUBY, "-e",
"puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
:rlimit_core=>n, :rlimit_cpu=>3600]) {|io|
assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read)
}
assert_raise(ArgumentError) do
- system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123)
+ system(env, RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123)
end
- assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600)
+ assert_separately([env],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600)
BUG = "[ruby-core:82033] [Bug #13744]"
begin;
assert_equal([3600,3600], Process.getrlimit(:CPU), BUG)
end;
assert_raise_with_message(ArgumentError, /bogus/) do
- system(RUBY, '-e', 'exit', :rlimit_bogus => 123)
+ system(env, RUBY, '-e', 'exit', :rlimit_bogus => 123)
end
assert_raise_with_message(ArgumentError, /rlimit_cpu/) {
- system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600)
+ system(env, RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600)
}
end
@@ -273,7 +275,7 @@ class TestProcess < Test::Unit::TestCase
end;
end
- MANDATORY_ENVS = %w[RUBYLIB MJIT_SEARCH_BUILD_DIR]
+ MANDATORY_ENVS = %w[RUBYLIB RJIT_SEARCH_BUILD_DIR]
case RbConfig::CONFIG['target_os']
when /linux/
MANDATORY_ENVS << 'LD_PRELOAD'
@@ -362,7 +364,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopt_env_path
bug8004 = '[ruby-core:53103] [Bug #8004]'
Dir.mktmpdir do |d|
- open("#{d}/tmp_script.cmd", "w") {|f| f.puts ": ;"; f.chmod(0755)}
+ File.write("#{d}/tmp_script.cmd", ": ;\n", perm: 0o755)
assert_not_nil(pid = Process.spawn({"PATH" => d}, "tmp_script.cmd"), bug8004)
wpid, st = Process.waitpid2(pid)
assert_equal([pid, true], [wpid, st.success?], bug8004)
@@ -400,7 +402,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_env_popen_string
with_tmpchdir do |d|
- open('test-script', 'w') do |f|
+ File.open('test-script', 'w') do |f|
ENVCOMMAND.each_with_index do |cmd, i|
next if i.zero? or cmd == "-e"
f.puts cmd
@@ -412,16 +414,14 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_preserve_env_on_exec_failure
with_tmpchdir {|d|
- write_file 's', <<-"End"
+ File.write 's', <<-"End"
ENV["mgg"] = nil
prog = "./nonexistent"
begin
Process.exec({"mgg" => "mggoo"}, [prog, prog])
rescue Errno::ENOENT
end
- open('out', 'w') {|f|
- f.print ENV["mgg"].inspect
- }
+ File.write('out', ENV["mgg"].inspect)
End
system(RUBY, 's')
assert_equal(nil.inspect, File.read('out'),
@@ -431,9 +431,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_env_single_word
with_tmpchdir {|d|
- open("test_execopts_env_single_word.rb", "w") {|f|
- f.puts "print ENV['hgga']"
- }
+ File.write("test_execopts_env_single_word.rb", "print ENV['hgga']\n")
system({"hgga"=>"ugu"}, RUBY,
:in => 'test_execopts_env_single_word.rb',
:out => 'test_execopts_env_single_word.out')
@@ -555,7 +553,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("a", File.read("out").chomp)
if windows?
# currently telling to child the file modes is not supported.
- open("out", "a") {|f| f.write "0\n"}
+ File.write("out", "0\n", mode: "a")
else
Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644])
assert_equal("a\n0\n", File.read("out"))
@@ -666,6 +664,7 @@ class TestProcess < Test::Unit::TestCase
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_raise
+ pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -683,15 +682,21 @@ class TestProcess < Test::Unit::TestCase
puts "ok"
end
EOS
+ pid = io.pid
assert_equal("start\n", io.gets)
sleep 0.5
Process.kill(:USR1, io.pid)
assert_equal("ok\n", io.read)
}
+ assert_equal(pid, $?.pid)
+ assert_predicate($?, :success?)
}
+ ensure
+ assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)} if pid
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_print
+ pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -704,14 +709,25 @@ class TestProcess < Test::Unit::TestCase
puts "start"
system("cat", :in => "fifo")
EOS
+ pid = io.pid
assert_equal("start\n", io.gets)
sleep 0.2 # wait for the child to stop at opening "fifo"
Process.kill(:USR1, io.pid)
assert_equal("trap\n", io.readpartial(8))
+ sleep 0.2 # wait for the child to return to opening "fifo".
+ # On arm64-darwin22, often deadlocks while the child is
+ # opening "fifo". Not sure to where "ok" line being written
+ # at the next has gone.
File.write("fifo", "ok\n")
assert_equal("ok\n", io.read)
}
+ assert_equal(pid, $?.pid)
+ assert_predicate($?, :success?)
}
+ ensure
+ if pid
+ assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)}
+ end
end unless windows? # does not support fifo
def test_execopts_redirect_pipe
@@ -859,7 +875,7 @@ class TestProcess < Test::Unit::TestCase
def test_execopts_exec
with_tmpchdir {|d|
- write_file("s", 'exec "echo aaa", STDOUT=>"foo"')
+ File.write("s", 'exec "echo aaa", STDOUT=>"foo"')
pid = spawn RUBY, 's'
Process.wait pid
assert_equal("aaa\n", File.read("foo"))
@@ -933,7 +949,7 @@ class TestProcess < Test::Unit::TestCase
}
with_pipe {|r, w|
with_tmpchdir {|d|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec(#{RUBY.dump}, '-e',
'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil',
#{w.fileno.to_s.dump}, :close_others=>false)
@@ -987,7 +1003,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("bi\n", r.read)
}
with_pipe {|r, w|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec(#{RUBY.dump}, '-e',
'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")',
#{w.fileno.to_s.dump},
@@ -1113,7 +1129,7 @@ class TestProcess < Test::Unit::TestCase
def test_exec_noshell
with_tmpchdir {|d|
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
str = "echo non existing command name which contains spaces"
STDERR.reopen(STDOUT)
begin
@@ -1129,7 +1145,7 @@ class TestProcess < Test::Unit::TestCase
def test_system_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" }
exit 5
End
@@ -1145,7 +1161,7 @@ class TestProcess < Test::Unit::TestCase
def test_spawn_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" }
exit 6
End
@@ -1162,7 +1178,7 @@ class TestProcess < Test::Unit::TestCase
def test_popen_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
print "fufu pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
@@ -1181,7 +1197,7 @@ class TestProcess < Test::Unit::TestCase
def test_popen_wordsplit_beginning_and_trailing_spaces
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
print "fufumm pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
@@ -1200,7 +1216,7 @@ class TestProcess < Test::Unit::TestCase
def test_exec_wordsplit
with_tmpchdir {|d|
- write_file("script", <<-'End')
+ File.write("script", <<-'End')
File.open("result", "w") {|t|
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
t << "hehe ppid=#{Process.ppid}"
@@ -1210,7 +1226,7 @@ class TestProcess < Test::Unit::TestCase
}
exit 6
End
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
ruby = #{RUBY.dump}
exec "\#{ruby} script"
End
@@ -1231,11 +1247,11 @@ class TestProcess < Test::Unit::TestCase
def test_system_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
@@ -1251,7 +1267,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1>out")
+ File.write(bat = path + "/bat test.bat", "@echo %1>out")
system(bat, "foo 'bar'")
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
system(%[#{bat.dump} "foo 'bar'"])
@@ -1262,11 +1278,11 @@ class TestProcess < Test::Unit::TestCase
def test_spawn_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
@@ -1283,7 +1299,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1>out")
+ File.write(bat = path + "/bat test.bat", "@echo %1>out")
pid = spawn(bat, "foo 'bar'")
Process.wait pid
status = $?
@@ -1302,11 +1318,11 @@ class TestProcess < Test::Unit::TestCase
def test_popen_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
puts "tako pid=#{$$} ppid=#{Process.ppid}"
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
puts "tika pid=#{$$} ppid=#{Process.ppid}"
exit 8
End
@@ -1321,7 +1337,7 @@ class TestProcess < Test::Unit::TestCase
if windows?
Dir.mkdir(path = "path with space")
- write_file(bat = path + "/bat test.bat", "@echo %1")
+ File.write(bat = path + "/bat test.bat", "@echo %1")
r = IO.popen([bat, "foo 'bar'"]) {|f| f.read}
assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]')
r = IO.popen(%[#{bat.dump} "foo 'bar'"]) {|f| f.read}
@@ -1332,15 +1348,15 @@ class TestProcess < Test::Unit::TestCase
def test_exec_shell
with_tmpchdir {|d|
- write_file("script1", <<-'End')
+ File.write("script1", <<-'End')
File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" }
exit 7
End
- write_file("script2", <<-'End')
+ File.write("script2", <<-'End')
File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" }
exit 8
End
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
ruby = #{RUBY.dump}
exec("\#{ruby} script1 || \#{ruby} script2")
End
@@ -1367,7 +1383,7 @@ class TestProcess < Test::Unit::TestCase
assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read })
- write_file("s", <<-"End")
+ File.write("s", <<-"End")
exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5")
End
pid = spawn RUBY, "s"
@@ -1377,7 +1393,7 @@ class TestProcess < Test::Unit::TestCase
end
def with_stdin(filename)
- open(filename) {|f|
+ File.open(filename) {|f|
begin
old = STDIN.dup
begin
@@ -1394,8 +1410,8 @@ class TestProcess < Test::Unit::TestCase
def test_argv0_noarg
with_tmpchdir {|d|
- open("t", "w") {|f| f.print "exit true" }
- open("f", "w") {|f| f.print "exit false" }
+ File.write("t", "exit true")
+ File.write("f", "exit false")
with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) }
with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) }
@@ -1425,6 +1441,11 @@ class TestProcess < Test::Unit::TestCase
REPRO
end
+ def test_argv0_frozen
+ assert_predicate Process.argv0, :frozen?
+ assert_predicate $0, :frozen?
+ end
+
def test_status
with_tmpchdir do
s = run_in_child("exit 1")
@@ -1433,8 +1454,15 @@ class TestProcess < Test::Unit::TestCase
assert_equal(s, s)
assert_equal(s, s.to_i)
- assert_equal(s.to_i & 0x55555555, s & 0x55555555)
- assert_equal(s.to_i >> 1, s >> 1)
+ assert_deprecated_warn(/\buse .*Process::Status/) do
+ assert_equal(s.to_i & 0x55555555, s & 0x55555555)
+ end
+ assert_deprecated_warn(/\buse .*Process::Status/) do
+ assert_equal(s.to_i >> 1, s >> 1)
+ end
+ assert_raise(ArgumentError) do
+ s >> -1
+ end
assert_equal(false, s.stopped?)
assert_equal(nil, s.stopsig)
@@ -1450,7 +1478,7 @@ class TestProcess < Test::Unit::TestCase
expected = Signal.list.include?("QUIT") ? [false, true, false, nil] : [true, false, false, true]
with_tmpchdir do
- write_file("foo", "Process.kill(:KILL, $$); exit(42)")
+ File.write("foo", "Process.kill(:KILL, $$); exit(42)")
system(RUBY, "foo")
s = $?
assert_equal(expected,
@@ -1498,7 +1526,7 @@ class TestProcess < Test::Unit::TestCase
def test_wait_without_arg
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
pid = spawn(RUBY, "foo")
assert_equal(pid, Process.wait)
end
@@ -1506,7 +1534,7 @@ class TestProcess < Test::Unit::TestCase
def test_wait2
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
pid = spawn(RUBY, "foo")
assert_equal([pid, 0], Process.wait2)
end
@@ -1514,7 +1542,7 @@ class TestProcess < Test::Unit::TestCase
def test_waitall
with_tmpchdir do
- write_file("foo", "sleep 0.1")
+ File.write("foo", "sleep 0.1")
ps = (0...3).map { spawn(RUBY, "foo") }.sort
ss = Process.waitall.sort
ps.zip(ss) do |p1, (p2, s)|
@@ -1547,6 +1575,8 @@ class TestProcess < Test::Unit::TestCase
assert_operator(diff, :<, sec,
->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"})
f.puts
+ rescue Errno::EPIPE
+ omit "child process exited already in #{diff} seconds"
end
end
@@ -1554,7 +1584,7 @@ class TestProcess < Test::Unit::TestCase
with_tmpchdir do
s = run_in_child("abort")
assert_not_predicate(s, :success?)
- write_file("test-script", "#{<<~"begin;"}\n#{<<~'end;'}")
+ File.write("test-script", "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
STDERR.reopen(STDOUT)
begin
@@ -1695,11 +1725,6 @@ class TestProcess < Test::Unit::TestCase
end
def test_wait_and_sigchild
- if /freebsd|openbsd/ =~ RUBY_PLATFORM
- # this relates #4173
- # When ruby can use 2 cores, signal and wait4 may miss the signal.
- omit "this fails on FreeBSD and OpenBSD on multithreaded environment"
- end
signal_received = []
IO.pipe do |sig_r, sig_w|
Signal.trap(:CHLD) do
@@ -1718,7 +1743,7 @@ class TestProcess < Test::Unit::TestCase
Process.wait pid
assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
end
- if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
+ if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # checking -DRJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
assert_equal [true], signal_received.uniq, "[ruby-core:19744]"
else
assert_equal [true], signal_received, "[ruby-core:19744]"
@@ -1752,16 +1777,16 @@ class TestProcess < Test::Unit::TestCase
def test_fallback_to_sh
feature = '[ruby-core:32745]'
with_tmpchdir do |d|
- open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", ": ;\n", perm: 0o755)
assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature)
wpid, st = Process.waitpid2(pid)
assert_equal([pid, true], [wpid, st.success?], feature)
- open("tmp_script.#{$$}", "w") {|f| f.puts "echo $#: $@"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", "echo $#: $@", perm: 0o755)
result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
assert_equal("2: a b c\n", result, feature)
- open("tmp_script.#{$$}", "w") {|f| f.puts "echo $hghg"; f.chmod(0755)}
+ File.write("tmp_script.#{$$}", "echo $hghg", perm: 0o755)
result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
assert_equal("mogomogo\n", result, feature)
@@ -1789,14 +1814,20 @@ class TestProcess < Test::Unit::TestCase
exs << Errno::EINVAL if windows?
exs << Errno::E2BIG if defined?(Errno::E2BIG)
opts = {[STDOUT, STDERR]=>File::NULL}
- opts[:rlimit_nproc] = 128 if defined?(Process::RLIMIT_NPROC)
+ if defined?(Process::RLIMIT_NPROC)
+ opts[:rlimit_nproc] = /openbsd/i =~ RUBY_PLATFORM ? 64 : 128
+ end
EnvUtil.suppress_warning do
assert_raise(*exs, mesg) do
begin
loop do
Process.spawn(cmds.join(sep), opts)
min = [cmds.size, min].max
- cmds *= 100
+ begin
+ cmds *= 100
+ rescue ArgumentError
+ raise NoMemoryError
+ end
end
rescue NoMemoryError
size = cmds.size
@@ -1817,7 +1848,7 @@ class TestProcess < Test::Unit::TestCase
with_tmpchdir do
assert_nothing_raised('[ruby-dev:12261]') do
- EnvUtil.timeout(3) do
+ EnvUtil.timeout(10) do
pid = spawn('yes | ls')
Process.waitpid pid
end
@@ -1876,6 +1907,28 @@ class TestProcess < Test::Unit::TestCase
assert_not_equal(cpid, dpid)
end
+ def test_daemon_detached
+ IO.popen("-", "r+") do |f|
+ if f
+ assert_equal(f.pid, Process.wait(f.pid))
+
+ dpid, ppid, dsid = 3.times.map {Integer(f.gets)}
+
+ message = "daemon #{dpid} should be detached"
+ assert_not_equal($$, ppid, message) # would be 1 almost always
+ assert_raise(Errno::ECHILD, message) {Process.wait(dpid)}
+ assert_kind_of(Integer, Process.kill(0, dpid), message)
+ assert_equal(dpid, dsid)
+
+ break # close f, and let the daemon resume and exit
+ end
+ Process.setsid rescue nil
+ Process.daemon(false, true)
+ puts $$, Process.ppid, Process.getsid
+ $stdin.gets # wait for the above assertions using signals
+ end
+ end
+
if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM
def test_daemon_no_threads
pid, data = IO.popen("-", "r+") do |f|
@@ -2116,7 +2169,7 @@ EOS
"c\u{1EE7}a",
].each do |arg|
begin
- arg = arg.encode(Encoding.find("locale"))
+ arg = arg.encode(Encoding.local_charmap)
rescue
else
assert_in_out_err([], "#{<<-"begin;"}\n#{<<-"end;"}", [arg], [], bug12841)
@@ -2134,7 +2187,9 @@ EOS
t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
assert_operator(t1, :<=, t2)
assert_operator(t2, :<=, t3)
- assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) }
+ assert_raise_with_message(Errno::EINVAL, /:foo/) do
+ Process.clock_gettime(:foo)
+ end
end
def test_clock_gettime_unit
@@ -2239,7 +2294,9 @@ EOS
rescue Errno::EINVAL
else
assert_kind_of(Integer, r)
- assert_raise(Errno::EINVAL) { Process.clock_getres(:foo) }
+ assert_raise_with_message(Errno::EINVAL, /:foo/) do
+ Process.clock_getres(:foo)
+ end
end
def test_clock_getres_constants
@@ -2326,7 +2383,7 @@ EOS
end
def test_deadlock_by_signal_at_forking
- assert_separately(%W(--disable=gems - #{RUBY}), <<-INPUT, timeout: 100)
+ assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100)
ruby = ARGV.shift
GC.start # reduce garbage
GC.disable # avoid triggering CoW after forks
@@ -2565,6 +2622,26 @@ EOS
end
end if Process.respond_to?(:_fork)
+ def test__fork_pid_cache
+ _parent_pid = Process.pid
+ r, w = IO.pipe
+ pid = Process._fork
+ if pid == 0
+ begin
+ r.close
+ w << "ok: #{Process.pid}"
+ w.close
+ ensure
+ exit!
+ end
+ else
+ w.close
+ assert_equal("ok: #{pid}", r.read)
+ r.close
+ Process.waitpid(pid)
+ end
+ end if Process.respond_to?(:_fork)
+
def test__fork_hook
%w(fork Process.fork).each do |method|
feature17795 = '[ruby-core:103400] [Feature #17795]'
@@ -2637,4 +2714,148 @@ EOS
end
end;
end if Process.respond_to?(:_fork)
+
+ def test_warmup_promote_all_objects_to_oldgen
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ require 'objspace'
+ begin;
+ obj = Object.new
+
+ assert_not_include(ObjectSpace.dump(obj), '"old":true')
+ Process.warmup
+ assert_include(ObjectSpace.dump(obj), '"old":true')
+ end;
+ end
+
+ def test_warmup_run_major_gc_and_compact
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # Run a GC to ensure that we are not in the middle of a GC run
+ GC.start
+
+ major_gc_count = GC.stat(:major_gc_count)
+ compact_count = GC.stat(:compact_count)
+ Process.warmup
+ assert_equal major_gc_count + 1, GC.stat(:major_gc_count)
+ assert_equal compact_count + 1, GC.stat(:compact_count)
+ end;
+ end
+
+ def test_warmup_precompute_string_coderange
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ require 'objspace'
+ begin;
+ obj = "a" * 12
+ obj.force_encoding(Encoding::UTF_16LE)
+ obj.force_encoding(Encoding::BINARY)
+ assert_include(ObjectSpace.dump(obj), '"coderange":"unknown"')
+ Process.warmup
+ assert_include(ObjectSpace.dump(obj), '"coderange":"7bit"')
+ end;
+ end
+
+ def test_warmup_frees_pages
+ assert_separately([{"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO" => "1.0"}, "-W0"], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ GC.start
+
+ TIMES = 10_000
+ ary = Array.new(TIMES)
+ TIMES.times do |i|
+ ary[i] = Object.new
+ end
+ ary.clear
+ ary = nil
+
+ # Disable GC so we can make sure GC only runs in Process.warmup
+ GC.disable
+
+ total_pages_before = GC.stat_heap.map { |_, v| v[:heap_eden_pages] + v[:heap_allocatable_pages] }
+
+ Process.warmup
+
+ # Number of pages freed should cause equal increase in number of allocatable pages.
+ total_pages_before.each_with_index do |val, i|
+ assert_equal(val, GC.stat_heap(i, :heap_eden_pages) + GC.stat_heap(i, :heap_allocatable_pages), "size pool: #{i}")
+ end
+ assert_equal(0, GC.stat(:heap_tomb_pages))
+ assert_operator(GC.stat(:total_freed_pages), :>, 0)
+ end;
+ end
+
+ def test_concurrent_group_and_pid_wait
+ # Use a pair of pipes that will make long_pid exit when this test exits, to avoid
+ # leaking temp processes.
+ long_rpipe, long_wpipe = IO.pipe
+ short_rpipe, short_wpipe = IO.pipe
+ # This process should run forever
+ long_pid = fork do
+ [short_rpipe, short_wpipe, long_wpipe].each(&:close)
+ long_rpipe.read
+ end
+ # This process will exit
+ short_pid = fork do
+ [long_rpipe, long_wpipe, short_wpipe].each(&:close)
+ short_rpipe.read
+ end
+ t1, t2, t3 = nil
+ EnvUtil.timeout(5) do
+ t1 = Thread.new do
+ Process.waitpid long_pid
+ end
+ # Wait for us to be blocking in a call to waitpid2
+ Thread.pass until t1.stop?
+ short_wpipe.close # Make short_pid exit
+
+ # The short pid has exited, so -1 should pick that up.
+ assert_equal short_pid, Process.waitpid(-1)
+
+ # Terminate t1 for the next phase of the test.
+ t1.kill
+ t1.join
+
+ t2 = Thread.new do
+ Process.waitpid(-1)
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t2.stop?
+ t3 = Thread.new do
+ Process.waitpid long_pid
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t3.stop?
+
+ # it's actually nondeterministic which of t2 or t3 will receive the wait (this
+ # nondeterminism comes from the behaviour of the underlying system calls)
+ long_wpipe.close
+ assert_equal [long_pid], [t2, t3].map(&:value).compact
+ end
+ ensure
+ [t1, t2, t3].each { _1&.kill rescue nil }
+ [t1, t2, t3].each { _1&.join rescue nil }
+ [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
+ end if defined?(fork)
+
+ def test_handle_interrupt_with_fork
+ Thread.handle_interrupt(RuntimeError => :never) do
+ Thread.current.raise(RuntimeError, "Queued error")
+
+ assert_predicate Thread, :pending_interrupt?
+
+ pid = Process.fork do
+ if Thread.pending_interrupt?
+ exit 1
+ end
+ end
+
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+
+ assert_predicate Thread, :pending_interrupt?
+ end
+ rescue RuntimeError
+ # Ignore.
+ end if defined?(fork)
end
diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb
index f066433f6a..a4beffd689 100644
--- a/test/ruby/test_rand.rb
+++ b/test/ruby/test_rand.rb
@@ -336,6 +336,14 @@ class TestRand < Test::Unit::TestCase
}
end
+ def test_seed_leading_zero_guard
+ guard = 1<<32
+ range = 0...(1<<32)
+ all_assertions_foreach(nil, 0, 1, 2) do |i|
+ assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range))
+ end
+ end
+
def test_marshal
bug3656 = '[ruby-core:31622]'
assert_raise(TypeError, bug3656) {
diff --git a/test/ruby/test_random_formatter.rb b/test/ruby/test_random_formatter.rb
index a5072099e1..f927522d96 100644
--- a/test/ruby/test_random_formatter.rb
+++ b/test/ruby/test_random_formatter.rb
@@ -75,6 +75,47 @@ module Random::Formatter
assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid)
end
+ def assert_uuid_v7(**opts)
+ t1 = current_uuid7_time(**opts)
+ uuid = @it.uuid_v7(**opts)
+ t3 = current_uuid7_time(**opts)
+
+ assert_match(/\A\h{8}-\h{4}-7\h{3}-[89ab]\h{3}-\h{12}\z/, uuid)
+
+ t2 = get_uuid7_time(uuid, **opts)
+ assert_operator(t1, :<=, t2)
+ assert_operator(t2, :<=, t3)
+ end
+
+ def test_uuid_v7
+ assert_uuid_v7
+ 0.upto(12) do |extra_timestamp_bits|
+ assert_uuid_v7 extra_timestamp_bits: extra_timestamp_bits
+ end
+ end
+
+ # It would be nice to simply use Time#floor here. But that is problematic
+ # due to the difference between decimal vs binary fractions.
+ def current_uuid7_time(extra_timestamp_bits: 0)
+ denominator = (1 << extra_timestamp_bits).to_r
+ Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
+ .then {|ns| ((ns / 1_000_000r) * denominator).floor / denominator }
+ .then {|ms| Time.at(ms / 1000r, in: "+00:00") }
+ end
+
+ def get_uuid7_time(uuid, extra_timestamp_bits: 0)
+ denominator = (1 << extra_timestamp_bits) * 1000r
+ extra_chars = extra_timestamp_bits / 4
+ last_char_bits = extra_timestamp_bits % 4
+ extra_chars += 1 if last_char_bits != 0
+ timestamp_re = /\A(\h{8})-(\h{4})-7(\h{#{extra_chars}})/
+ timestamp_chars = uuid.match(timestamp_re).captures.join
+ timestamp = timestamp_chars.to_i(16)
+ timestamp >>= 4 - last_char_bits unless last_char_bits == 0
+ timestamp /= denominator
+ Time.at timestamp, in: "+00:00"
+ end
+
def test_alphanumeric
65.times do |n|
an = @it.alphanumeric(n)
@@ -83,6 +124,20 @@ module Random::Formatter
end
end
+ def test_alphanumeric_chars
+ [
+ [[*"0".."9"], /\A\d*\z/],
+ [[*"a".."t"], /\A[a-t]*\z/],
+ ["一二三四五六七八九十".chars, /\A[一二三四五六七八九十]*\z/],
+ ].each do |chars, pattern|
+ 10.times do |n|
+ an = @it.alphanumeric(n, chars: chars)
+ assert_match(pattern, an)
+ assert_equal(n, an.length)
+ end
+ end
+ end
+
def assert_in_range(range, result, mesg = nil)
assert(range.cover?(result), build_message(mesg, "Expected #{result} to be in #{range}"))
end
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index 8789eca749..84b3b205f0 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -2,7 +2,7 @@
require 'test/unit'
require 'delegate'
require 'timeout'
-require 'bigdecimal'
+require 'date'
require 'rbconfig/sizeof'
class TestRange < Test::Unit::TestCase
@@ -392,6 +392,26 @@ class TestRange < Test::Unit::TestCase
assert_equal(4, (1.0...5.6).step(1.5).to_a.size)
end
+ def test_step_with_succ
+ c = Struct.new(:i) do
+ def succ; self.class.new(i+1); end
+ def <=>(other) i <=> other.i;end
+ end.new(0)
+
+ result = []
+ (c..c.succ).step(2) do |d|
+ result << d.i
+ end
+ assert_equal([0], result)
+
+ result = []
+ (c..).step(2) do |d|
+ result << d.i
+ break if d.i >= 4
+ end
+ assert_equal([0, 2, 4], result)
+ end
+
def test_each
a = []
(0..10).each {|x| a << x }
@@ -456,6 +476,171 @@ class TestRange < Test::Unit::TestCase
assert_equal(["a", "b", "c"], a)
end
+ def test_each_with_succ
+ c = Struct.new(:i) do
+ def succ; self.class.new(i+1); end
+ def <=>(other) i <=> other.i;end
+ end.new(0)
+
+ result = []
+ (c..c.succ).each do |d|
+ result << d.i
+ end
+ assert_equal([0, 1], result)
+
+ result = []
+ (c..).each do |d|
+ result << d.i
+ break if d.i >= 4
+ end
+ assert_equal([0, 1, 2, 3, 4], result)
+ end
+
+ def test_reverse_each
+ a = []
+ (1..3).reverse_each {|x| a << x }
+ assert_equal([3, 2, 1], a)
+
+ a = []
+ (1...3).reverse_each {|x| a << x }
+ assert_equal([2, 1], a)
+
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+
+ a = []
+ (fmax+1..fmax+3).reverse_each {|x| a << x }
+ assert_equal([fmax+3, fmax+2, fmax+1], a)
+
+ a = []
+ (fmax+1...fmax+3).reverse_each {|x| a << x }
+ assert_equal([fmax+2, fmax+1], a)
+
+ a = []
+ (fmax-1..fmax+1).reverse_each {|x| a << x }
+ assert_equal([fmax+1, fmax, fmax-1], a)
+
+ a = []
+ (fmax-1...fmax+1).reverse_each {|x| a << x }
+ assert_equal([fmax, fmax-1], a)
+
+ a = []
+ (fmin-1..fmin+1).reverse_each{|x| a << x }
+ assert_equal([fmin+1, fmin, fmin-1], a)
+
+ a = []
+ (fmin-1...fmin+1).reverse_each{|x| a << x }
+ assert_equal([fmin, fmin-1], a)
+
+ a = []
+ (fmin-3..fmin-1).reverse_each{|x| a << x }
+ assert_equal([fmin-1, fmin-2, fmin-3], a)
+
+ a = []
+ (fmin-3...fmin-1).reverse_each{|x| a << x }
+ assert_equal([fmin-2, fmin-3], a)
+
+ a = []
+ ("a".."c").reverse_each {|x| a << x }
+ assert_equal(["c", "b", "a"], a)
+ end
+
+ def test_reverse_each_for_beginless_range
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+
+ a = []
+ (..3).reverse_each {|x| a << x; break if x <= 0 }
+ assert_equal([3, 2, 1, 0], a)
+
+ a = []
+ (...3).reverse_each {|x| a << x; break if x <= 0 }
+ assert_equal([2, 1, 0], a)
+
+ a = []
+ (..fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 }
+ assert_equal([fmax+1, fmax, fmax-1], a)
+
+ a = []
+ (...fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 }
+ assert_equal([fmax, fmax-1], a)
+
+ a = []
+ (..fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 }
+ assert_equal([fmin+1, fmin, fmin-1], a)
+
+ a = []
+ (...fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 }
+ assert_equal([fmin, fmin-1], a)
+
+ a = []
+ (..fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 }
+ assert_equal([fmin-1, fmin-2, fmin-3], a)
+
+ a = []
+ (...fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 }
+ assert_equal([fmin-2, fmin-3], a)
+ end
+
+ def test_reverse_each_for_endless_range
+ assert_raise(TypeError) { (1..).reverse_each {} }
+
+ enum = nil
+ assert_nothing_raised { enum = (1..).reverse_each }
+ assert_raise(TypeError) { enum.each {} }
+ end
+
+ def test_reverse_each_for_single_point_range
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+
+ values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2]
+
+ values.each do |b|
+ r = b..b
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([b], a, "failed on #{r}")
+
+ r = b...b+1
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([b], a, "failed on #{r}")
+ end
+ end
+
+ def test_reverse_each_for_empty_range
+ fmin = RbConfig::LIMITS['FIXNUM_MIN']
+ fmax = RbConfig::LIMITS['FIXNUM_MAX']
+
+ values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2]
+
+ values.each do |b|
+ r = b..b-1
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([], a, "failed on #{r}")
+ end
+
+ values.repeated_permutation(2).to_a.product([true, false]).each do |(b, e), excl|
+ next unless b > e || (b == e && excl)
+
+ r = Range.new(b, e, excl)
+ a = []
+ r.reverse_each {|x| a << x }
+ assert_equal([], a, "failed on #{r}")
+ end
+ end
+
+ def test_reverse_each_with_no_block
+ enum = (1..5).reverse_each
+ assert_equal 5, enum.size
+
+ a = []
+ enum.each {|x| a << x }
+ assert_equal [5, 4, 3, 2, 1], a
+ end
+
def test_begin_end
assert_equal(0, (0..1).begin)
assert_equal(1, (0..1).end)
@@ -539,6 +724,10 @@ class TestRange < Test::Unit::TestCase
assert_not_operator(0..10, :===, 11)
assert_operator(5..nil, :===, 11)
assert_not_operator(5..nil, :===, 0)
+ assert_operator(nil..10, :===, 0)
+ assert_operator(nil..nil, :===, 0)
+ assert_operator(nil..nil, :===, Object.new)
+ assert_not_operator(0..10, :===, 0..10)
end
def test_eqq_string
@@ -583,6 +772,28 @@ class TestRange < Test::Unit::TestCase
assert_operator(c.new(0)..c.new(10), :===, c.new(5), bug12003)
end
+ def test_eqq_unbounded_ruby_bug_19864
+ t1 = Date.today
+ t2 = t1 + 1
+ assert_equal(true, (..t1) === t1)
+ assert_equal(false, (..t1) === t2)
+ assert_equal(true, (..t2) === t1)
+ assert_equal(true, (..t2) === t2)
+ assert_equal(false, (...t1) === t1)
+ assert_equal(false, (...t1) === t2)
+ assert_equal(true, (...t2) === t1)
+ assert_equal(false, (...t2) === t2)
+
+ assert_equal(true, (t1..) === t1)
+ assert_equal(true, (t1..) === t2)
+ assert_equal(false, (t2..) === t1)
+ assert_equal(true, (t2..) === t2)
+ assert_equal(true, (t1...) === t1)
+ assert_equal(true, (t1...) === t2)
+ assert_equal(false, (t2...) === t1)
+ assert_equal(true, (t2...) === t2)
+ end
+
def test_eqq_non_iteratable
k = Class.new do
include Comparable
@@ -599,13 +810,14 @@ class TestRange < Test::Unit::TestCase
assert_include("a"..."z", "y")
assert_not_include("a"..."z", "z")
assert_not_include("a".."z", "cc")
- assert_include("a".., "c")
- assert_not_include("a".., "5")
+ assert_raise(TypeError) {("a"..).include?("c")}
+ assert_raise(TypeError) {("a"..).include?("5")}
+
assert_include(0...10, 5)
assert_include(5..., 10)
assert_not_include(5..., 0)
- assert_include(.."z", "z")
- assert_not_include(..."z", "z")
+ assert_raise(TypeError) {(.."z").include?("z")}
+ assert_raise(TypeError) {(..."z").include?("z")}
assert_include(..10, 10)
assert_not_include(...10, 10)
end
@@ -670,6 +882,35 @@ class TestRange < Test::Unit::TestCase
assert_not_operator(1..10, :cover?, 3...3)
assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz')
assert_not_operator(1..10, :cover?, 1...10.1)
+
+ assert_operator(..2, :cover?, 1)
+ assert_operator(..2, :cover?, 2)
+ assert_not_operator(..2, :cover?, 3)
+ assert_not_operator(...2, :cover?, 2)
+ assert_not_operator(..2, :cover?, "2")
+ assert_operator(..2, :cover?, ..2)
+ assert_operator(..2, :cover?, ...2)
+ assert_not_operator(..2, :cover?, .."2")
+ assert_not_operator(...2, :cover?, ..2)
+
+ assert_not_operator(2.., :cover?, 1)
+ assert_operator(2.., :cover?, 2)
+ assert_operator(2..., :cover?, 3)
+ assert_operator(2.., :cover?, 2)
+ assert_not_operator(2.., :cover?, "2")
+ assert_operator(2.., :cover?, 2..)
+ assert_operator(2.., :cover?, 2...)
+ assert_not_operator(2.., :cover?, "2"..)
+ assert_not_operator(2..., :cover?, 2..)
+ assert_operator(2..., :cover?, 3...)
+ assert_not_operator(2..., :cover?, 3..)
+ assert_not_operator(3.., :cover?, 2..)
+
+ assert_operator(nil..., :cover?, Object.new)
+ assert_operator(nil..., :cover?, nil...)
+ assert_operator(nil.., :cover?, nil...)
+ assert_not_operator(nil..., :cover?, nil..)
+ assert_not_operator(nil..., :cover?, 1..)
end
def test_beg_len
@@ -742,18 +983,38 @@ class TestRange < Test::Unit::TestCase
end
def test_size
- assert_equal 42, (1..42).size
- assert_equal 41, (1...42).size
- assert_equal 6, (1...6.3).size
- assert_equal 5, (1.1...6).size
- assert_equal 42, (1..42).each.size
+ Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end|
+ r = Range.new(beg.send(m1), ende.send(m2), exclude_end)
+ iterable = true
+ yielded = []
+ begin
+ r.each { yielded << _1 }
+ rescue TypeError
+ iterable = false
+ end
+
+ if iterable
+ assert_equal(yielded.size, r.size, "failed on #{r}")
+ assert_equal(yielded.size, r.each.size, "failed on #{r}")
+ else
+ assert_raise(TypeError, "failed on #{r}") { r.size }
+ assert_raise(TypeError, "failed on #{r}") { r.each.size }
+ end
+ end
+
assert_nil ("a"..."z").size
- assert_equal Float::INFINITY, (1...).size
- assert_equal Float::INFINITY, (1.0...).size
- assert_equal Float::INFINITY, (...1).size
- assert_equal Float::INFINITY, (...1.0).size
- assert_nil ("a"...).size
+ assert_equal Float::INFINITY, (1..).size
+ assert_raise(TypeError) { (1.0..).size }
+ assert_raise(TypeError) { (1r..).size }
+ assert_nil ("a"..).size
+
+ assert_raise(TypeError) { (..1).size }
+ assert_raise(TypeError) { (..1.0).size }
+ assert_raise(TypeError) { (..1r).size }
+ assert_raise(TypeError) { (..'z').size }
+
+ assert_raise(TypeError) { (nil...nil).size }
end
def test_bsearch_typechecks_return_values
@@ -777,9 +1038,6 @@ class TestRange < Test::Unit::TestCase
assert_raise(TypeError) {
(Rational(-1,2)..Rational(9,4)).bsearch
}
- assert_raise(TypeError) {
- (BigDecimal('0.5')..BigDecimal('2.25')).bsearch
- }
end
def test_bsearch_for_fixnum
@@ -953,7 +1211,10 @@ class TestRange < Test::Unit::TestCase
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 100 })
assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true })
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false })
+
+ assert_equal(bignum * 2 + 1, (0...).bsearch {|i| i > bignum * 2 })
assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 })
+ assert_equal(-bignum * 2 + 1, (...0).bsearch {|i| i > -bignum * 2 })
assert_equal(-bignum * 2 + 1, (...-bignum).bsearch {|i| i > -bignum * 2 })
assert_raise(TypeError) { ("a".."z").bsearch {} }
@@ -978,6 +1239,81 @@ class TestRange < Test::Unit::TestCase
end
def test_count
+ assert_equal 42, (1..42).count
+ assert_equal 41, (1...42).count
+ assert_equal 0, (42..1).count
+ assert_equal 0, (42...1).count
+ assert_equal 2**100, (1..2**100).count
+ assert_equal 6, (1...6.3).count
+ assert_equal 4, ('a'..'d').count
+ assert_equal 3, ('a'...'d').count
+
assert_equal(Float::INFINITY, (1..).count)
+ assert_equal(Float::INFINITY, (..1).count)
+ end
+
+ def test_overlap?
+ assert_not_operator(0..2, :overlap?, -2..-1)
+ assert_not_operator(0..2, :overlap?, -2...0)
+ assert_operator(0..2, :overlap?, -1..0)
+ assert_operator(0..2, :overlap?, 1..2)
+ assert_operator(0..2, :overlap?, 2..3)
+ assert_not_operator(0..2, :overlap?, 3..4)
+ assert_not_operator(0...2, :overlap?, 2..3)
+
+ assert_operator(..0, :overlap?, -1..0)
+ assert_operator(...0, :overlap?, -1..0)
+ assert_operator(..0, :overlap?, 0..1)
+ assert_operator(..0, :overlap?, ..1)
+ assert_not_operator(..0, :overlap?, 1..2)
+ assert_not_operator(...0, :overlap?, 0..1)
+
+ assert_not_operator(0.., :overlap?, -2..-1)
+ assert_not_operator(0.., :overlap?, ...0)
+ assert_operator(0.., :overlap?, -1..0)
+ assert_operator(0.., :overlap?, ..0)
+ assert_operator(0.., :overlap?, 0..1)
+ assert_operator(0.., :overlap?, 1..2)
+ assert_operator(0.., :overlap?, 1..)
+
+ assert_not_operator((1..3), :overlap?, ('a'..'d'))
+ assert_not_operator((1..), :overlap?, ('a'..))
+ assert_not_operator((..1), :overlap?, (..'a'))
+
+ assert_raise(TypeError) { (0..).overlap?(1) }
+ assert_raise(TypeError) { (0..).overlap?(nil) }
+
+ assert_operator((1..3), :overlap?, (2..4))
+ assert_operator((1...3), :overlap?, (2..3))
+ assert_operator((2..3), :overlap?, (1..2))
+ assert_operator((..3), :overlap?, (3..))
+ assert_operator((nil..nil), :overlap?, (3..))
+ assert_operator((nil...nil), :overlap?, (nil..))
+
+ assert_raise(TypeError) { (1..3).overlap?(1) }
+
+ assert_not_operator((1..2), :overlap?, (2...2))
+ assert_not_operator((2...2), :overlap?, (1..2))
+
+ assert_not_operator((4..1), :overlap?, (2..3))
+ assert_not_operator((4..1), :overlap?, (..3))
+ assert_not_operator((4..1), :overlap?, (2..))
+
+ assert_not_operator((1..4), :overlap?, (3..2))
+ assert_not_operator((..4), :overlap?, (3..2))
+ assert_not_operator((1..), :overlap?, (3..2))
+
+ assert_not_operator((4..5), :overlap?, (2..3))
+ assert_not_operator((4..5), :overlap?, (2...4))
+
+ assert_not_operator((1..2), :overlap?, (3..4))
+ assert_not_operator((1...3), :overlap?, (3..4))
+
+ assert_not_operator((4..5), :overlap?, (2..3))
+ assert_not_operator((4..5), :overlap?, (2...4))
+
+ assert_not_operator((1..2), :overlap?, (3..4))
+ assert_not_operator((1...3), :overlap?, (3..4))
+ assert_not_operator((...3), :overlap?, (3..))
end
end
diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb
index fe9de64c4c..a51ce3dc99 100644
--- a/test/ruby/test_rational.rb
+++ b/test/ruby/test_rational.rb
@@ -823,6 +823,8 @@ class Rational_Test < Test::Unit::TestCase
ng[5, 1, '5/_3']
ng[5, 3, '5/3_']
ng[5, 3, '5/3x']
+
+ ng[5, 1, '5/-3']
end
def test_parse_zero_denominator
diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb
index c0754d8cf0..11acf31f21 100644
--- a/test/ruby/test_refinement.rb
+++ b/test/ruby/test_refinement.rb
@@ -1044,7 +1044,7 @@ class TestRefinement < Test::Unit::TestCase
end
using Test
def t
- 'Refinements are broken!'.chop!
+ 'Refinements are broken!'.dup.chop!
end
t
module Test
@@ -1206,6 +1206,41 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
+ def test_refined_protected_methods
+ assert_separately([], <<-"end;")
+ bug18806 = '[ruby-core:108705] [Bug #18806]'
+ class C; end
+
+ module R
+ refine C do
+ def refined_call_foo = foo
+ def refined_call_foo_on(other) = other.foo
+
+ protected
+
+ def foo = :foo
+ end
+ end
+
+ class C
+ using R
+
+ def call_foo = foo
+ def call_foo_on(other) = other.foo
+ end
+
+ c = C.new
+ assert_equal :foo, c.call_foo, bug18806
+ assert_equal :foo, c.call_foo_on(c), bug18806
+ assert_equal :foo, c.call_foo_on(C.new), bug18806
+
+ using R
+ assert_equal :foo, c.refined_call_foo, bug18806
+ assert_equal :foo, c.refined_call_foo_on(c), bug18806
+ assert_equal :foo, c.refined_call_foo_on(C.new), bug18806
+ end;
+ end
+
def test_refine_basic_object
assert_separately([], <<-"end;")
bug10106 = '[ruby-core:64166] [Bug #10106]'
@@ -1571,18 +1606,35 @@ class TestRefinement < Test::Unit::TestCase
end
using R
+ def m
+ C.new.m
+ end
+
assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
module R
refine C do
+
+ assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
+
alias m m
+
+ assert_equal(:foo, C.new.m)
+ assert_equal(:foo, m)
+
def m
:bar
end
+
+ assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]")
+ assert_equal(:bar, m, "[Bug #20285]")
end
end
assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]")
+ assert_equal(:bar, m, "[Bug #20285]")
end;
end
@@ -1763,7 +1815,7 @@ class TestRefinement < Test::Unit::TestCase
assert_equal([int_refinement, str_refinement], m.refinements)
end
- def test_refined_class
+ def test_target
refinements = Module.new {
refine Integer do
end
@@ -1771,8 +1823,14 @@ class TestRefinement < Test::Unit::TestCase
refine String do
end
}.refinements
- assert_equal(Integer, refinements[0].refined_class)
- assert_equal(String, refinements[1].refined_class)
+ assert_equal(Integer, refinements[0].target)
+ assert_warn(/Refinement#refined_class is deprecated and will be removed in Ruby 3.4; use Refinement#target instead/) do
+ assert_equal(Integer, refinements[0].refined_class)
+ end
+ assert_equal(String, refinements[1].target)
+ assert_warn(/Refinement#refined_class is deprecated and will be removed in Ruby 3.4; use Refinement#target instead/) do
+ assert_equal(String, refinements[1].refined_class)
+ end
end
def test_warn_setconst_in_refinmenet
@@ -2591,6 +2649,75 @@ class TestRefinement < Test::Unit::TestCase
assert_equal([], Refinement.used_modules)
end
+ def test_inlinecache
+ assert_separately([], <<-"end;")
+ module R
+ refine String do
+ def to_s = :R
+ end
+ end
+
+ 2.times{|i|
+ s = ''.to_s
+ assert_equal '', s if i == 0
+ assert_equal :R, s if i == 1
+ using R if i == 0
+ assert_equal :R, ''.to_s
+ }
+ end;
+ end
+
+ def test_inline_cache_invalidation
+ klass = Class.new do
+ def cached_foo_callsite = foo
+
+ def foo = :v1
+
+ host = self
+ @refinement = Module.new do
+ refine(host) do
+ def foo = :unused
+ end
+ end
+ end
+
+ obj = klass.new
+ obj.cached_foo_callsite # prime cache
+ klass.class_eval do
+ def foo = :v2 # invalidate
+ end
+ assert_equal(:v2, obj.cached_foo_callsite)
+ end
+
+ # [Bug #20302]
+ def test_multiple_refinements_for_same_module
+ assert_in_out_err([], <<-INPUT, %w(:f2 :f1), [])
+ module M1
+ refine(Kernel) do
+ def f1 = :f1
+ end
+ end
+
+ module M2
+ refine(Kernel) do
+ def f2 = :f2
+ end
+ end
+
+ class Foo
+ using M1
+ using M2
+
+ def test
+ p f2
+ p f1
+ end
+ end
+
+ Foo.new.test
+ INPUT
+ end
+
private
def eval_using(mod, s)
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index 84687c5380..c72029ca80 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -40,19 +40,6 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("a".gsub(/a\Z/, ""), "")
end
- def test_yoshidam_net_20041111_1
- s = "[\xC2\xA0-\xC3\xBE]"
- r = assert_deprecated_warning(/ignored/) {Regexp.new(s, nil, "u")}
- assert_match(r, "\xC3\xBE")
- end
-
- def test_yoshidam_net_20041111_2
- assert_raise(RegexpError) do
- s = "[\xFF-\xFF]".force_encoding("utf-8")
- assert_warning(/ignored/) {Regexp.new(s, nil, "u")}
- end
- end
-
def test_ruby_dev_31309
assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-'))
end
@@ -85,12 +72,141 @@ class TestRegexp < Test::Unit::TestCase
end
end
+ def test_to_s_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ str = "abcd\u3042"
+ [:UTF_16BE, :UTF_16LE, :UTF_32BE, :UTF_32LE].each do |es|
+ enc = Encoding.const_get(es)
+ rs = Regexp.new(str.encode(enc)).to_s
+ assert_equal("(?-mix:abcd\u3042)".encode(enc), rs)
+ assert_equal(enc, rs.encoding)
+ end
+ end
+ end
+
def test_to_s_extended_subexp
re = /#\g#{"\n"}/x
re = /#{re}/
assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s}
end
+ def test_extended_comment_invalid_escape_bug_18294
+ assert_separately([], <<-RUBY)
+ re = / C:\\\\[a-z]{5} # e.g. C:\\users /x
+ assert_match(re, 'C:\\users')
+ assert_not_match(re, 'C:\\user')
+
+ re = /
+ foo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[#o]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[[:alnum:]#]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f(?# \\M-ca)oo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# \\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /[-(?# fca)]oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# ca\0\\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+ RUBY
+
+ assert_raise(SyntaxError) {eval "/\\users/x"}
+ assert_raise(SyntaxError) {eval "/[\\users]/x"}
+ assert_raise(SyntaxError) {eval "/(?<\\users)/x"}
+ assert_raise(SyntaxError) {eval "/# \\users/"}
+ end
+
+ def test_nonextended_section_of_extended_regexp_bug_19379
+ assert_separately([], <<-'RUBY')
+ re = /(?-x:#)/x
+ assert_match(re, '#')
+ assert_not_match(re, '-')
+
+ re = /(?xi:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?mix:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?x-im:#
+ y)/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ y))/x
+ assert_match(re, 'y')
+ assert_not_match(re, '-')
+
+ re = /(?x)#
+ y/
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?mx-i)#
+ y/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ (?-x)y#))/x
+ assert_match(re, 'Y#')
+ assert_not_match(re, '-#')
+
+ re = /(?imx:#
+ (?-xim:#(?im)#(?x)#
+ )#
+ (?x)#
+ y)/
+ assert_match(re, '###Y')
+ assert_not_match(re, '###-')
+
+ re = %r{#c-\w+/comment/[\w-]+}
+ re = %r{https?://[^/]+#{re}}x
+ assert_match(re, 'http://foo#c-x/comment/bar')
+ assert_not_match(re, 'http://foo#cx/comment/bar')
+ RUBY
+ end
+
+ def test_utf8_comment_in_usascii_extended_regexp_bug_19455
+ assert_separately([], <<-RUBY)
+ assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding)
+ assert_equal(Encoding::UTF_8, /#\u1000/x.encoding)
+ RUBY
+ end
+
def test_union
assert_equal :ok, begin
Regexp.union(
@@ -208,6 +324,9 @@ class TestRegexp < Test::Unit::TestCase
assert_equal({'a' => '1', 'b' => '2', 'c' => '3'}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('123').named_captures)
assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures)
+ assert_equal({a: '1', b: '2', c: ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: true))
+ assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: false))
+
assert_equal({'a' => 'x'}, /(?<a>x)|(?<a>y)/.match('x').named_captures)
assert_equal({'a' => 'y'}, /(?<a>x)|(?<a>y)/.match('y').named_captures)
@@ -351,6 +470,13 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('/\/\xF1\xF2\xF3/i', /\/#{s}/i.inspect)
end
+ def test_inspect_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ assert_equal('/(?-mix:\\/)|/', Regexp.union(/\//, "").inspect)
+ end
+ end
+
def test_char_to_option
assert_equal("BAR", "FOOBARBAZ"[/b../i])
assert_equal("bar", "foobarbaz"[/ b . . /x])
@@ -555,19 +681,73 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect)
end
+ def test_match_data_deconstruct
+ m = /foo.+/.match("foobarbaz")
+ assert_equal([], m.deconstruct)
+
+ m = /(foo).+(baz)/.match("foobarbaz")
+ assert_equal(["foo", "baz"], m.deconstruct)
+
+ m = /(...)(...)(...)(...)?/.match("foobarbaz")
+ assert_equal(["foo", "bar", "baz", nil], m.deconstruct)
+ end
+
+ def test_match_data_deconstruct_keys
+ m = /foo.+/.match("foobarbaz")
+ assert_equal({}, m.deconstruct_keys([:a]))
+
+ m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz")
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil))
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b]))
+ assert_equal({b: "baz"}, m.deconstruct_keys([:b]))
+ assert_equal({}, m.deconstruct_keys([:c, :a]))
+ assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c]))
+ assert_equal({}, m.deconstruct_keys([:a, :b, :c]))
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(0)
+ }
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(["a", "b"])
+ }
+ end
+
+ def test_match_no_match_no_matchdata
+ EnvUtil.without_gc do
+ h = {}
+ ObjectSpace.count_objects(h)
+ prev_matches = h[:T_MATCH] || 0
+ _md = /[A-Z]/.match('1') # no match
+ ObjectSpace.count_objects(h)
+ new_matches = h[:T_MATCH] || 0
+ assert_equal prev_matches, new_matches, "Bug [#20104]"
+ end
+ end
+
def test_initialize
assert_raise(ArgumentError) { Regexp.new }
assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)})
assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)})
assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)})
- assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding)
- assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")])
- assert_equal(//n, Regexp.new("", nil, "n"))
+ arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value
- arg_encoding_none = 32 # ARG_ENCODING_NONE is implementation defined value
- assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options)
- assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options)
+ assert_deprecated_warning('') do
+ assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding)
+ assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)])
+ assert_equal(//, Regexp.new(""))
+ assert_equal(//, Regexp.new("", timeout: 1))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1))
+
+ assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options)
+
+ assert_nil(Regexp.new("").timeout)
+ assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout)
+ assert_nil(Regexp.compile("").timeout)
+ assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout)
+ end
assert_raise(RegexpError) { Regexp.new(")(") }
assert_raise(RegexpError) { Regexp.new('[\\40000000000') }
@@ -575,6 +755,35 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") }
end
+ def test_initialize_from_regex_memory_corruption
+ assert_ruby_status([], <<-'end;')
+ 10_000.times { Regexp.new(Regexp.new("(?<name>)")) }
+ end;
+ end
+
+ def test_initialize_bool_warning
+ assert_warning(/expected true or false as ignorecase/) do
+ Regexp.new("foo", :i)
+ end
+ end
+
+ def test_initialize_option
+ assert_equal(//i, Regexp.new("", "i"))
+ assert_equal(//m, Regexp.new("", "m"))
+ assert_equal(//x, Regexp.new("", "x"))
+ assert_equal(//imx, Regexp.new("", "imx"))
+ assert_equal(//, Regexp.new("", ""))
+ assert_equal(//imx, Regexp.new("", "mimix"))
+
+ assert_raise(ArgumentError) { Regexp.new("", "e") }
+ assert_raise(ArgumentError) { Regexp.new("", "n") }
+ assert_raise(ArgumentError) { Regexp.new("", "s") }
+ assert_raise(ArgumentError) { Regexp.new("", "u") }
+ assert_raise(ArgumentError) { Regexp.new("", "o") }
+ assert_raise(ArgumentError) { Regexp.new("", "j") }
+ assert_raise(ArgumentError) { Regexp.new("", "xmen") }
+ end
+
def test_match_control_meta_escape
assert_equal(0, /\c\xFF/ =~ "\c\xFF")
assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF")
@@ -683,6 +892,14 @@ class TestRegexp < Test::Unit::TestCase
$_ = nil; assert_nil(~/./)
end
+ def test_match_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042")
+ assert_equal("h", m.match(:foo))
+ end
+ end
+
def test_match_p
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
@@ -1189,30 +1406,109 @@ class TestRegexp < Test::Unit::TestCase
end
def test_unicode_age
- assert_match(/^\p{Age=6.0}$/u, "\u261c")
- assert_match(/^\p{Age=1.1}$/u, "\u261c")
- assert_no_match(/^\P{age=6.0}$/u, "\u261c")
-
- assert_match(/^\p{age=6.0}$/u, "\u31f6")
- assert_match(/^\p{age=3.2}$/u, "\u31f6")
- assert_no_match(/^\p{age=3.1}$/u, "\u31f6")
- assert_no_match(/^\p{age=3.0}$/u, "\u31f6")
- assert_no_match(/^\p{age=1.1}$/u, "\u31f6")
-
- assert_match(/^\p{age=6.0}$/u, "\u2754")
- assert_no_match(/^\p{age=5.0}$/u, "\u2754")
- assert_no_match(/^\p{age=4.0}$/u, "\u2754")
- assert_no_match(/^\p{age=3.0}$/u, "\u2754")
- assert_no_match(/^\p{age=2.0}$/u, "\u2754")
- assert_no_match(/^\p{age=1.1}$/u, "\u2754")
-
- assert_no_match(/^\p{age=12.0}$/u, "\u32FF")
- assert_match(/^\p{age=12.1}$/u, "\u32FF")
- assert_no_match(/^\p{age=13.0}$/u, "\u{10570}")
- assert_match(/^\p{age=14.0}$/u, "\u{10570}")
- assert_match(/^\p{age=14.0}$/u, "\u9FFF")
- assert_match(/^\p{age=14.0}$/u, "\u{2A6DF}")
- assert_match(/^\p{age=14.0}$/u, "\u{2B738}")
+ assert_unicode_age("\u261c", matches: %w"6.0 1.1", unmatches: [])
+
+ assert_unicode_age("\u31f6", matches: %w"6.0 3.2", unmatches: %w"3.1 3.0 1.1")
+ assert_unicode_age("\u2754", matches: %w"6.0", unmatches: %w"5.0 4.0 3.0 2.0 1.1")
+
+ assert_unicode_age("\u32FF", matches: %w"12.1", unmatches: %w"12.0")
+ end
+
+ def test_unicode_age_14_0
+ @matches = %w"14.0"
+ @unmatches = %w"13.0"
+
+ assert_unicode_age("\u{10570}")
+ assert_unicode_age("\u9FFF")
+ assert_unicode_age("\u{2A6DF}")
+ assert_unicode_age("\u{2B738}")
+ end
+
+ def test_unicode_age_15_0
+ @matches = %w"15.0"
+ @unmatches = %w"14.0"
+
+ assert_unicode_age("\u{0CF3}",
+ "KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT")
+ assert_unicode_age("\u{0ECE}", "LAO YAMAKKAN")
+ assert_unicode_age("\u{10EFD}".."\u{10EFF}",
+ "ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA")
+ assert_unicode_age("\u{1123F}".."\u{11241}",
+ "KHOJKI LETTER QA..KHOJKI VOWEL SIGN VOCALIC R")
+ assert_unicode_age("\u{11B00}".."\u{11B09}",
+ "DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU")
+ assert_unicode_age("\u{11F00}".."\u{11F10}",
+ "KAWI SIGN CANDRABINDU..KAWI LETTER O")
+ assert_unicode_age("\u{11F12}".."\u{11F3A}",
+ "KAWI LETTER KA..KAWI VOWEL SIGN VOCALIC R")
+ assert_unicode_age("\u{11F3E}".."\u{11F59}",
+ "KAWI VOWEL SIGN E..KAWI DIGIT NINE")
+ assert_unicode_age("\u{1342F}",
+ "EGYPTIAN HIEROGLYPH V011D")
+ assert_unicode_age("\u{13439}".."\u{1343F}",
+ "EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE")
+ assert_unicode_age("\u{13440}".."\u{13455}",
+ "EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED")
+ assert_unicode_age("\u{1B132}", "HIRAGANA LETTER SMALL KO")
+ assert_unicode_age("\u{1B155}", "KATAKANA LETTER SMALL KO")
+ assert_unicode_age("\u{1D2C0}".."\u{1D2D3}",
+ "KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN")
+ assert_unicode_age("\u{1DF25}".."\u{1DF2A}",
+ "LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK")
+ assert_unicode_age("\u{1E030}".."\u{1E06D}",
+ "MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE")
+ assert_unicode_age("\u{1E08F}",
+ "COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I")
+ assert_unicode_age("\u{1E4D0}".."\u{1E4F9}",
+ "NAG MUNDARI LETTER O..NAG MUNDARI DIGIT NINE")
+ assert_unicode_age("\u{1F6DC}", "WIRELESS")
+ assert_unicode_age("\u{1F774}".."\u{1F776}",
+ "LOT OF FORTUNE..LUNAR ECLIPSE")
+ assert_unicode_age("\u{1F77B}".."\u{1F77F}",
+ "HAUMEA..ORCUS")
+ assert_unicode_age("\u{1F7D9}", "NINE POINTED WHITE STAR")
+ assert_unicode_age("\u{1FA75}".."\u{1FA77}",
+ "LIGHT BLUE HEART..PINK HEART")
+ assert_unicode_age("\u{1FA87}".."\u{1FA88}",
+ "MARACAS..FLUTE")
+ assert_unicode_age("\u{1FAAD}".."\u{1FAAF}",
+ "FOLDING HAND FAN..KHANDA")
+ assert_unicode_age("\u{1FABB}".."\u{1FABD}",
+ "HYACINTH..WING")
+ assert_unicode_age("\u{1FABF}", "GOOSE")
+ assert_unicode_age("\u{1FACE}".."\u{1FACF}",
+ "MOOSE..DONKEY")
+ assert_unicode_age("\u{1FADA}".."\u{1FADB}",
+ "GINGER ROOT..PEA POD")
+ assert_unicode_age("\u{1FAE8}", "SHAKING FACE")
+ assert_unicode_age("\u{1FAF7}".."\u{1FAF8}",
+ "LEFTWARDS PUSHING HAND..RIGHTWARDS PUSHING HAND")
+ assert_unicode_age("\u{2B739}",
+ "CJK UNIFIED IDEOGRAPH-2B739")
+ assert_unicode_age("\u{31350}".."\u{323AF}",
+ "CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF")
+ end
+
+ UnicodeAgeRegexps = Hash.new do |h, age|
+ h[age] = [/\A\p{age=#{age}}+\z/u, /\A\P{age=#{age}}+\z/u].freeze
+ end
+
+ def assert_unicode_age(char, mesg = nil, matches: @matches, unmatches: @unmatches)
+ if Range === char
+ char = char.to_a.join("")
+ end
+
+ matches.each do |age|
+ pos, neg = UnicodeAgeRegexps[age]
+ assert_match(pos, char, mesg)
+ assert_not_match(neg, char, mesg)
+ end
+
+ unmatches.each do |age|
+ pos, neg = UnicodeAgeRegexps[age]
+ assert_not_match(pos, char, mesg)
+ assert_match(neg, char, mesg)
+ end
end
MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end")
@@ -1417,6 +1713,9 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(0, /(?~(a)c)/ =~ "abb")
assert_nil($1)
+
+ assert_equal(0, /(?~(a))/ =~ "")
+ assert_nil($1)
end
def test_backref_overrun
@@ -1471,17 +1770,16 @@ class TestRegexp < Test::Unit::TestCase
end
def test_s_timeout
- assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect }
begin;
- timeout = EnvUtil.apply_timeout_scale(0.2)
-
Regexp.timeout = timeout
- assert_equal(timeout, Regexp.timeout)
+ assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON)
t = Time.now
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
# A typical ReDoS case
- /^(a*)*$/ =~ "a" * 1000000 + "x"
+ /^(a*)*\1$/ =~ "a" * 1000000 + "x"
end
t = Time.now - t
@@ -1489,15 +1787,68 @@ class TestRegexp < Test::Unit::TestCase
end;
end
- def test_timeout
- assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ def test_s_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
begin;
- dummy_timeout = EnvUtil.apply_timeout_scale(10)
- timeout = EnvUtil.apply_timeout_scale(0.2)
+ assert_nil(Regexp.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ Regexp.timeout = Float::INFINITY
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ Regexp.timeout = 1e300
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ assert_raise(ArgumentError) { Regexp.timeout = 0 }
+ assert_raise(ArgumentError) { Regexp.timeout = -1 }
+
+ Regexp.timeout = nil
+ assert_nil(Regexp.timeout)
+ end;
+ end
- Regexp.timeout = dummy_timeout # This should be ignored
+ def test_s_timeout_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", "[Bug #20228]", rss: true)
+ Regexp.timeout = 0.001
+ regex = /^(a*)*$/
+ str = "a" * 1000000 + "x"
- re = Regexp.new("^a*b?a*$", timeout: timeout)
+ code = proc do
+ regex =~ str
+ rescue
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000.times(&code)
+ end;
+ end
+
+ def test_bug_20453
+ re = Regexp.new("^(a*)x$", timeout: 0.001)
+
+ assert_raise(Regexp::TimeoutError) do
+ re =~ "a" * 1000000 + "x"
+ end
+ end
+
+ def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
+ per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect }
+ expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect }
+ begin;
+ Regexp.timeout = global_timeout
+
+ re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout)
+ if per_instance_timeout
+ assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON)
+ else
+ assert_nil(re.timeout)
+ end
t = Time.now
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
@@ -1505,7 +1856,229 @@ class TestRegexp < Test::Unit::TestCase
end
t = Time.now - t
- assert_in_delta(timeout, t, timeout / 2)
+ assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4)
+ end;
+ end
+
+ def test_timeout_shorter_than_global
+ omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/
+ per_instance_redos_test(10, 0.2, 0.2)
+ end
+
+ def test_timeout_longer_than_global
+ omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/
+ per_instance_redos_test(0.01, 0.5, 0.5)
+ end
+
+ def test_timeout_nil
+ per_instance_redos_test(0.5, nil, 0.5)
+ end
+
+ def test_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_nil(//.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout)
+
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout)
+
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) }
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
+ end;
+ end
+
+ def test_match_cache_exponential
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_square
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_atomic
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?>a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_atomic_complex
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/a*(?>a*)ab/ =~ "a" * 1000000 + "b")
end;
end
+
+ def test_match_cache_positive_look_ahead
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?=a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_positive_look_ahead_complex
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_equal(/(?:(?=a*)a)*/ =~ "a" * 1000000, 0)
+ end;
+ end
+
+ def test_match_cache_negative_look_ahead
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/^a*?(?!a*a*)$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_positive_look_behind
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/(?<=abc|def)(a|a)*$/ =~ "abc" + "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_negative_look_behind
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/(?<!x)(a|a)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_match_cache_with_peek_optimization
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_nil(/a+z/ =~ "a" * 1000000 + "xz")
+ end;
+ end
+
+ def test_cache_opcodes_initialize
+ str = 'test1-test2-test3-test4-test_5'
+ re = '^([0-9a-zA-Z\-/]*){1,256}$'
+ 100.times do
+ assert !Regexp.new(re).match?(str)
+ end
+ end
+
+ def test_bug_19273 # [Bug #19273]
+ pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/
+ assert_equal("10:0:0".match(pattern)[0], "10:0:0")
+ end
+
+ def test_bug_19467 # [Bug #19467]
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y")
+ end;
+ end
+
+ def test_bug_19476 # [Bug #19476]
+ assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789")
+ assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789")
+ end
+
+ def test_encoding_flags_are_preserved_when_initialized_with_another_regexp
+ re = Regexp.new("\u2018hello\u2019".encode("UTF-8"))
+ str = "".encode("US-ASCII")
+
+ assert_nothing_raised do
+ str.match?(re)
+ str.match?(Regexp.new(re))
+ end
+ end
+
+ def test_bug_19537 # [Bug #19537]
+ str = 'aac'
+ re = '^([ab]{1,3})(a?)*$'
+ 100.times do
+ assert !Regexp.new(re).match?(str)
+ end
+ end
+
+ def test_bug_20083 # [Bug #20083]
+ re = /([\s]*ABC)$/i
+ (1..100).each do |n|
+ text = "#{"0" * n}ABC"
+ assert text.match?(re)
+ end
+ end
+
+ def test_bug_20098 # [Bug #20098]
+ assert(/a((.|.)|bc){,4}z/.match? 'abcbcbcbcz')
+ assert(/a(b+?c*){4,5}z/.match? 'abbbccbbbccbcbcz')
+ assert(/a(b+?(.|.)){2,3}z/.match? 'abbbcbbbcbbbcz')
+ assert(/a(b*?(.|.)[bc]){2,5}z/.match? 'abcbbbcbcccbcz')
+ assert(/^(?:.+){2,4}?b|b/.match? "aaaabaa")
+ end
+
+ def test_bug_20207 # [Bug #20207]
+ assert(!'clan'.match?(/(?=.*a)(?!.*n)/))
+ end
+
+ def test_bug_20212 # [Bug #20212]
+ regex = Regexp.new(
+ /\A((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+)\Z/x
+ )
+ string = "www.google.com"
+ 100.times.each { assert(regex.match?(string)) }
+ end
+
+ def test_bug_20246 # [Bug #20246]
+ assert_equal '1.2.3', '1.2.3'[/(\d+)(\.\g<1>){2}/]
+ assert_equal '1.2.3', '1.2.3'[/((?:\d|foo|bar)+)(\.\g<1>){2}/]
+ end
+
+ def test_linear_time_p
+ assert_send [Regexp, :linear_time?, /a/]
+ assert_send [Regexp, :linear_time?, 'a']
+ assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE]
+ assert_not_send [Regexp, :linear_time?, /(a)\1/]
+ assert_not_send [Regexp, :linear_time?, "(a)\\1"]
+
+ assert_not_send [Regexp, :linear_time?, /(?=(a))/]
+ assert_not_send [Regexp, :linear_time?, /(?!(a))/]
+
+ assert_raise(TypeError) {Regexp.linear_time?(nil)}
+ assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)}
+ end
+
+ def test_linear_performance
+ pre = ->(n) {[Regexp.new("a?" * n + "a" * n), "a" * n]}
+ assert_linear_performance([10, 29], pre: pre) do |re, s|
+ re =~ s
+ end
+ end
end
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 5a24cb1ba5..ef33928376 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -6,11 +6,27 @@ require 'tmpdir'
class TestRequire < Test::Unit::TestCase
def test_load_error_path
- filename = "should_not_exist"
- error = assert_raise(LoadError) do
- require filename
- end
- assert_equal filename, error.path
+ Tempfile.create(["should_not_exist", ".rb"]) {|t|
+ filename = t.path
+ t.close
+ File.unlink(filename)
+
+ error = assert_raise(LoadError) do
+ require filename
+ end
+ assert_equal filename, error.path
+
+ # with --disable=gems
+ assert_separately(["-", filename], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ filename = ARGV[0]
+ path = Struct.new(:to_path).new(filename)
+ error = assert_raise(LoadError) do
+ require path
+ end
+ assert_equal filename, error.path
+ end;
+ }
end
def test_require_invalid_shared_object
@@ -52,7 +68,8 @@ class TestRequire < Test::Unit::TestCase
def test_require_nonascii
bug3758 = '[ruby-core:31915]'
["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path|
- assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path}
+ e = assert_raise(LoadError, bug3758) {require path}
+ assert_operator(e.message, :end_with?, path, bug3758)
end
end
@@ -192,7 +209,7 @@ class TestRequire < Test::Unit::TestCase
File.write(req, "p :ok\n")
assert_file.exist?(req)
req[/.rb$/i] = ""
- assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), [])
+ assert_in_out_err([], <<-INPUT, %w(:ok), [])
require "#{req}"
require "#{req}"
INPUT
@@ -210,6 +227,7 @@ class TestRequire < Test::Unit::TestCase
assert_not_nil(bt = e.backtrace, "no backtrace")
assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}, proc {bt.inspect})
end
+ ensure
$LOADED_FEATURES.replace loaded_features
end
@@ -352,6 +370,26 @@ class TestRequire < Test::Unit::TestCase
end
end
+ def test_public_in_wrapped_load
+ Tempfile.create(["test_public_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "public :foo"
+ t.close
+ assert_warning(/main\.public/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
+ def test_private_in_wrapped_load
+ Tempfile.create(["test_private_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "private :foo"
+ t.close
+ assert_warning(/main\.private/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
def test_load_scope
bug1982 = '[ruby-core:25039] [Bug #1982]'
Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
@@ -562,9 +600,6 @@ class TestRequire < Test::Unit::TestCase
assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
assert_equal([:pre, :post], scratch, bug5754)
-
- assert_match(/circular require/, output)
- assert_match(/in #{__method__}'$/o, output)
}
ensure
$VERBOSE = verbose
@@ -682,7 +717,7 @@ class TestRequire < Test::Unit::TestCase
Dir.mktmpdir {|tmp|
Dir.chdir(tmp) {
open("foo.rb", "w") {}
- assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
+ assert_in_out_err([{"RUBYOPT"=>nil}], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
begin;
$:.replace([IO::NULL])
a = Object.new
@@ -710,7 +745,7 @@ class TestRequire < Test::Unit::TestCase
Dir.mktmpdir {|tmp|
Dir.chdir(tmp) {
open("foo.rb", "w") {}
- assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
+ assert_in_out_err([{"RUBYOPT"=>nil}], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
begin;
$:.replace([IO::NULL])
a = Object.new
@@ -740,7 +775,7 @@ class TestRequire < Test::Unit::TestCase
open("foo.rb", "w") {}
Dir.mkdir("a")
open(File.join("a", "bar.rb"), "w") {}
- assert_in_out_err(['--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383)
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383)
begin;
$:.replace([IO::NULL])
$:.#{add} "#{tmp}"
@@ -824,6 +859,8 @@ class TestRequire < Test::Unit::TestCase
end if File.respond_to?(:mkfifo)
def test_loading_fifo_threading_success
+ omit "[Bug #18613]" if /freebsd/=~ RUBY_PLATFORM
+
Tempfile.create(%w'fifo .rb') {|f|
f.close
File.unlink(f.path)
@@ -959,4 +996,19 @@ class TestRequire < Test::Unit::TestCase
assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso"))
end
end
+
+ def test_require_with_public_method_missing
+ # [Bug #19793]
+ assert_separately(["-W0", "-rtempfile"], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ GC.stress = true
+
+ class Object
+ public :method_missing
+ end
+
+ Tempfile.create(["empty", ".rb"]) do |file|
+ require file.path
+ end
+ RUBY
+ end
end
diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb
index 95fa3f29e1..a88279727e 100644
--- a/test/ruby/test_require_lib.rb
+++ b/test/ruby/test_require_lib.rb
@@ -1,25 +1,26 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
require 'test/unit'
class TestRequireLib < Test::Unit::TestCase
- TEST_RATIO = ENV["TEST_REQUIRE_THREAD_RATIO"]&.tap {|s|break s.to_f} || 0.05 # testing all files needs too long time...
+ libdir = __dir__ + '/../../lib'
- Dir.glob(File.expand_path('../../lib/**/*.rb', __dir__)).each do |lib|
- # skip some problems
- next if %r!/lib/(?:bundler|rubygems)\b! =~ lib
- next if %r!/lib/(?:debug|mkmf)\.rb\z! =~ lib
- next if %r!/lib/irb/ext/tracer\.rb\z! =~ lib
- # skip many files that almost use no threads
- next if TEST_RATIO < rand(0.0..1.0)
+ # .rb files at lib
+ scripts = Dir.glob('*.rb', base: libdir).map {|f| f.chomp('.rb')}
+
+ # .rb files in subdirectories of lib without same name script
+ dirs = Dir.glob('*/', base: libdir).map {|d| d.chomp('/')}
+ dirs -= scripts
+ scripts.concat(Dir.glob(dirs.map {|d| d + '/*.rb'}, base: libdir).map {|f| f.chomp('.rb')})
+
+ # skip some problems
+ scripts -= %w[bundler bundled_gems rubygems mkmf]
+
+ scripts.each do |lib|
define_method "test_thread_size:#{lib}" do
- assert_separately(['--disable-gems', '-W0'], "#{<<~"begin;"}\n#{<<~"end;"}")
+ assert_separately(['-W0'], "#{<<~"begin;"}\n#{<<~"end;"}")
begin;
n = Thread.list.size
- begin
- require #{lib.dump}
- rescue Exception
- omit $!
- end
+ require #{lib.dump}
assert_equal n, Thread.list.size
end;
end
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index ab15006e4c..76be9152a7 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -7,13 +7,19 @@ require 'tempfile'
require_relative '../lib/jit_support'
class TestRubyOptions < Test::Unit::TestCase
- def self.yjit_enabled? = defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ def self.rjit_enabled? = defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
+ def self.yjit_enabled? = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+
+ # Here we're defining our own RUBY_DESCRIPTION without "+PRISM". We do this
+ # here so that the various tests that reference RUBY_DESCRIPTION don't have to
+ # worry about it. The flag itself is tested in its own test.
+ RUBY_DESCRIPTION = ::RUBY_DESCRIPTION.sub(/\+PRISM /, '')
NO_JIT_DESCRIPTION =
- if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE
- RUBY_DESCRIPTION.sub(/\+MJIT /, '')
- elsif yjit_enabled? # checking -DYJIT_FORCE_ENABLE
- RUBY_DESCRIPTION.sub(/\+YJIT /, '')
+ if rjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+RJIT /, '')
+ elsif yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '')
else
RUBY_DESCRIPTION
end
@@ -40,7 +46,7 @@ class TestRubyOptions < Test::Unit::TestCase
def test_usage
assert_in_out_err(%w(-h)) do |r, e|
assert_operator(r.size, :<=, 25)
- longer = r[1..-1].select {|x| x.size > 80}
+ longer = r[1..-1].select {|x| x.size >= 80}
assert_equal([], longer)
assert_equal([], e)
end
@@ -73,7 +79,7 @@ class TestRubyOptions < Test::Unit::TestCase
def test_backtrace_limit
assert_in_out_err(%w(--backtrace-limit), "", [], /missing argument for --backtrace-limit/)
assert_in_out_err(%w(--backtrace-limit= 1), "", [], /missing argument for --backtrace-limit/)
- assert_in_out_err(%w(--backtrace-limit=-1), "", [], /wrong limit for backtrace length/)
+ assert_in_out_err(%w(--backtrace-limit=-2), "", [], /wrong limit for backtrace length/)
code = 'def f(n);n > 0 ? f(n-1) : raise;end;f(5)'
assert_in_out_err(%w(--backtrace-limit=1), code, [],
[/.*unhandled exception\n/, /^\tfrom .*\n/,
@@ -83,46 +89,71 @@ class TestRubyOptions < Test::Unit::TestCase
/^\t \.{3} \d+ levels\.{3}\n/])
assert_kind_of(Integer, Thread::Backtrace.limit)
assert_in_out_err(%w(--backtrace-limit=1), "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err(%w(--backtrace-limit 1), "p Thread::Backtrace.limit", ['1'], [])
+ env = {"RUBYOPT" => "--backtrace-limit=5"}
+ assert_in_out_err([env], "p Thread::Backtrace.limit", ['5'], [])
+ assert_in_out_err([env, "--backtrace-limit=1"], "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err([env, "--backtrace-limit=-1"], "p Thread::Backtrace.limit", ['-1'], [])
+ assert_in_out_err([env, "--backtrace-limit=3", "--backtrace-limit=1"],
+ "p Thread::Backtrace.limit", ['1'], [])
+ assert_in_out_err([{"RUBYOPT" => "--backtrace-limit=5 --backtrace-limit=3"}],
+ "p Thread::Backtrace.limit", ['3'], [])
+ long_max = RbConfig::LIMITS["LONG_MAX"]
+ assert_in_out_err(%W(--backtrace-limit=#{long_max}), "p Thread::Backtrace.limit",
+ ["#{long_max}"], [])
end
def test_warning
- save_rubyopt = ENV['RUBYOPT']
- ENV['RUBYOPT'] = nil
+ save_rubyopt = ENV.delete('RUBYOPT')
assert_in_out_err(%w(-W0 -e) + ['p $-W'], "", %w(0), [])
assert_in_out_err(%w(-W1 -e) + ['p $-W'], "", %w(1), [])
assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-W -e) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-We) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), [])
- assert_in_out_err(%w(-W:deprecated -e) + ['p Warning[:deprecated]'], "", %w(true), [])
- assert_in_out_err(%w(-W:no-deprecated -e) + ['p Warning[:deprecated]'], "", %w(false), [])
- assert_in_out_err(%w(-W:experimental -e) + ['p Warning[:experimental]'], "", %w(true), [])
- assert_in_out_err(%w(-W:no-experimental -e) + ['p Warning[:experimental]'], "", %w(false), [])
- assert_in_out_err(%w(-W:qux), "", [], /unknown warning category: `qux'/)
- assert_in_out_err(%w(-w -e) + ['p Warning[:deprecated]'], "", %w(true), [])
- assert_in_out_err(%w(-W -e) + ['p Warning[:deprecated]'], "", %w(true), [])
- assert_in_out_err(%w(-We) + ['p Warning[:deprecated]'], "", %w(true), [])
- assert_in_out_err(%w(-e) + ['p Warning[:deprecated]'], "", %w(false), [])
- code = 'puts "#{$VERBOSE}:#{Warning[:deprecated]}:#{Warning[:experimental]}"'
+
+ categories = {deprecated: 1, experimental: 0, performance: 2}
+ assert_equal categories.keys.sort, Warning.categories.sort
+
+ categories.each do |category, level|
+ assert_in_out_err(["-W:#{category}", "-e", "p Warning[:#{category}]"], "", %w(true), [])
+ assert_in_out_err(["-W:no-#{category}", "-e", "p Warning[:#{category}]"], "", %w(false), [])
+ assert_in_out_err(["-e", "p Warning[:#{category}]"], "", level > 0 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-w", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-W", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ assert_in_out_err(["-We", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), [])
+ end
+ assert_in_out_err(%w(-W:qux), "", [], /unknown warning category: 'qux'/)
+
+ def categories.expected(lev = 1, **warnings)
+ [
+ (lev > 1).to_s,
+ *map {|category, level| warnings.fetch(category, lev > level).to_s}
+ ].join(':')
+ end
+ code = ['#{$VERBOSE}', *categories.map {|category, | "\#{Warning[:#{category}]}"}].join(':')
+ code = %[puts "#{code}"]
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) do |t|
t.puts code
t.close
- assert_in_out_err(["-r#{t.path}", '-e', code], "", %w(false:false:true false:false:true), [])
- assert_in_out_err(["-r#{t.path}", '-w', '-e', code], "", %w(true:true:true true:true:true), [])
- assert_in_out_err(["-r#{t.path}", '-W:deprecated', '-e', code], "", %w(false:true:true false:true:true), [])
- assert_in_out_err(["-r#{t.path}", '-W:no-experimental', '-e', code], "", %w(false:false:false false:false:false), [])
+ assert_in_out_err(["-r#{t.path}", '-e', code], "", [categories.expected(1)]*2, [])
+ assert_in_out_err(["-r#{t.path}", '-w', '-e', code], "", [categories.expected(2)]*2, [])
+ categories.each do |category, |
+ assert_in_out_err(["-r#{t.path}", "-W:#{category}", '-e', code], "", [categories.expected(category => 'true')]*2, [])
+ assert_in_out_err(["-r#{t.path}", "-W:no-#{category}", '-e', code], "", [categories.expected(category => 'false')]*2, [])
+ end
end
ensure
ENV['RUBYOPT'] = save_rubyopt
end
def test_debug
- assert_in_out_err(["--disable-gems", "-de", "p $DEBUG"], "", %w(true), [])
+ assert_in_out_err(["-de", "p $DEBUG"], "", %w(true), [])
- assert_in_out_err(["--disable-gems", "--debug", "-e", "p $DEBUG"],
+ assert_in_out_err(["--debug", "-e", "p $DEBUG"],
"", %w(true), [])
- assert_in_out_err(["--disable-gems", "--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/)
+ assert_in_out_err(["--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/)
end
q = Regexp.method(:quote)
@@ -136,21 +167,21 @@ class TestRubyOptions < Test::Unit::TestCase
end
private_constant :VERSION_PATTERN
- VERSION_PATTERN_WITH_JIT =
+ VERSION_PATTERN_WITH_RJIT =
case RUBY_ENGINE
when 'ruby'
- /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+MJIT \[#{q[RUBY_PLATFORM]}\]$/
+ /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+RJIT (\+MN )?\[#{q[RUBY_PLATFORM]}\]$/
else
VERSION_PATTERN
end
- private_constant :VERSION_PATTERN_WITH_JIT
+ private_constant :VERSION_PATTERN_WITH_RJIT
def test_verbose
assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? && !mjit_force_enabled? # checking -DMJIT_FORCE_ENABLE
+ if self.class.rjit_enabled? && !JITSupport.rjit_force_enabled?
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif self.class.yjit_enabled? && !yjit_force_enabled? # checking -DYJIT_FORCE_ENABLE
+ elsif self.class.yjit_enabled? && !JITSupport.yjit_force_enabled?
assert_equal(NO_JIT_DESCRIPTION, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -171,13 +202,18 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_enable
- if JITSupport.supported?
+ if JITSupport.yjit_supported?
assert_in_out_err(%w(--enable all -e) + [""], "", [], [])
assert_in_out_err(%w(--enable-all -e) + [""], "", [], [])
assert_in_out_err(%w(--enable=all -e) + [""], "", [], [])
+ elsif JITSupport.rjit_supported?
+ # Avoid failing tests by RJIT warnings
+ assert_in_out_err(%w(--enable all --disable rjit -e) + [""], "", [], [])
+ assert_in_out_err(%w(--enable-all --disable-rjit -e) + [""], "", [], [])
+ assert_in_out_err(%w(--enable=all --disable=rjit -e) + [""], "", [], [])
end
assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [],
- /unknown argument for --enable: `foobarbazqux'/)
+ /unknown argument for --enable: 'foobarbazqux'/)
assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/)
end
@@ -186,11 +222,11 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--disable-all -e) + [""], "", [], [])
assert_in_out_err(%w(--disable=all -e) + [""], "", [], [])
assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [],
- /unknown argument for --disable: `foobarbazqux'/)
+ /unknown argument for --disable: 'foobarbazqux'/)
assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/)
- assert_in_out_err(%w(--disable-gems -e) + ['p defined? Gem'], "", ["nil"], [])
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [])
assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], [])
- assert_in_out_err(%w(--disable-gems -e) + ['p defined? DidYouMean'], "", ["nil"], [])
+ assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], [])
end
def test_kanji
@@ -211,31 +247,29 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_version
- env = {'RUBY_YJIT_ENABLE' => nil} # unset in children
+ env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
assert_in_out_err([env, '--version']) do |r, e|
assert_match(VERSION_PATTERN, r[0])
if ENV['RUBY_YJIT_ENABLE'] == '1'
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE
+ elsif self.class.rjit_enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE
assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
end
assert_equal([], e)
end
+ end
- return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no'
- return if yjit_force_enabled?
+ def test_rjit_disabled_version
+ return unless JITSupport.rjit_supported?
+ return if JITSupport.yjit_force_enabled?
+ env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
[
- %w(--version --mjit --disable=mjit),
- %w(--version --enable=mjit --disable=mjit),
- %w(--version --enable-mjit --disable-mjit),
- *([
- %w(--version --jit --disable=jit),
- %w(--version --enable=jit --disable=jit),
- %w(--version --enable-jit --disable-jit),
- ] unless JITSupport.yjit_supported?),
+ %w(--version --rjit --disable=rjit),
+ %w(--version --enable=rjit --disable=rjit),
+ %w(--version --enable-rjit --disable-rjit),
].each do |args|
assert_in_out_err([env] + args) do |r, e|
assert_match(VERSION_PATTERN, r[0])
@@ -243,31 +277,45 @@ class TestRubyOptions < Test::Unit::TestCase
assert_equal([], e)
end
end
+ end
- if JITSupport.supported?
- [
- %w(--version --mjit),
- %w(--version --enable=mjit),
- %w(--version --enable-mjit),
- *([
- %w(--version --jit),
- %w(--version --enable=jit),
- %w(--version --enable-jit),
- ] unless JITSupport.yjit_supported?),
- ].each do |args|
- assert_in_out_err([env] + args) do |r, e|
- assert_match(VERSION_PATTERN_WITH_JIT, r[0])
- if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE
- assert_equal(RUBY_DESCRIPTION, r[0])
- else
- assert_equal(EnvUtil.invoke_ruby([env, '--mjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
- end
- assert_equal([], e)
+ def test_rjit_version
+ return unless JITSupport.rjit_supported?
+ return if JITSupport.yjit_force_enabled?
+
+ env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
+ [
+ %w(--version --rjit),
+ %w(--version --enable=rjit),
+ %w(--version --enable-rjit),
+ ].each do |args|
+ assert_in_out_err([env] + args) do |r, e|
+ assert_match(VERSION_PATTERN_WITH_RJIT, r[0])
+ if JITSupport.rjit_force_enabled?
+ assert_equal(RUBY_DESCRIPTION, r[0])
+ else
+ assert_equal(EnvUtil.invoke_ruby([env, '--rjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
end
+ assert_equal([], e)
end
end
end
+ def test_parser_flag
+ warning = /compiler based on the Prism parser is currently experimental/
+
+ assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), warning)
+ assert_in_out_err(%w(--parser=prism -W:no-experimental -e) + ["puts :hi"], "", %w(hi), [])
+ assert_in_out_err(%w(--parser=prism -W:no-experimental --dump=parsetree -e _=:hi), "", /"hi"/, [])
+
+ assert_in_out_err(%w(--parser=parse.y -e) + ["puts :hi"], "", %w(hi), [])
+ assert_norun_with_rflag('--parser=parse.y', '--version', "")
+
+ assert_in_out_err(%w(--parser=notreal -e) + ["puts :hi"], "", [], /unknown parser notreal/)
+
+ assert_in_out_err(%w(--parser=prism --version), "", /\+PRISM/, [])
+ end
+
def test_eval
assert_in_out_err(%w(-e), "", [], /no code specified for -e \(RuntimeError\)/)
end
@@ -334,22 +382,32 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--encoding test_ruby_test_rubyoptions_foobarbazqux), "", [],
/unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/)
- if /mswin|mingw|aix|android/ =~ RUBY_PLATFORM &&
- (str = "\u3042".force_encoding(Encoding.find("external"))).valid_encoding?
- # This result depends on locale because LANG=C doesn't affect locale
- # on Windows.
- # On AIX, the source encoding of stdin with LANG=C is ISO-8859-1,
- # which allows \u3042.
- out, err = [str], []
- else
- out, err = [], /invalid multibyte char/
- end
- assert_in_out_err(%w(-Eutf-8), "puts '\u3042'", out, err)
- assert_in_out_err(%w(--encoding utf-8), "puts '\u3042'", out, err)
+ assert_in_out_err(%w(-Eutf-8), 'puts Encoding::default_external', ["UTF-8"])
+ assert_in_out_err(%w(-Ecesu-8), 'puts Encoding::default_external', ["CESU-8"])
+ assert_in_out_err(%w(--encoding utf-8), 'puts Encoding::default_external', ["UTF-8"])
+ assert_in_out_err(%w(--encoding cesu-8), 'puts Encoding::default_external', ["CESU-8"])
end
def test_syntax_check
- assert_in_out_err(%w(-c -e a=1+1 -e !a), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e a=1+1 -e !a), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e break), "", [], ["-e:1: Invalid break", :*])
+ assert_in_out_err(%w(-cw -e next), "", [], ["-e:1: Invalid next", :*])
+ assert_in_out_err(%w(-cw -e redo), "", [], ["-e:1: Invalid redo", :*])
+ assert_in_out_err(%w(-cw -e retry), "", [], ["-e:1: Invalid retry", :*])
+ assert_in_out_err(%w(-cw -e yield), "", [], ["-e:1: Invalid yield", :*])
+ assert_in_out_err(%w(-cw -e begin -e break -e end), "", [], ["-e:2: Invalid break", :*])
+ assert_in_out_err(%w(-cw -e begin -e next -e end), "", [], ["-e:2: Invalid next", :*])
+ assert_in_out_err(%w(-cw -e begin -e redo -e end), "", [], ["-e:2: Invalid redo", :*])
+ assert_in_out_err(%w(-cw -e begin -e retry -e end), "", [], ["-e:2: Invalid retry", :*])
+ assert_in_out_err(%w(-cw -e begin -e yield -e end), "", [], ["-e:2: Invalid yield", :*])
+ assert_in_out_err(%w(-cw -e !defined?(break)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(next)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(redo)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(retry)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-cw -e !defined?(yield)), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e break), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e next), "", ["Syntax OK"], [])
+ assert_in_out_err(%w(-n -cw -e redo), "", ["Syntax OK"], [])
end
def test_invalid_option
@@ -396,13 +454,12 @@ class TestRubyOptions < Test::Unit::TestCase
ENV['RUBYOPT'] = '-W:no-experimental'
assert_in_out_err(%w(), "p Warning[:experimental]", ["false"])
ENV['RUBYOPT'] = '-W:qux'
- assert_in_out_err(%w(), "", [], /unknown warning category: `qux'/)
+ assert_in_out_err(%w(), "", [], /unknown warning category: 'qux'/)
+
+ ENV['RUBYOPT'] = 'w'
+ assert_in_out_err(%w(), "p $VERBOSE", ["true"])
ensure
- if rubyopt_orig
- ENV['RUBYOPT'] = rubyopt_orig
- else
- ENV.delete('RUBYOPT')
- end
+ ENV['RUBYOPT'] = rubyopt_orig
end
def test_search
@@ -492,6 +549,16 @@ class TestRubyOptions < Test::Unit::TestCase
/invalid name for global variable - -# \(NameError\)/)
end
+ def test_option_missing_argument
+ assert_in_out_err(%w(-0 --enable), "", [], /missing argument for --enable/)
+ assert_in_out_err(%w(-0 --disable), "", [], /missing argument for --disable/)
+ assert_in_out_err(%w(-0 --dump), "", [], /missing argument for --dump/)
+ assert_in_out_err(%w(-0 --encoding), "", [], /missing argument for --encoding/)
+ assert_in_out_err(%w(-0 --external-encoding), "", [], /missing argument for --external-encoding/)
+ assert_in_out_err(%w(-0 --internal-encoding), "", [], /missing argument for --internal-encoding/)
+ assert_in_out_err(%w(-0 --backtrace-limit), "", [], /missing argument for --backtrace-limit/)
+ end
+
def test_assignment_in_conditional
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
t.puts "if a = 1"
@@ -502,7 +569,7 @@ class TestRubyOptions < Test::Unit::TestCase
t.puts " end"
t.puts "end"
t.flush
- warning = ' warning: found `= literal\' in conditional, should be =='
+ warning = ' warning: found \'= literal\' in conditional, should be =='
err = ["#{t.path}:1:#{warning}",
"#{t.path}:4:#{warning}",
]
@@ -519,16 +586,29 @@ class TestRubyOptions < Test::Unit::TestCase
t.puts "if a = {}; end"
t.puts "if a = {1=>2}; end"
t.puts "if a = {3=>a}; end"
+ t.puts "if a = :sym; end"
t.flush
err = ["#{t.path}:1:#{warning}",
"#{t.path}:2:#{warning}",
"#{t.path}:3:#{warning}",
"#{t.path}:5:#{warning}",
"#{t.path}:6:#{warning}",
+ "#{t.path}:8:#{warning}",
]
feature4299 = '[ruby-dev:43083]'
assert_in_out_err(["-w", t.path], "", [], err, feature4299)
assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, feature4299)
+
+ t.rewind
+ t.truncate(0)
+ t.puts "if a = __LINE__; end"
+ t.puts "if a = __FILE__; end"
+ t.flush
+ err = ["#{t.path}:1:#{warning}",
+ "#{t.path}:2:#{warning}",
+ ]
+ assert_in_out_err(["-w", t.path], "", [], err)
+ assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err)
}
end
@@ -740,7 +820,7 @@ class TestRubyOptions < Test::Unit::TestCase
-e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n
)x,
%r(
- #{ Regexp.quote(NO_JIT_DESCRIPTION) }\n\n
+ #{ Regexp.quote((TestRubyOptions.rjit_enabled? && !JITSupport.rjit_force_enabled?) ? NO_JIT_DESCRIPTION : RUBY_DESCRIPTION) }\n\n
)x,
%r(
(?:--\s(?:.+\n)*\n)?
@@ -751,12 +831,15 @@ class TestRubyOptions < Test::Unit::TestCase
%r(
(?:
--\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n
- (?:-e:1:in\s\`(?:block\sin\s)?<main>\'\n)*
- -e:1:in\s\`kill\'\n
+ (?:-e:1:in\s\'(?:block\sin\s)?<main>\'\n)*
+ -e:1:in\s\'kill\'\n
\n
)?
)x,
%r(
+ (?:--\sThreading(?:.+\n)*\n)?
+ )x,
+ %r(
(?:--\sMachine(?:.+\n)*\n)?
)x,
%r(
@@ -772,26 +855,36 @@ class TestRubyOptions < Test::Unit::TestCase
)?
)x,
]
+
+ KILL_SELF = "Process.kill :SEGV, $$"
end
- def assert_segv(args, message=nil)
- omit if ENV['RUBY_ON_BUG']
+ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block)
+ # We want YJIT to be enabled in the subprocess if it's enabled for us
+ # so that the Ruby description matches.
+ env = Hash === args.first ? args.shift : {}
+ args.unshift("--yjit") if self.class.yjit_enabled?
+ env.update({'RUBY_ON_BUG' => nil})
+ # ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when
+ # catching sigsegv; we don't expect that output, so suppress it.
+ env.update({'ASAN_OPTIONS' => 'handle_segv=0'})
+ args.unshift(env)
test_stdin = ""
- opt = SEGVTest::ExecOptions.dup
- list = SEGVTest::ExpectedStderrList
+ tests = [//, list] unless block
- assert_in_out_err(args, test_stdin, //, list, encoding: "ASCII-8BIT", **opt)
+ assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT",
+ **SEGVTest::ExecOptions, **opt, &block)
end
def test_segv_test
- assert_segv(["--disable-gems", "-e", "Process.kill :SEGV, $$"])
+ assert_segv(["--disable-gems", "-e", SEGVTest::KILL_SELF])
end
def test_segv_loaded_features
bug7402 = '[ruby-core:49573]'
- status = assert_segv(['-e', 'END {Process.kill :SEGV, $$}',
+ status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
'-e', 'class Bogus; def to_str; exit true; end; end',
'-e', '$".clear',
'-e', '$".unshift Bogus.new',
@@ -805,10 +898,64 @@ class TestRubyOptions < Test::Unit::TestCase
Tempfile.create(["test_ruby_test_bug7597", ".rb"]) {|t|
t.write "f" * 100
t.flush
- assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], bug7597)
+ assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; #{SEGVTest::KILL_SELF}", t.path], bug7597)
}
end
+ def assert_crash_report(path, cmd = nil, &block)
+ Dir.mktmpdir("ruby_crash_report") do |dir|
+ list = SEGVTest::ExpectedStderrList
+ if cmd
+ FileUtils.mkpath(File.join(dir, File.dirname(cmd)))
+ File.write(File.join(dir, cmd), SEGVTest::KILL_SELF+"\n")
+ c = Regexp.quote(cmd)
+ list = list.map {|re| Regexp.new(re.source.gsub(/^\s*(\(\?:)?\K-e(?=:)/) {c}, re.options)}
+ else
+ cmd = ['-e', SEGVTest::KILL_SELF]
+ end
+ status = assert_segv([{"RUBY_CRASH_REPORT"=>path}, *cmd], list: [], chdir: dir, &block)
+ next if block
+ reports = Dir.glob("*.log", File::FNM_DOTMATCH, base: dir)
+ assert_equal(1, reports.size)
+ assert_pattern_list(list, File.read(File.join(dir, reports.first)))
+ break status, reports.first
+ end
+ end
+
+ def test_crash_report
+ status, report = assert_crash_report("%e.%f.%p.log")
+ assert_equal("#{File.basename(EnvUtil.rubybin)}.-e.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_script
+ status, report = assert_crash_report("%e.%f.%p.log", "bug.rb")
+ assert_equal("#{File.basename(EnvUtil.rubybin)}.bug.rb.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_executable_path
+ omit if EnvUtil.rubybin.size > 245
+ status, report = assert_crash_report("%E.%p.log")
+ path = EnvUtil.rubybin.sub(/\A\w\K:[\/\\]/, '!').tr_s('/', '!')
+ assert_equal("#{path}.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_script_path
+ status, report = assert_crash_report("%F.%p.log", "test/bug.rb")
+ assert_equal("test!bug.rb.#{status.pid}.log", report)
+ end
+
+ def test_crash_report_pipe
+ if File.executable?(echo = "/bin/echo")
+ elsif /mswin|ming/ =~ RUBY_PLATFORM
+ echo = "echo"
+ else
+ omit "/bin/echo not found"
+ end
+ assert_crash_report("| #{echo} %e:%f:%p") do |stdin, stdout, status|
+ assert_equal(["#{File.basename(EnvUtil.rubybin)}:-e:#{status.pid}"], stdin)
+ end
+ end
+
def test_DATA
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
t.puts "puts DATA.read.inspect"
@@ -991,18 +1138,17 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157)
end
- def assert_norun_with_rflag(*opt)
+ def assert_norun_with_rflag(*opt, test_stderr: [])
bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option"
stderr = []
Tempfile.create(%w"bug10435- .rb") do |script|
dir, base = File.split(script.path)
- script.puts "abort ':run'"
- script.close
+ File.write(script, "abort ':run'\n")
opts = ['-C', dir, '-r', "./#{base}", *opt]
- _, e = assert_in_out_err([*opts, '-ep'], "", //)
+ _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr)
stderr.concat(e) if e
stderr << "---"
- _, e = assert_in_out_err([*opts, base], "", //)
+ _, e = assert_in_out_err([*opts, base], "", //, test_stderr)
stderr.concat(e) if e
end
assert_not_include(stderr, ":run", bug10435)
@@ -1021,6 +1167,17 @@ class TestRubyOptions < Test::Unit::TestCase
def test_dump_parsetree_with_rflag
assert_norun_with_rflag('--dump=parsetree')
assert_norun_with_rflag('--dump=parsetree', '-e', '#frozen-string-literal: true')
+ assert_norun_with_rflag('--dump=parsetree+error_tolerant')
+ assert_norun_with_rflag('--dump=parse+error_tolerant')
+ end
+
+ def test_dump_parsetree_error_tolerant
+ assert_in_out_err(['--dump=parse', '-e', 'begin'],
+ "", [], /unexpected end-of-input/, success: false)
+ assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'],
+ "", /^# @/, /unexpected end-of-input/, success: true)
+ assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'],
+ "", [], /unexpected end-of-input/, success: false)
end
def test_dump_insns_with_rflag
@@ -1051,13 +1208,16 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_frozen_string_literal_debug
+ default_frozen = eval("'test'").frozen?
+
with_debug_pat = /created at/
wo_debug_pat = /can\'t modify frozen String: "\w+" \(FrozenError\)\n\z/
frozen = [
["--enable-frozen-string-literal", true],
["--disable-frozen-string-literal", false],
- [nil, false],
]
+ frozen << [nil, false] unless default_frozen
+
debugs = [
["--debug-frozen-string-literal", true],
["--debug=frozen-string-literal", true],
@@ -1122,21 +1282,9 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err([IO::NULL], success: true)
end
- def test_mjit_debug
- # mswin uses prebuilt precompiled header. Thus it does not show a pch compilation log to check "-O0 -O1".
- if JITSupport.supported? && !RUBY_PLATFORM.match?(/mswin/)
- env = { 'MJIT_SEARCH_BUILD_DIR' => 'true' }
- assert_in_out_err([env, "--disable-yjit", "--mjit-debug=-O0 -O1", "--mjit-verbose=2", "" ], "", [], /-O0 -O1/)
- end
- end
-
- private
-
- def mjit_force_enabled?
- "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?MJIT_FORCE_ENABLE\b/)
- end
-
- def yjit_force_enabled?
- "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?YJIT_FORCE_ENABLE\b/)
+ def test_free_at_exit_env_var
+ env = {"RUBY_FREE_AT_EXIT"=>"1"}
+ assert_ruby_status([env, "-e;"])
+ assert_in_out_err([env, "-W"], "", [], /Free at exit is experimental and may be unstable/)
end
end
diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb
index d0b7cba341..d729aa5af8 100644
--- a/test/ruby/test_rubyvm.rb
+++ b/test/ruby/test_rubyvm.rb
@@ -4,11 +4,9 @@ require 'test/unit'
class TestRubyVM < Test::Unit::TestCase
def test_stat
assert_kind_of Hash, RubyVM.stat
- assert_kind_of Integer, RubyVM.stat[:class_serial]
RubyVM.stat(stat = {})
assert_not_empty stat
- assert_equal stat[:class_serial], RubyVM.stat(:class_serial)
end
def test_stat_unknown
diff --git a/test/ruby/test_rubyvm_mjit.rb b/test/ruby/test_rubyvm_mjit.rb
deleted file mode 100644
index 94b773c4e6..0000000000
--- a/test/ruby/test_rubyvm_mjit.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require_relative '../lib/jit_support'
-
-return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no'
-
-class TestRubyVMMJIT < Test::Unit::TestCase
- include JITSupport
-
- def setup
- unless JITSupport.supported?
- omit 'JIT seems not supported on this platform'
- end
- end
-
- def test_pause
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- i = 0
- while i < 5
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::MJIT.pause
- print RubyVM::MJIT.pause
- while i < 10
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::MJIT.pause # no JIT here
- EOS
- assert_equal('truefalsefalse', out)
- assert_equal(
- 5, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- )
- end
-
- def test_pause_waits_until_compaction
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- def a() end; a
- def b() end; b
- RubyVM::MJIT.pause
- EOS
- assert_equal(
- 2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- )
- assert_equal(
- 1, err.scan(/#{JITSupport::JIT_COMPACTION_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- ) unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet
- end
-
- def test_pause_does_not_hang_on_full_units
- out, _ = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, max_cache: 10, wait: false)
- i = 0
- while i < 11
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::MJIT.pause
- EOS
- assert_equal('true', out)
- end
-
- def test_pause_wait_false
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- i = 0
- while i < 10
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::MJIT.pause(wait: false)
- print RubyVM::MJIT.pause(wait: false)
- EOS
- assert_equal('truefalse', out)
- assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10)
- end
-
- def test_resume
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- print RubyVM::MJIT.resume
- print RubyVM::MJIT.pause
- print RubyVM::MJIT.resume
- print RubyVM::MJIT.resume
- print RubyVM::MJIT.pause
- EOS
- assert_equal('falsetruetruefalsetrue', out)
- assert_equal(0, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size)
- end
-end
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index b43f9c114d..1251f8879f 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
+EnvUtil.suppress_warning {require 'continuation'}
class TestSetTraceFunc < Test::Unit::TestCase
def setup
@@ -50,6 +51,49 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal([], events)
end
+ def test_c_return_no_binding
+ binding = :none
+ TracePoint.new(:c_return){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_no_binding
+ binding = :none
+ TracePoint.new(:c_call){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_removed_method
+ # [Bug #19305]
+ klass = Class.new do
+ attr_writer :bar
+ alias_method :set_bar, :bar=
+ remove_method :bar=
+ end
+
+ obj = klass.new
+ method_id = nil
+ parameters = nil
+
+ TracePoint.new(:c_call) { |tp|
+ method_id = tp.method_id
+ parameters = tp.parameters
+ }.enable {
+ obj.set_bar(1)
+ }
+
+ assert_equal(:bar=, method_id)
+ assert_equal([[:req]], parameters)
+ end
+
def test_call
events = []
name = "#{self.class}\##{__method__}"
@@ -315,18 +359,18 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_thread_trace
events = {:set => [], :add => []}
+ name = "#{self.class}\##{__method__}"
prc = Proc.new { |event, file, lineno, mid, binding, klass|
- events[:set] << [event, lineno, mid, klass, :set]
+ events[:set] << [event, lineno, mid, klass, :set] if file == name
}
prc = prc # suppress warning
prc2 = Proc.new { |event, file, lineno, mid, binding, klass|
- events[:add] << [event, lineno, mid, klass, :add]
+ events[:add] << [event, lineno, mid, klass, :add] if file == name
}
prc2 = prc2 # suppress warning
th = Thread.new do
th = Thread.current
- name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: th.set_trace_func(prc)
2: th.add_trace_func(prc2)
@@ -459,9 +503,9 @@ class TestSetTraceFunc < Test::Unit::TestCase
begin
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
- 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
+ 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
3: }
- 4: 1.times{|;_local_var| _local_var = :inner
+ 4: [1].reverse_each{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
@@ -488,29 +532,29 @@ class TestSetTraceFunc < Test::Unit::TestCase
answer_events = [
#
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
+ [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
- [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
+ [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, :outer, :nothing],
- [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, :outer, nil],
- [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
- [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
+ [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
+ [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
+ [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing],
+ [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
- [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
- [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
- [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
+ [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing],
+ [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing],
+ [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy],
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
@@ -521,17 +565,17 @@ class TestSetTraceFunc < Test::Unit::TestCase
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
- [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
- [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
- [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
+ [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc],
+ [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc],
+ [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
- [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
- [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
+ [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing],
+ [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true],
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
]
@@ -582,6 +626,19 @@ PREP
CODE
end
+ def test_tracepoint_bmethod_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true)
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+ tp = TracePoint.new(:return) {}
+ begin;
+ 1_000_000.times do
+ tp.enable(target: bmethod) {}
+ end
+ end;
+ end
+
def trace_by_set_trace_func
events = []
trace = nil
@@ -596,7 +653,7 @@ CODE
1: set_trace_func(lambda{|event, file, line, id, binding, klass|
2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy'
3: })
- 4: 1.times{|;_local_var| _local_var = :inner
+ 4: [1].map{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
@@ -912,6 +969,55 @@ CODE
assert_equal(expected*2, events)
end
+ def test_tracepoint_struct
+ c = Struct.new(:x) do
+ alias y x
+ alias y= x=
+ end
+ obj = c.new
+
+ ar_meth = obj.method(:x)
+ aw_meth = obj.method(:x=)
+ aar_meth = obj.method(:y)
+ aaw_meth = obj.method(:y=)
+ events = []
+ trace = TracePoint.new(:c_call, :c_return){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ next if tp.method_id == :call
+ case tp.event
+ when :c_call
+ assert_raise(RuntimeError) {tp.return_value}
+ events << [tp.event, tp.method_id, tp.callee_id]
+ when :c_return
+ events << [tp.event, tp.method_id, tp.callee_id, tp.return_value]
+ end
+ }
+ test_proc = proc do
+ obj.x = 1
+ obj.x
+ obj.y = 2
+ obj.y
+ aw_meth.call(1)
+ ar_meth.call
+ aaw_meth.call(2)
+ aar_meth.call
+ end
+ test_proc.call # populate call caches
+ trace.enable(&test_proc)
+ expected = [
+ [:c_call, :x=, :x=],
+ [:c_return, :x=, :x=, 1],
+ [:c_call, :x, :x],
+ [:c_return, :x, :x, 1],
+ [:c_call, :x=, :y=],
+ [:c_return, :x=, :y=, 2],
+ [:c_call, :x, :y],
+ [:c_return, :x, :y, 2],
+ ]
+ assert_equal(expected*2, events)
+ end
+
class XYZZYException < Exception; end
def method_test_tracepoint_raised_exception err
raise err
@@ -951,7 +1057,7 @@ CODE
/return/ =~ tp.event ? tp.return_value : nil
]
}.enable{
- 1.times{
+ [1].map{
3
}
method_for_test_tracepoint_block{
@@ -961,10 +1067,10 @@ CODE
# pp events
# expected_events =
[[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
- [:c_call, :times, Integer, Integer, nil],
+ [:c_call, :map, Array, Array, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3],
- [:c_return, :times, Integer, Integer, 1],
+ [:c_return, :map, Array, Array, [3]],
[:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
@@ -1018,9 +1124,9 @@ CODE
when :line
assert_match(/ in /, str)
when :call, :c_call
- assert_match(/call \`/, str) # #<TracePoint:c_call `inherited' ../trunk/test.rb:11>
+ assert_match(/call \'/, str) # #<TracePoint:c_call 'inherited' ../trunk/test.rb:11>
when :return, :c_return
- assert_match(/return \`/, str) # #<TracePoint:return `m' ../trunk/test.rb:3>
+ assert_match(/return \'/, str) # #<TracePoint:return 'm' ../trunk/test.rb:3>
when /thread/
assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>>
else
@@ -1153,15 +1259,17 @@ CODE
end
}
assert_normal_exit src % %q{obj.zip({}) {}}, bug7774
- assert_normal_exit src % %q{
- require 'continuation'
- begin
- c = nil
- obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
- c.call
- rescue RuntimeError
- end
- }, bug7774
+ if respond_to?(:callcc)
+ assert_normal_exit src % %q{
+ require 'continuation'
+ begin
+ c = nil
+ obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
+ c.call
+ rescue RuntimeError
+ end
+ }, bug7774
+ end
# TracePoint
tp_b = nil
@@ -1267,7 +1375,7 @@ CODE
next if !target_thread?
events << tp.event
}.enable{
- 1.times{
+ [1].map{
3
}
method_for_test_tracepoint_block{
@@ -1289,7 +1397,7 @@ CODE
next if !target_thread?
events << tp.event
}.enable{
- 1.times{
+ [1].map{
3
}
method_for_test_tracepoint_block{
@@ -1648,7 +1756,7 @@ CODE
Bug10724.new
}
- assert_equal([:call, :return], evs)
+ assert_equal([:call, :call, :return, :return], evs)
end
require 'fiber'
@@ -1880,7 +1988,11 @@ CODE
def tp_return_value mid
ary = []
- TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{
+ TracePoint.new(:return, :b_return){|tp|
+ next if !target_thread?
+ next if tp.path != __FILE__
+ ary << [tp.event, tp.method_id, tp.return_value]
+ }.enable{
send mid
}
ary.pop # last b_return event is not required.
@@ -2089,7 +2201,7 @@ CODE
q = Thread::Queue.new
t = Thread.new{
Thread.current.add_trace_func proc{|ev, file, line, *args|
- events << [ev, line]
+ events << [ev, line] if file == __FILE__
} # do not stop trace. They will be stopped at Thread termination.
q.push 1
_x = 1
@@ -2125,9 +2237,9 @@ CODE
}
# it is dirty hack. usually we shouldn't use such technique
Thread.pass until t.status == 'sleep'
- # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
+ # When RJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
# This sleep forces it to reach m2t_q.pop for --jit-wait.
- sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
t.add_trace_func proc{|ev, file, line, *args|
if file == __FILE__
@@ -2140,17 +2252,16 @@ CODE
m2t_q.push 1
t.join
- assert_equal ["c-return", base_line + 31], events[0]
- assert_equal ["line", base_line + 32], events[1]
- assert_equal ["line", base_line + 33], events[2]
- assert_equal ["call", base_line + -6], events[3]
- assert_equal ["return", base_line + -4], events[4]
- assert_equal ["line", base_line + 34], events[5]
- assert_equal ["line", base_line + 35], events[6]
- assert_equal ["c-call", base_line + 35], events[7] # Thread.current
- assert_equal ["c-return", base_line + 35], events[8] # Thread.current
- assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func
- assert_equal nil, events[10]
+ assert_equal ["line", base_line + 32], events[0]
+ assert_equal ["line", base_line + 33], events[1]
+ assert_equal ["call", base_line + -6], events[2]
+ assert_equal ["return", base_line + -4], events[3]
+ assert_equal ["line", base_line + 34], events[4]
+ assert_equal ["line", base_line + 35], events[5]
+ assert_equal ["c-call", base_line + 35], events[6] # Thread.current
+ assert_equal ["c-return", base_line + 35], events[7] # Thread.current
+ assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func
+ assert_equal nil, events[9]
end
def test_lineno_in_optimized_insn
@@ -2364,6 +2475,40 @@ CODE
assert_equal [:tp1, 1, 2, :tp2, 3], events
end
+ def test_multiple_enable
+ ary = []
+ trace = TracePoint.new(:call) do |tp|
+ ary << tp.method_id
+ end
+ trace.enable
+ trace.enable
+ foo
+ trace.disable
+ assert_equal(1, ary.count(:foo), '[Bug #19114]')
+ end
+
+ def test_multiple_tracepoints_same_bmethod
+ events = []
+ tp1 = TracePoint.new(:return) do |tp|
+ events << :tp1
+ end
+ tp2 = TracePoint.new(:return) do |tp|
+ events << :tp2
+ end
+
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+
+ tp1.enable(target: bmethod) do
+ tp2.enable(target: bmethod) do
+ obj.foo
+ end
+ end
+
+ assert_equal([:tp2, :tp1], events, '[Bug #18031]')
+ end
+
def test_script_compiled
events = []
tp = TracePoint.new(:script_compiled){|tp|
@@ -2561,6 +2706,20 @@ CODE
end
bar
EOS
+
+ assert_normal_exit(<<-EOS, 'Bug #18730')
+ def bar
+ 42
+ end
+ tp_line = TracePoint.new(:line) do |tp0|
+ tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp|
+ tp0.disable
+ end
+ tp_multi1.enable
+ end
+ tp_line.enable(target: method(:bar))
+ bar
+ EOS
end
def test_stat_exists
@@ -2620,4 +2779,150 @@ CODE
TracePoint.allow_reentry{}
end
end
+
+ def test_raising_from_b_return_tp_tracing_bmethod
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.trace(:b_return) do |tp|
+ raise
+ end
+
+ Foo.foo
+ RUBY
+
+ # Same thing but with a target
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.new(:b_return) do |tp|
+ raise
+ end.enable(target: Foo.method(:foo))
+
+ Foo.foo
+ RUBY
+ end
+
+ def helper_cant_rescue
+ begin
+ raise SyntaxError
+ rescue
+ cant_rescue
+ end
+ end
+
+ def test_tp_rescue
+ lines = []
+ TracePoint.new(:line){|tp|
+ next unless target_thread?
+ lines << tp.lineno
+ }.enable{
+ begin
+ helper_cant_rescue
+ rescue SyntaxError
+ end
+ }
+ _call_line = lines.shift
+ _raise_line = lines.shift
+ assert_equal [], lines
+ end
+
+ def helper_can_rescue
+ begin
+ raise __LINE__.to_s
+ rescue SyntaxError
+ :ng
+ rescue
+ :ok
+ end
+ end
+
+ def helper_can_rescue_empty_body
+ begin
+ raise __LINE__.to_s
+ rescue SyntaxError
+ :ng
+ rescue
+ end
+ end
+
+ def test_tp_rescue_event
+ lines = []
+ TracePoint.new(:rescue){|tp|
+ next unless target_thread?
+ lines << [tp.lineno, tp.raised_exception]
+ }.enable{
+ helper_can_rescue
+ }
+
+ line, err, = lines.pop
+ assert_equal [], lines
+ assert err.kind_of?(RuntimeError)
+ assert_equal err.message.to_i + 4, line
+
+ lines = []
+ TracePoint.new(:rescue){|tp|
+ next unless target_thread?
+ lines << [tp.lineno, tp.raised_exception]
+ }.enable{
+ helper_can_rescue_empty_body
+ }
+
+ line, err, = lines.pop
+ assert_equal [], lines
+ assert err.kind_of?(RuntimeError)
+ assert_equal err.message.to_i + 3, line
+ end
+
+ def test_tracepoint_thread_begin
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_begin) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end_with_exception
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+ raise
+ end
+
+ # Ignore the exception raised by the thread:
+ thread.join rescue nil
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
end
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb
new file mode 100644
index 0000000000..9b02504384
--- /dev/null
+++ b/test/ruby/test_shapes.rb
@@ -0,0 +1,1040 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'objspace'
+require 'json'
+
+# These test the functionality of object shapes
+class TestShapes < Test::Unit::TestCase
+ MANY_IVS = 80
+
+ class IVOrder
+ def expected_ivs
+ %w{ @a @b @c @d @e @f @g @h @i @j @k }
+ end
+
+ def set_ivs
+ expected_ivs.each { instance_variable_set(_1, 1) }
+ self
+ end
+ end
+
+ class ShapeOrder
+ def initialize
+ @b = :b # 5 => 6
+ end
+
+ def set_b
+ @b = :b # 5 => 6
+ end
+
+ def set_c
+ @c = :c # 5 => 7
+ end
+ end
+
+ class OrderedAlloc
+ def add_ivars
+ 10.times do |i|
+ instance_variable_set("@foo" + i.to_s, 0)
+ end
+ end
+ end
+
+ class Example
+ def initialize
+ @a = 1
+ end
+ end
+
+ class RemoveAndAdd
+ def add_foo
+ @foo = 1
+ end
+
+ def remove_foo
+ remove_instance_variable(:@foo)
+ end
+
+ def add_bar
+ @bar = 1
+ end
+ end
+
+ class TooComplex
+ attr_reader :hopefully_unique_name, :b
+
+ def initialize
+ @hopefully_unique_name = "a"
+ @b = "b"
+ end
+
+ # Make enough lazily defined accessors to allow us to force
+ # polymorphism
+ class_eval (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ "def a#{_1}_m; @a#{_1} ||= #{_1}; end"
+ }.join(" ; ")
+
+ class_eval "attr_accessor " + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ ":a#{_1}"
+ }.join(", ")
+
+ def iv_not_defined; @not_defined; end
+
+ def write_iv_method
+ self.a3 = 12345
+ end
+
+ def write_iv
+ @a3 = 12345
+ end
+ end
+
+ # RubyVM::Shape.of returns new instances of shape objects for
+ # each call. This helper method allows us to define equality for
+ # shapes
+ def assert_shape_equal(shape1, shape2)
+ assert_equal(shape1.id, shape2.id)
+ assert_equal(shape1.parent_id, shape2.parent_id)
+ assert_equal(shape1.depth, shape2.depth)
+ assert_equal(shape1.type, shape2.type)
+ end
+
+ def refute_shape_equal(shape1, shape2)
+ refute_equal(shape1.id, shape2.id)
+ end
+
+ def test_iv_order_correct_on_complex_objects
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times {
+ IVOrder.new.instance_variable_set("@a#{_1}", 1)
+ }
+
+ obj = IVOrder.new
+ iv_list = obj.set_ivs.instance_variables
+ assert_equal obj.expected_ivs, iv_list.map(&:to_s)
+ end
+
+ def test_too_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_ordered_alloc_is_not_complex
+ 5.times { OrderedAlloc.new.add_ivars }
+ obj = JSON.parse(ObjectSpace.dump(OrderedAlloc))
+ assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+
+ def test_too_many_ivs_on_obj
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Hi.new
+ obj.instance_variable_set(:@b, 1)
+ obj.instance_variable_set(:@c, 1)
+ obj.instance_variable_set(:@d, 1)
+
+ assert_predicate RubyVM::Shape.of(obj), :too_complex?
+ end;
+ end
+
+ def test_too_many_ivs_on_class
+ obj = Class.new
+
+ (MANY_IVS + 1).times do
+ obj.instance_variable_set(:"@a#{_1}", 1)
+ end
+
+ assert_false RubyVM::Shape.of(obj).too_complex?
+ end
+
+ def test_removing_when_too_many_ivs_on_class
+ obj = Class.new
+
+ (MANY_IVS + 2).times do
+ obj.instance_variable_set(:"@a#{_1}", 1)
+ end
+ (MANY_IVS + 2).times do
+ obj.remove_instance_variable(:"@a#{_1}")
+ end
+
+ assert_empty obj.instance_variables
+ end
+
+ def test_removing_when_too_many_ivs_on_module
+ obj = Module.new
+
+ (MANY_IVS + 2).times do
+ obj.instance_variable_set(:"@a#{_1}", 1)
+ end
+ (MANY_IVS + 2).times do
+ obj.remove_instance_variable(:"@a#{_1}")
+ end
+
+ assert_empty obj.instance_variables
+ end
+
+ def test_too_complex_geniv
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class TooComplex < Hash
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", 1)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:@very_unique, 3)
+ tc.instance_variable_set(:@very_unique2, 4)
+ assert_equal 3, tc.instance_variable_get(:@very_unique)
+ assert_equal 4, tc.instance_variable_get(:@very_unique2)
+
+ assert_equal [:@very_unique, :@very_unique2], tc.instance_variables
+ end;
+ end
+
+ def test_use_all_shapes_then_freeze
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+ RubyVM::Shape.exhaust_shapes(3)
+
+ obj = Hi.new
+ i = 0
+ while RubyVM::Shape.shapes_available > 0
+ obj.instance_variable_set(:"@b#{i}", 1)
+ i += 1
+ end
+ obj.freeze
+
+ assert obj.frozen?
+ end;
+ end
+
+ def test_run_out_of_shape_for_object
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def initialize
+ @a = 1
+ end
+ end
+ RubyVM::Shape.exhaust_shapes
+
+ A.new
+ end;
+ end
+
+ def test_run_out_of_shape_for_class_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ c = Class.new
+ c.instance_variable_set(:@a, 1)
+ assert_equal(1, c.instance_variable_get(:@a))
+
+ c.remove_instance_variable(:@a)
+ assert_nil(c.instance_variable_get(:@a))
+
+ assert_raise(NameError) do
+ c.remove_instance_variable(:@a)
+ end
+ end;
+ end
+
+ def test_evacuate_class_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Class.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_evacuate_generic_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Hash.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_evacuate_object_ivar_and_compaction
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ count = 20
+
+ c = Object.new
+ count.times do |ivar|
+ c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}")
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ GC.auto_compact = true
+ GC.stress = true
+
+ # Cause evacuation
+ c.instance_variable_set(:@a, o = Object.new)
+ assert_equal(o, c.instance_variable_get(:@a))
+
+ GC.stress = false
+
+ count.times do |ivar|
+ assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}")
+ end
+ end;
+ end
+
+ def test_gc_stress_during_evacuate_generic_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ [].instance_variable_set(:@a, 1)
+
+ RubyVM::Shape.exhaust_shapes
+
+ ary = 10.times.map { [] }
+
+ GC.stress = true
+ ary.each do |o|
+ o.instance_variable_set(:@a, 1)
+ o.instance_variable_set(:@b, 1)
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_for_module_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ module Foo
+ @a = 1
+ @b = 2
+ assert_equal 1, @a
+ assert_equal 2, @b
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_for_class_cvar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ c = Class.new
+
+ c.class_variable_set(:@@a, 1)
+ assert_equal(1, c.class_variable_get(:@@a))
+
+ c.class_eval { remove_class_variable(:@@a) }
+ assert_false(c.class_variable_defined?(:@@a))
+
+ assert_raise(NameError) do
+ c.class_eval { remove_class_variable(:@@a) }
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_generic_instance_variable_set
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class TooComplex < Hash
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:@a, 1)
+ tc.instance_variable_set(:@b, 2)
+
+ tc.remove_instance_variable(:@a)
+ assert_nil(tc.instance_variable_get(:@a))
+
+ assert_raise(NameError) do
+ tc.remove_instance_variable(:@a)
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_generic_ivar_set
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi < String
+ def initialize
+ 8.times do |i|
+ instance_variable_set("@ivar_#{i}", i)
+ end
+ end
+
+ def transition
+ @hi_transition ||= 1
+ end
+ end
+
+ a = Hi.new
+
+ # Try to run out of shapes
+ RubyVM::Shape.exhaust_shapes
+
+ assert_equal 1, a.transition
+ assert_equal 1, a.transition
+ end;
+ end
+
+ def test_run_out_of_shape_instance_variable_defined
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ attr_reader :a, :b, :c, :d
+ def initialize
+ @a = @b = @c = @d = 1
+ end
+ end
+
+ RubyVM::Shape.exhaust_shapes
+
+ a = A.new
+ assert_equal true, a.instance_variable_defined?(:@a)
+ end;
+ end
+
+ def test_run_out_of_shape_instance_variable_defined_on_module
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::Shape.exhaust_shapes
+
+ module A
+ @a = @b = @c = @d = 1
+ end
+
+ assert_equal true, A.instance_variable_defined?(:@a)
+ end;
+ end
+
+ def test_run_out_of_shape_during_remove_instance_variable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ o = Object.new
+ 10.times { |i| o.instance_variable_set(:"@a#{i}", i) }
+
+ RubyVM::Shape.exhaust_shapes
+
+ o.remove_instance_variable(:@a0)
+ (1...10).each do |i|
+ assert_equal(i, o.instance_variable_get(:"@a#{i}"))
+ end
+ end;
+ end
+
+ def test_run_out_of_shape_remove_instance_variable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ attr_reader :a, :b, :c, :d
+ def initialize
+ @a = @b = @c = @d = 1
+ end
+ end
+
+ a = A.new
+
+ RubyVM::Shape.exhaust_shapes
+
+ a.remove_instance_variable(:@b)
+ assert_nil a.b
+
+ a.remove_instance_variable(:@a)
+ assert_nil a.a
+
+ a.remove_instance_variable(:@c)
+ assert_nil a.c
+
+ assert_equal 1, a.d
+ end;
+ end
+
+ def test_run_out_of_shape_rb_obj_copy_ivar
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def initialize
+ init # Avoid right sizing
+ end
+
+ def init
+ @a = @b = @c = @d = @e = @f = 1
+ end
+ end
+
+ a = A.new
+
+ RubyVM::Shape.exhaust_shapes
+
+ a.dup
+ end;
+ end
+
+ def test_evacuate_generic_ivar_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true)
+ o = []
+ o.instance_variable_set(:@a, 1)
+
+ RubyVM::Shape.exhaust_shapes
+
+ ary = 1_000_000.times.map { [] }
+ begin;
+ ary.each do |o|
+ o.instance_variable_set(:@a, 1)
+ o.instance_variable_set(:@b, 1)
+ end
+ ary.clear
+ ary = nil
+ GC.start
+ end;
+ end
+
+ def test_use_all_shapes_module
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Module.new
+ 3.times do
+ obj.instance_variable_set(:"@a#{_1}", _1)
+ end
+
+ ivs = 3.times.map do
+ obj.instance_variable_get(:"@a#{_1}")
+ end
+
+ assert_equal [0, 1, 2], ivs
+ end;
+ end
+
+ def test_complex_freeze_after_clone
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+
+ RubyVM::Shape.exhaust_shapes(2)
+
+ obj = Object.new
+ i = 0
+ while RubyVM::Shape.shapes_available > 0
+ obj.instance_variable_set(:"@b#{i}", i)
+ i += 1
+ end
+
+ v = obj.clone(freeze: true)
+ assert_predicate v, :frozen?
+ assert_equal 0, v.instance_variable_get(:@b0)
+ end;
+ end
+
+ def test_too_complex_ractor
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take
+ assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort
+ end;
+ end
+
+ def test_too_complex_ractor_shareable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
+ def test_too_complex_obj_ivar_ractor_share
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+
+ RubyVM::Shape.exhaust_shapes
+
+ r = Ractor.new do
+ o = Object.new
+ o.instance_variable_set(:@a, "hello")
+ Ractor.yield(o)
+ end
+
+ o = r.take
+ assert_equal "hello", o.instance_variable_get(:@a)
+ end;
+ end
+
+ def test_too_complex_generic_ivar_ractor_share
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+
+ RubyVM::Shape.exhaust_shapes
+
+ r = Ractor.new do
+ o = []
+ o.instance_variable_set(:@a, "hello")
+ Ractor.yield(o)
+ end
+
+ o = r.take
+ assert_equal "hello", o.instance_variable_get(:@a)
+ end;
+ end
+
+ def test_read_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ end
+
+ def test_read_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ assert_equal 3, tc.a3
+ end
+
+ def test_write_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv_method
+ tc.write_iv_method
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_write_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv
+ tc.write_iv
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_iv_read_via_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ assert_equal 3, tc.instance_variable_get(:@a3)
+ end
+
+ def test_delete_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.remove_instance_variable(:@a3)
+ assert_nil tc.a3
+ end
+
+ def test_delete_undefined_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ refute tc.instance_variable_defined?(:@a3)
+ assert_raise(NameError) do
+ tc.remove_instance_variable(:@a3)
+ end
+ assert_nil tc.a3
+ end
+
+ def test_remove_instance_variable
+ ivars_count = 5
+ object = Object.new
+ ivars_count.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, 2, 3, 4], ivars
+
+ object.remove_instance_variable(:@ivar_2)
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, nil, 3, 4], ivars
+ end
+
+ def test_remove_instance_variable_when_out_of_shapes
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ivars_count = 5
+ object = Object.new
+ ivars_count.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, 2, 3, 4], ivars
+
+ RubyVM::Shape.exhaust_shapes
+
+ object.remove_instance_variable(:@ivar_2)
+
+ ivars = ivars_count.times.map do |i|
+ object.instance_variable_get("@ivar_#{i}")
+ end
+ assert_equal [0, 1, nil, 3, 4], ivars
+ end;
+ end
+
+ def test_remove_instance_variable_capacity_transition
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ t_object_shape = RubyVM::Shape.find_by_id(RubyVM::Shape::FIRST_T_OBJECT_SHAPE_ID)
+ assert_equal(RubyVM::Shape::SHAPE_T_OBJECT, t_object_shape.type)
+
+ initial_capacity = t_object_shape.capacity
+
+ # a does not transition in capacity
+ a = Class.new.new
+ initial_capacity.times do |i|
+ a.instance_variable_set(:"@ivar#{i + 1}", i)
+ end
+
+ # b transitions in capacity
+ b = Class.new.new
+ (initial_capacity + 1).times do |i|
+ b.instance_variable_set(:"@ivar#{i}", i)
+ end
+
+ assert_operator(RubyVM::Shape.of(a).capacity, :<, RubyVM::Shape.of(b).capacity)
+
+ # b will now have the same tree as a
+ b.remove_instance_variable(:@ivar0)
+
+ a.instance_variable_set(:@foo, 1)
+ a.instance_variable_set(:@bar, 1)
+
+ # Check that there is no heap corruption
+ GC.verify_internal_consistency
+ end;
+ end
+
+ def test_freeze_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.freeze
+ assert_raise(FrozenError) { tc.a3_m }
+ # doesn't transition to frozen shape in this case
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_read_undefined_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal nil, tc.iv_not_defined
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_shape_order
+ bar = ShapeOrder.new # 0 => 1
+ bar.set_c # 1 => 2
+ bar.set_b # 2 => 2
+
+ foo = ShapeOrder.new # 0 => 1
+ shape_id = RubyVM::Shape.of(foo).id
+ foo.set_b # should not transition
+ assert_equal shape_id, RubyVM::Shape.of(foo).id
+ end
+
+ def test_iv_index
+ example = RemoveAndAdd.new
+ initial_shape = RubyVM::Shape.of(example)
+ assert_equal 0, initial_shape.next_iv_index
+
+ example.add_foo # makes a transition
+ add_foo_shape = RubyVM::Shape.of(example)
+ assert_equal([:@foo], example.instance_variables)
+ assert_equal(initial_shape.id, add_foo_shape.parent.id)
+ assert_equal(1, add_foo_shape.next_iv_index)
+
+ example.remove_foo # makes a transition
+ remove_foo_shape = RubyVM::Shape.of(example)
+ assert_equal([], example.instance_variables)
+ assert_shape_equal(initial_shape, remove_foo_shape)
+
+ example.add_bar # makes a transition
+ bar_shape = RubyVM::Shape.of(example)
+ assert_equal([:@bar], example.instance_variables)
+ assert_equal(initial_shape.id, bar_shape.parent_id)
+ assert_equal(1, bar_shape.next_iv_index)
+ end
+
+ def test_remove_then_add_again
+ example = RemoveAndAdd.new
+ _initial_shape = RubyVM::Shape.of(example)
+
+ example.add_foo # makes a transition
+ add_foo_shape = RubyVM::Shape.of(example)
+ example.remove_foo # makes a transition
+ example.add_foo # makes a transition
+ assert_shape_equal(add_foo_shape, RubyVM::Shape.of(example))
+ end
+
+ class TestObject; end
+
+ def test_new_obj_has_t_object_shape
+ obj = TestObject.new
+ shape = RubyVM::Shape.of(obj)
+ assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_nil shape.parent
+ end
+
+ def test_str_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(""))
+ end
+
+ def test_array_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
+ end
+
+ def test_true_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id)
+ end
+
+ def test_nil_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id)
+ end
+
+ def test_root_shape_transition_to_special_const_on_frozen
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of([].freeze).id)
+ end
+
+ def test_basic_shape_transition
+ omit "Failing with RJIT for some reason" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
+ obj = Example.new
+ shape = RubyVM::Shape.of(obj)
+ refute_equal(RubyVM::Shape.root_shape, shape)
+ assert_equal :@a, shape.edge_name
+ assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
+
+ shape = shape.parent
+ assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_nil shape.parent
+
+ assert_equal(1, obj.instance_variable_get(:@a))
+ end
+
+ def test_different_objects_make_same_transition
+ obj = []
+ obj2 = ""
+ obj.instance_variable_set(:@a, 1)
+ obj2.instance_variable_set(:@a, 1)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_objects
+ obj = Example.new
+ obj2 = obj.dup
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_too_complex_objects_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true)
+ RubyVM::Shape.exhaust_shapes
+
+ o = Object.new
+ o.instance_variable_set(:@a, 0)
+ begin;
+ 1_000_000.times do
+ o.dup
+ end
+ end;
+ end
+
+ def test_freezing_and_duplicating_object
+ obj = Object.new.freeze
+ obj2 = obj.dup
+ refute_predicate(obj2, :frozen?)
+ # dup'd objects shouldn't be frozen, and the shape should be the
+ # parent shape of the copied object
+ assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
+ end
+
+ def test_freezing_and_duplicating_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.dup
+ refute_predicate(obj2, :frozen?)
+ refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_duplicating_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ str2 = str.dup
+ refute_predicate(str2, :frozen?)
+ refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
+ assert_equal(str2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_cloning_objects
+ obj = Object.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_cloning_with_freeze_option
+ obj = Object.new
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent)
+ end
+
+ def test_freezing_and_cloning_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_cloning_string
+ str = ("str" + "str").freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ end
+
+ def test_freezing_and_cloning_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ assert_equal(str2.instance_variable_get(:@a), 1)
+ end
+
+ def test_out_of_bounds_shape
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(RubyVM.stat[:next_shape_id])
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(-1)
+ end
+ end
+
+ def ensure_complex
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ tc = TooComplex.new
+ tc.send("a#{_1}_m")
+ end
+ end
+end if defined?(RubyVM::Shape)
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb
index c5043eea59..7877a35129 100644
--- a/test/ruby/test_signal.rb
+++ b/test/ruby/test_signal.rb
@@ -323,49 +323,6 @@ class TestSignal < Test::Unit::TestCase
end;
end
- def test_sigchld_ignore
- omit 'no SIGCHLD' unless Signal.list['CHLD']
- old = trap(:CHLD, 'IGNORE')
- cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ]
- assert(system(*cmd, 'exit!(0)'), 'no ECHILD')
- IO.pipe do |r, w|
- pid = spawn(*cmd, "STDIN.read", in: r)
- nb = Process.wait(pid, Process::WNOHANG)
- th = Thread.new(Thread.current) do |parent|
- Thread.pass until parent.stop? # wait for parent to Process.wait
- w.close
- end
- assert_raise(Errno::ECHILD) { Process.wait(pid) }
- th.join
- assert_nil nb
- end
-
- IO.pipe do |r, w|
- pids = 3.times.map { spawn(*cmd, 'exit!', out: w) }
- w.close
- zombies = pids.dup
- assert_nil r.read(1), 'children dead'
-
- Timeout.timeout(10) do
- zombies.delete_if do |pid|
- begin
- Process.kill(0, pid)
- false
- rescue Errno::ESRCH
- true
- end
- end while zombies[0]
- end
- assert_predicate zombies, :empty?, 'zombies leftover'
-
- pids.each do |pid|
- assert_raise(Errno::ECHILD) { Process.waitpid(pid) }
- end
- end
- ensure
- trap(:CHLD, old) if Signal.list['CHLD']
- end
-
def test_sigwait_fd_unused
t = EnvUtil.apply_timeout_scale(0.1)
assert_separately([], <<-End)
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index f2e73eb58d..c453ecd350 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -362,11 +362,16 @@ class TestSprintf < Test::Unit::TestCase
def test_char
assert_equal("a", sprintf("%c", 97))
assert_equal("a", sprintf("%c", ?a))
- assert_raise(ArgumentError) { sprintf("%c", sprintf("%c%c", ?a, ?a)) }
+ assert_equal("a", sprintf("%c", "a"))
+ assert_equal("a", sprintf("%c", sprintf("%c%c", ?a, ?a)))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%c", ?a))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%-1c", ?a))
assert_equal(" " * BSIZ + "a", sprintf("%#{ BSIZ + 1 }c", ?a))
assert_equal("a" + " " * BSIZ, sprintf("%-#{ BSIZ + 1 }c", ?a))
+ assert_raise(ArgumentError) { sprintf("%c", -1) }
+ s = sprintf("%c".encode(Encoding::US_ASCII), 0x80)
+ assert_equal("\x80".b, s)
+ assert_predicate(s, :valid_encoding?)
end
def test_string
@@ -507,6 +512,16 @@ class TestSprintf < Test::Unit::TestCase
end
end
+ def test_coderange
+ format_str = "wrong constant name %s"
+ interpolated_str = "\u3042"
+ assert_predicate format_str, :ascii_only?
+ refute_predicate interpolated_str, :ascii_only?
+
+ str = format_str % interpolated_str
+ refute_predicate str, :ascii_only?
+ end
+
def test_named_default
h = Hash.new('world')
assert_equal("hello world", "hello %{location}" % h)
diff --git a/test/ruby/test_stack.rb b/test/ruby/test_stack.rb
index 763aeb6bc2..8a78848322 100644
--- a/test/ruby/test_stack.rb
+++ b/test/ruby/test_stack.rb
@@ -18,7 +18,6 @@ class TestStack < Test::Unit::TestCase
env = {}
env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
- env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS']
stdout, stderr, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, timeout: 30)
assert(!status.signaled?, FailDesc[status, nil, stderr])
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index d96e8dff22..ebe85dac82 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -9,9 +9,6 @@ class TestString < Test::Unit::TestCase
def initialize(*args)
@cls = String
- @aref_re_nth = true
- @aref_re_silent = false
- @aref_slicebang_silent = true
super
end
@@ -80,6 +77,13 @@ class TestString < Test::Unit::TestCase
assert_equal("mystring", str.__send__(:initialize, "mystring", capacity: 1000))
str = S("mystring")
assert_equal("mystring", str.__send__(:initialize, str, capacity: 1000))
+
+ if @cls == String
+ 100.times {
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".
+ __send__(:initialize, capacity: -1)
+ }
+ end
end
def test_initialize_shared
@@ -100,7 +104,7 @@ class TestString < Test::Unit::TestCase
return unless @cls == String
assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
-code = proc {('x'*100000).__send__(:initialize, '')}
+code = proc {('x'*100_000).__send__(:initialize, '')}
1_000.times(&code)
PREP
100_000.times(&code)
@@ -112,7 +116,7 @@ CODE
return unless @cls == String
assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
-code = proc {0.to_s.__send__(:initialize, capacity: 10000)}
+code = proc {0.to_s.__send__(:initialize, capacity: 100_000)}
1_000.times(&code)
PREP
100_000.times(&code)
@@ -146,14 +150,12 @@ CODE
assert_equal(nil, S("FooBar")[S("xyzzy")])
assert_equal(nil, S("FooBar")[S("plugh")])
- if @aref_re_nth
- assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 1])
- assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 2])
- assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, 3])
- assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -1])
- assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -2])
- assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, -3])
- end
+ assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 1])
+ assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 2])
+ assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, 3])
+ assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -1])
+ assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -2])
+ assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, -3])
o = Object.new
def o.to_int; 2; end
@@ -199,24 +201,18 @@ CODE
assert_equal(S("BarBar"), s)
s[/..r$/] = S("Foo")
assert_equal(S("BarFoo"), s)
- if @aref_re_silent
- s[/xyzzy/] = S("None")
- assert_equal(S("BarFoo"), s)
- else
- assert_raise(IndexError) { s[/xyzzy/] = S("None") }
- end
- if @aref_re_nth
- s[/([A-Z]..)([A-Z]..)/, 1] = S("Foo")
- assert_equal(S("FooFoo"), s)
- s[/([A-Z]..)([A-Z]..)/, 2] = S("Bar")
- assert_equal(S("FooBar"), s)
- assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, 3] = "None" }
- s[/([A-Z]..)([A-Z]..)/, -1] = S("Foo")
- assert_equal(S("FooFoo"), s)
- s[/([A-Z]..)([A-Z]..)/, -2] = S("Bar")
- assert_equal(S("BarFoo"), s)
- assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, -3] = "None" }
- end
+ assert_raise(IndexError) { s[/xyzzy/] = S("None") }
+
+ s[/([A-Z]..)([A-Z]..)/, 1] = S("Foo")
+ assert_equal(S("FooFoo"), s)
+ s[/([A-Z]..)([A-Z]..)/, 2] = S("Bar")
+ assert_equal(S("FooBar"), s)
+ assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, 3] = "None" }
+ s[/([A-Z]..)([A-Z]..)/, -1] = S("Foo")
+ assert_equal(S("FooFoo"), s)
+ s[/([A-Z]..)([A-Z]..)/, -2] = S("Bar")
+ assert_equal(S("BarFoo"), s)
+ assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, -3] = "None" }
s = S("FooBar")
s[S("Foo")] = S("Bar")
@@ -301,6 +297,9 @@ CODE
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -1}
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << 0x81308130}
assert_nothing_raised {S("a".force_encoding(Encoding::GB18030)) << 0x81308130}
+
+ s = "\x95".force_encoding(Encoding::SJIS).tap(&:valid_encoding?)
+ assert_predicate(s << 0x5c, :valid_encoding?)
end
def test_MATCH # '=~'
@@ -587,6 +586,8 @@ CODE
assert_equal("foo", s.chomp!("\n"))
s = "foo\r"
assert_equal("foo", s.chomp!("\n"))
+
+ assert_raise(ArgumentError) {String.new.chomp!("", "")}
ensure
$/ = save
$VERBOSE = verbose
@@ -661,6 +662,27 @@ CODE
assert_equal(Encoding::UTF_8, "#{s}x".encoding)
end
+ def test_string_interpolations_across_size_pools_get_embedded
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ require 'objspace'
+ base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ small_obj_size = (base_slot_size / 2)
+ large_obj_size = base_slot_size * 2
+
+ a = "a" * small_obj_size
+ b = "a" * large_obj_size
+
+ res = "#{a}, #{b}"
+ dump_res = ObjectSpace.dump(res)
+ dump_orig = ObjectSpace.dump(a)
+ new_slot_size = Integer(dump_res.match(/"slot_size":(\d+)/)[1])
+ orig_slot_size = Integer(dump_orig.match(/"slot_size":(\d+)/)[1])
+
+ assert_match(/"embedded":true/, dump_res)
+ assert_operator(new_slot_size, :>, orig_slot_size)
+ end
+
def test_count
a = S("hello world")
assert_equal(5, a.count(S("lo")))
@@ -875,6 +897,18 @@ CODE
}
end
+ def test_undump_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ a = S("Test") << 1 << 2 << 3 << 9 << 13 << 10
+ EnvUtil.under_gc_compact_stress do
+ assert_equal(a, S('"Test\\x01\\x02\\x03\\t\\r\\n"').undump)
+ end
+
+ EnvUtil.under_gc_compact_stress do
+ assert_equal(S("\u{ABCDE 10ABCD}"), S('"\\u{ABCDE 10ABCD}"').undump)
+ end
+ end
+
def test_dup
for frozen in [ false, true ]
a = S("hello")
@@ -887,6 +921,18 @@ CODE
end
end
+ class StringWithIVSet < String
+ def set_iv
+ @foo = 1
+ end
+ end
+
+ def test_ivar_set_after_frozen_dup
+ str = StringWithIVSet.new.freeze
+ str.dup.set_iv
+ assert_raise(FrozenError) { str.set_iv }
+ end
+
def test_each
verbose, $VERBOSE = $VERBOSE, nil
@@ -1062,6 +1108,22 @@ CODE
assert_equal("C", res[2])
end
+ def test_grapheme_clusters_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #todo]", rss: true)
+ begin;
+ str = "hello world".encode(Encoding::UTF_32LE)
+
+ 10_000.times do
+ str.grapheme_clusters
+ end
+ end;
+ end
+
+ def test_byteslice_grapheme_clusters
+ string = "안녕"
+ assert_equal(["안"], string.byteslice(0,4).grapheme_clusters)
+ end
+
def test_each_line
verbose, $VERBOSE = $VERBOSE, nil
@@ -1121,14 +1183,19 @@ CODE
assert_equal(S("world"), res[1])
res = []
- S("hello\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\n\n\nworld\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\n"), res[1])
res = []
- S("hello\r\n\r\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\r\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\r\n\r\nworld\r\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\r\n"), res[1])
+
+ res = []
+ S("hello\r\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world"), res[1])
res = []
S("hello!world").each_line(S('!'), chomp: true) {|x| res << x}
@@ -1211,6 +1278,11 @@ CODE
assert_raise(ArgumentError) { S("foo").gsub }
end
+ def test_gsub_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal(S("h<e>ll<o>"), S("hello").gsub(/([aeiou])/, S('<\1>'))) }
+ end
+
def test_gsub_encoding
a = S("hello world")
a.force_encoding Encoding::UTF_8
@@ -1254,6 +1326,15 @@ CODE
assert_nil(a.sub!(S('X'), S('Y')))
end
+ def test_gsub_bang_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ a = S("hello")
+ a.gsub!(/([aeiou])/, S('<\1>'))
+ assert_equal(S("h<e>ll<o>"), a)
+ end
+ end
+
def test_sub_hash
assert_equal('azc', S('abc').sub(/b/, "b" => "z"))
assert_equal('ac', S('abc').sub(/b/, {}))
@@ -1281,6 +1362,9 @@ CODE
assert_not_equal(S("a").hash, S("a\0").hash, bug4104)
bug9172 = '[ruby-core:58658] [Bug #9172]'
assert_not_equal(S("sub-setter").hash, S("discover").hash, bug9172)
+ assert_equal(S("").hash, S("".encode(Encoding::UTF_32BE)).hash)
+ h1, h2 = ["\x80", "\x81"].map {|c| c.b.hash ^ c.hash}
+ assert_not_equal(h1, h2)
end
def test_hex
@@ -1301,54 +1385,54 @@ CODE
end
def test_index
- assert_equal(0, S("hello").index(?h))
- assert_equal(1, S("hello").index(S("ell")))
- assert_equal(2, S("hello").index(/ll./))
+ assert_index(0, S("hello"), ?h)
+ assert_index(1, S("hello"), S("ell"))
+ assert_index(2, S("hello"), /ll./)
- assert_equal(3, S("hello").index(?l, 3))
- assert_equal(3, S("hello").index(S("l"), 3))
- assert_equal(3, S("hello").index(/l./, 3))
+ assert_index(3, S("hello"), ?l, 3)
+ assert_index(3, S("hello"), S("l"), 3)
+ assert_index(3, S("hello"), /l./, 3)
- assert_nil(S("hello").index(?z, 3))
- assert_nil(S("hello").index(S("z"), 3))
- assert_nil(S("hello").index(/z./, 3))
+ assert_index(nil, S("hello"), ?z, 3)
+ assert_index(nil, S("hello"), S("z"), 3)
+ assert_index(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").index(?z))
- assert_nil(S("hello").index(S("z")))
- assert_nil(S("hello").index(/z./))
+ assert_index(nil, S("hello"), ?z)
+ assert_index(nil, S("hello"), S("z"))
+ assert_index(nil, S("hello"), /z./)
- assert_equal(0, S("").index(S("")))
- assert_equal(0, S("").index(//))
- assert_nil(S("").index(S("hello")))
- assert_nil(S("").index(/hello/))
- assert_equal(0, S("hello").index(S("")))
- assert_equal(0, S("hello").index(//))
+ assert_index(0, S(""), S(""))
+ assert_index(0, S(""), //)
+ assert_index(nil, S(""), S("hello"))
+ assert_index(nil, S(""), /hello/)
+ assert_index(0, S("hello"), S(""))
+ assert_index(0, S("hello"), //)
s = S("long") * 1000 << "x"
- assert_nil(s.index(S("y")))
- assert_equal(4 * 1000, s.index(S("x")))
+ assert_index(nil, s, S("y"))
+ assert_index(4 * 1000, s, S("x"))
s << "yx"
- assert_equal(4 * 1000, s.index(S("x")))
- assert_equal(4 * 1000, s.index(S("xyx")))
+ assert_index(4 * 1000, s, S("x"))
+ assert_index(4 * 1000, s, S("xyx"))
o = Object.new
def o.to_str; "bar"; end
- assert_equal(3, S("foobarbarbaz").index(o))
+ assert_index(3, S("foobarbarbaz"), o)
assert_raise(TypeError) { S("foo").index(Object.new) }
- assert_nil(S("foo").index(//, -100))
- assert_nil($~)
+ assert_index(nil, S("foo"), //, -100)
+ assert_index(nil, S("foo"), //, 4)
- assert_equal(2, S("abcdbce").index(/b\Kc/))
+ assert_index(2, S("abcdbce"), /b\Kc/)
- assert_equal(0, S("こんにちは").index(?こ))
- assert_equal(1, S("こんにちは").index(S("んにち")))
- assert_equal(2, S("こんにちは").index(/にち./))
+ assert_index(0, S("こんにちは"), ?こ)
+ assert_index(1, S("こんにちは"), S("んにち"))
+ assert_index(2, S("こんにちは"), /にち./)
- assert_equal(0, S("にんにちは").index(?に, 0))
- assert_equal(2, S("にんにちは").index(?に, 1))
- assert_equal(2, S("にんにちは").index(?に, 2))
- assert_nil(S("にんにちは").index(?に, 3))
+ assert_index(0, S("にんにちは"), ?に, 0)
+ assert_index(2, S("にんにちは"), ?に, 1)
+ assert_index(2, S("にんにちは"), ?に, 2)
+ assert_index(nil, S("にんにちは"), ?に, 3)
end
def test_insert
@@ -1495,57 +1579,57 @@ CODE
end
def test_rindex
- assert_equal(3, S("hello").rindex(?l))
- assert_equal(6, S("ell, hello").rindex(S("ell")))
- assert_equal(7, S("ell, hello").rindex(/ll./))
+ assert_rindex(3, S("hello"), ?l)
+ assert_rindex(6, S("ell, hello"), S("ell"))
+ assert_rindex(7, S("ell, hello"), /ll./)
- assert_equal(3, S("hello,lo").rindex(?l, 3))
- assert_equal(3, S("hello,lo").rindex(S("l"), 3))
- assert_equal(3, S("hello,lo").rindex(/l./, 3))
+ assert_rindex(3, S("hello,lo"), ?l, 3)
+ assert_rindex(3, S("hello,lo"), S("l"), 3)
+ assert_rindex(3, S("hello,lo"), /l./, 3)
- assert_nil(S("hello").rindex(?z, 3))
- assert_nil(S("hello").rindex(S("z"), 3))
- assert_nil(S("hello").rindex(/z./, 3))
+ assert_rindex(nil, S("hello"), ?z, 3)
+ assert_rindex(nil, S("hello"), S("z"), 3)
+ assert_rindex(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").rindex(?z))
- assert_nil(S("hello").rindex(S("z")))
- assert_nil(S("hello").rindex(/z./))
+ assert_rindex(nil, S("hello"), ?z)
+ assert_rindex(nil, S("hello"), S("z"))
+ assert_rindex(nil, S("hello"), /z./)
- assert_equal(5, S("hello").rindex(S("")))
- assert_equal(5, S("hello").rindex(S(""), 5))
- assert_equal(4, S("hello").rindex(S(""), 4))
- assert_equal(0, S("hello").rindex(S(""), 0))
+ assert_rindex(5, S("hello"), S(""))
+ assert_rindex(5, S("hello"), S(""), 5)
+ assert_rindex(4, S("hello"), S(""), 4)
+ assert_rindex(0, S("hello"), S(""), 0)
o = Object.new
def o.to_str; "bar"; end
- assert_equal(6, S("foobarbarbaz").rindex(o))
+ assert_rindex(6, S("foobarbarbaz"), o)
assert_raise(TypeError) { S("foo").rindex(Object.new) }
- assert_nil(S("foo").rindex(//, -100))
- assert_nil($~)
+ assert_rindex(nil, S("foo"), //, -100)
- assert_equal(3, S("foo").rindex(//))
- assert_equal([3, 3], $~.offset(0))
+ m = assert_rindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_rindex(3, S("foo"), //, 4)
- assert_equal(5, S("abcdbce").rindex(/b\Kc/))
+ assert_rindex(5, S("abcdbce"), /b\Kc/)
- assert_equal(2, S("こんにちは").rindex(?に))
- assert_equal(6, S("にちは、こんにちは").rindex(S("にちは")))
- assert_equal(6, S("にちは、こんにちは").rindex(/にち./))
+ assert_rindex(2, S("こんにちは"), ?に)
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"))
+ assert_rindex(6, S("にちは、こんにちは"), /にち./)
- assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 7))
- assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -2))
- assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 6))
- assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -3))
- assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 5))
- assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), -4))
- assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 1))
- assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 0))
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"), 7)
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"), -2)
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"), 6)
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"), -3)
+ assert_rindex(0, S("にちは、こんにちは"), S("にちは"), 5)
+ assert_rindex(0, S("にちは、こんにちは"), S("にちは"), -4)
+ assert_rindex(0, S("にちは、こんにちは"), S("にちは"), 1)
+ assert_rindex(0, S("にちは、こんにちは"), S("にちは"), 0)
- assert_equal(0, S("こんにちは").rindex(S("こんにちは")))
- assert_nil(S("こんにち").rindex(S("こんにちは")))
- assert_nil(S("こ").rindex(S("こんにちは")))
- assert_nil(S("").rindex(S("こんにちは")))
+ assert_rindex(0, S("こんにちは"), S("こんにちは"))
+ assert_rindex(nil, S("こんにち"), S("こんにちは"))
+ assert_rindex(nil, S("こ"), S("こんにちは"))
+ assert_rindex(nil, S(""), S("こんにちは"))
end
def test_rjust
@@ -1584,6 +1668,20 @@ CODE
assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./))
end
+ def test_scan_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { assert_equal([["1a"], ["2b"], ["3c"]], S("1a2b3c").scan(/(\d.)/)) }
+ end
+
+ def test_scan_segv
+ bug19159 = '[Bug #19159]'
+ assert_nothing_raised(Exception, bug19159) do
+ ObjectSpace.each_object(MatchData).to_a
+ "".scan(//)
+ ObjectSpace.each_object(MatchData).to_a.inspect
+ end
+ end
+
def test_size
assert_equal(0, S("").size)
assert_equal(4, S("1234").size)
@@ -1635,20 +1733,11 @@ CODE
assert_equal(S("FooBa"), a)
a = S("FooBar")
- if @aref_slicebang_silent
- assert_nil( a.slice!(6) )
- assert_nil( a.slice!(6r) )
- else
- assert_raise(IndexError) { a.slice!(6) }
- assert_raise(IndexError) { a.slice!(6r) }
- end
+ assert_nil( a.slice!(6) )
+ assert_nil( a.slice!(6r) )
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
- assert_nil( a.slice!(-7) )
- else
- assert_raise(IndexError) { a.slice!(-7) }
- end
+ assert_nil( a.slice!(-7) )
assert_equal(S("FooBar"), a)
a = S("FooBar")
@@ -1660,17 +1749,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
assert_nil(a.slice!(7,2)) # Maybe should be six?
- else
- assert_raise(IndexError) {a.slice!(7,2)} # Maybe should be six?
- end
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
assert_nil(a.slice!(-7,10))
- else
- assert_raise(IndexError) {a.slice!(-7,10)}
- end
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1682,17 +1763,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
assert_equal(S(""), a.slice!(6..2))
- else
- assert_raise(RangeError) {a.slice!(6..2)}
- end
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
assert_nil(a.slice!(-10..-7))
- else
- assert_raise(RangeError) {a.slice!(-10..-7)}
- end
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1704,17 +1777,9 @@ CODE
assert_equal(S("Foo"), a)
a=S("FooBar")
- if @aref_slicebang_silent
- assert_nil(a.slice!(/xyzzy/))
- else
- assert_raise(IndexError) {a.slice!(/xyzzy/)}
- end
+ assert_nil(a.slice!(/xyzzy/))
assert_equal(S("FooBar"), a)
- if @aref_slicebang_silent
- assert_nil(a.slice!(/plugh/))
- else
- assert_raise(IndexError) {a.slice!(/plugh/)}
- end
+ assert_nil(a.slice!(/plugh/))
assert_equal(S("FooBar"), a)
a=S("FooBar")
@@ -1891,10 +1956,15 @@ CODE
assert_send([S("hello"), :start_with?, S("hel")])
assert_not_send([S("hello"), :start_with?, S("el")])
assert_send([S("hello"), :start_with?, S("el"), S("he")])
+ assert_send([S("\xFF\xFE"), :start_with?, S("\xFF")])
+ assert_send([S("hello\xBE"), :start_with?, S("hello")])
+ assert_not_send([S("\u{c4}"), :start_with?, S("\xC3")])
bug5536 = '[ruby-core:40623]'
assert_raise(TypeError, bug5536) {S("str").start_with? :not_convertible_to_string}
+ end
+ def test_start_with_regexp
assert_equal(true, S("hello").start_with?(/hel/))
assert_equal("hel", $&)
assert_equal(false, S("hello").start_with?(/el/))
@@ -1997,6 +2067,16 @@ CODE
}
end
+ def test_sub_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress do
+ m = /&(?<foo>.*?);/.match(S("aaa &amp; yyy"))
+ assert_equal("amp", m["foo"])
+
+ assert_equal("aaa [amp] yyy", S("aaa &amp; yyy").sub(/&(?<foo>.*?);/, S('[\k<foo>]')))
+ end
+ end
+
def test_sub!
a = S("hello")
b = a.dup
@@ -2254,6 +2334,8 @@ CODE
assert_not_predicate(str, :ascii_only?)
assert_not_predicate(star, :ascii_only?)
assert_not_predicate(result, :ascii_only?, bug13950)
+
+ assert_equal(S("XYC"), S("ABC").tr("A-AB", "XY"))
end
def test_tr!
@@ -2278,6 +2360,8 @@ CODE
a = S("abc".force_encoding(Encoding::US_ASCII))
assert_nil(a.tr!(S("z"), S("\u0101")), '[ruby-core:22326]')
assert_equal(Encoding::US_ASCII, a.encoding, '[ruby-core:22326]')
+
+ assert_equal(S("XYC"), S("ABC").tr!("A-AB", "XY"))
end
def test_tr_s
@@ -2285,6 +2369,8 @@ CODE
assert_equal(S("h*o"), S("hello").tr_s(S("el"), S("*")))
assert_equal("a".hash, S("\u0101\u0101").tr_s("\u0101", "a").hash)
assert_equal(true, S("\u3041\u3041").tr("\u3041", "a").ascii_only?)
+
+ assert_equal(S("XYC"), S("ABC").tr_s("A-AB", "XY"))
end
def test_tr_s!
@@ -2297,6 +2383,8 @@ CODE
a = S("hello")
assert_equal(S("h*o"), a.tr_s!(S("el"), S("*")))
assert_equal(S("h*o"), a)
+
+ assert_equal(S("XYC"), S("ABC").tr_s!("A-AB", "XY"))
end
def test_unpack
@@ -2609,6 +2697,11 @@ CODE
assert_equal '"\x0012"', s.inspect, bug8290
end
+ def test_inspect_next_line
+ bug16842 = '[ruby-core:98231]'
+ assert_equal '"\\u0085"', 0x85.chr(Encoding::UTF_8).inspect, bug16842
+ end
+
def test_partition
assert_equal(%w(he l lo), S("hello").partition(/l/))
assert_equal(%w(he l lo), S("hello").partition("l"))
@@ -2795,6 +2888,11 @@ CODE
assert_equal("\u3042", s5)
assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc\x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("\x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S(" \x80 ".force_encoding('UTF-8')).rstrip! }
end
def test_lstrip
@@ -2826,11 +2924,13 @@ CODE
end
- def test_delete_prefix
+ def test_delete_prefix_type_error
assert_raise(TypeError) { S('hello').delete_prefix(nil) }
assert_raise(TypeError) { S('hello').delete_prefix(1) }
assert_raise(TypeError) { S('hello').delete_prefix(/hel/) }
+ end
+ def test_delete_prefix
s = S("hello")
assert_equal("lo", s.delete_prefix('hel'))
assert_equal("hello", s)
@@ -2850,8 +2950,9 @@ CODE
s = S("hello")
assert_equal("hello", s.delete_prefix("\u{3053 3093}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_prefix_broken_encoding
s = S("\xe3\x81\x82")
assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3"))
assert_equal("\xe3\x81\x82", s)
@@ -2860,23 +2961,31 @@ CODE
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.delete_prefix("\x95"))
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
- # clear coderange
+ assert_equal("\xFE", S("\xFF\xFE").delete_prefix("\xFF"))
+ assert_equal("\xBE", S("hello\xBE").delete_prefix("hello"))
+ assert_equal("\xBE", S("\xFFhello\xBE").delete_prefix("\xFFhello"))
+ end
+
+ def test_delete_prefix_clear_coderange
s = S("\u{3053 3093}hello")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_prefix("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_prefix_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("bba", s.delete_prefix(klass.new))
assert_equal("abba", s)
end
- def test_delete_prefix_bang
+ def test_delete_prefix_bang_type_error
assert_raise(TypeError) { S('hello').delete_prefix!(nil) }
assert_raise(TypeError) { S('hello').delete_prefix!(1) }
assert_raise(TypeError) { S('hello').delete_prefix!(/hel/) }
+ end
+ def test_delete_prefix_bang
s = S("hello")
assert_equal("lo", s.delete_prefix!('hel'))
assert_equal("lo", s)
@@ -2896,23 +3005,32 @@ CODE
s = S("hello")
assert_equal(nil, s.delete_prefix!("\u{3053 3093}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_prefix_bang_broken_encoding
s = S("\xe3\x81\x82")
assert_equal(nil, s.delete_prefix!("\xe3"))
assert_equal("\xe3\x81\x82", s)
- # clear coderange
+ s = S("\xFF\xFE")
+ assert_equal("\xFE", s.delete_prefix!("\xFF"))
+ assert_equal("\xFE", s)
+ end
+
+ def test_delete_prefix_bang_clear_coderange
s = S("\u{3053 3093}hello")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_prefix!("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_prefix_bang_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("bba", s.delete_prefix!(klass.new))
assert_equal("bba", s)
+ end
+ def test_delete_prefix_bang_frozen_error
s = S("ax").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!("a")}
@@ -2925,11 +3043,13 @@ CODE
assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!(o)}
end
- def test_delete_suffix
+ def test_delete_suffix_type_error
assert_raise(TypeError) { S('hello').delete_suffix(nil) }
assert_raise(TypeError) { S('hello').delete_suffix(1) }
assert_raise(TypeError) { S('hello').delete_suffix(/hel/) }
+ end
+ def test_delete_suffix
s = S("hello")
assert_equal("hel", s.delete_suffix('lo'))
assert_equal("hello", s)
@@ -2949,23 +3069,28 @@ CODE
s = S("hello")
assert_equal("hello", s.delete_suffix("\u{3061 306f}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_suffix_broken_encoding
s = S("\xe3\x81\x82")
assert_equal("\xe3\x81\x82", s.delete_suffix("\x82"))
assert_equal("\xe3\x81\x82", s)
+ end
- # clear coderange
+ def test_delete_suffix_clear_coderange
s = S("hello\u{3053 3093}")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_suffix("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_suffix_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("abb", s.delete_suffix(klass.new))
assert_equal("abba", s)
+ end
+ def test_delete_suffix_newline
# chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
# but delete_suffix does not
s = "foo\n"
@@ -2976,11 +3101,13 @@ CODE
assert_equal("foo\r", s.delete_suffix("\n"))
end
- def test_delete_suffix_bang
+ def test_delete_suffix_bang_type_error
assert_raise(TypeError) { S('hello').delete_suffix!(nil) }
assert_raise(TypeError) { S('hello').delete_suffix!(1) }
assert_raise(TypeError) { S('hello').delete_suffix!(/hel/) }
+ end
+ def test_delete_suffix_bang_frozen_error
s = S("hello").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!('lo')}
@@ -2991,7 +3118,9 @@ CODE
"x"
end
assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!(o)}
+ end
+ def test_delete_suffix_bang
s = S("hello")
assert_equal("hel", s.delete_suffix!('lo'))
assert_equal("hel", s)
@@ -3011,8 +3140,9 @@ CODE
s = S("hello")
assert_equal(nil, s.delete_suffix!("\u{3061 306f}"))
assert_equal("hello", s)
+ end
- # skip if argument is a broken string
+ def test_delete_suffix_bang_broken_encoding
s = S("\xe3\x81\x82")
assert_equal(nil, s.delete_suffix!("\x82"))
assert_equal("\xe3\x81\x82", s)
@@ -3020,18 +3150,22 @@ CODE
s = S("\x95\x5c").force_encoding("Shift_JIS")
assert_equal(nil, s.delete_suffix!("\x5c"))
assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+ end
- # clear coderange
+ def test_delete_suffix_bang_clear_coderange
s = S("hello\u{3053 3093}")
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.delete_suffix!("\u{3053 3093}"), :ascii_only?)
+ end
- # argument should be converted to String
+ def test_delete_suffix_bang_argument_conversion
klass = Class.new { def to_str; 'a'; end }
s = S("abba")
assert_equal("abb", s.delete_suffix!(klass.new))
assert_equal("abb", s)
+ end
+ def test_delete_suffix_bang_newline
# chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
# but delete_suffix does not
s = "foo\n"
@@ -3222,7 +3356,11 @@ CODE
assert_same(str, +str)
assert_not_same(str, -str)
- str = "bar".freeze
+ require 'objspace'
+
+ str = "test_uplus_minus_str".freeze
+ assert_includes ObjectSpace.dump(str), '"fstring":true'
+
assert_predicate(str, :frozen?)
assert_not_predicate(+str, :frozen?)
assert_predicate(-str, :frozen?)
@@ -3230,8 +3368,8 @@ CODE
assert_not_same(str, +str)
assert_same(str, -str)
- bar = %w(b a r).join('')
- assert_same(str, -bar, "uminus deduplicates [Feature #13077]")
+ bar = -%w(test uplus minus str).join('_')
+ assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}")
end
def test_uminus_frozen
@@ -3289,156 +3427,289 @@ CODE
end
def test_byteindex
- assert_equal(0, S("hello").byteindex(?h))
- assert_equal(1, S("hello").byteindex(S("ell")))
- assert_equal(2, S("hello").byteindex(/ll./))
+ assert_byteindex(0, S("hello"), ?h)
+ assert_byteindex(1, S("hello"), S("ell"))
+ assert_byteindex(2, S("hello"), /ll./)
- assert_equal(3, S("hello").byteindex(?l, 3))
- assert_equal(3, S("hello").byteindex(S("l"), 3))
- assert_equal(3, S("hello").byteindex(/l./, 3))
+ assert_byteindex(3, S("hello"), ?l, 3)
+ assert_byteindex(3, S("hello"), S("l"), 3)
+ assert_byteindex(3, S("hello"), /l./, 3)
- assert_nil(S("hello").byteindex(?z, 3))
- assert_nil(S("hello").byteindex(S("z"), 3))
- assert_nil(S("hello").byteindex(/z./, 3))
+ assert_byteindex(nil, S("hello"), ?z, 3)
+ assert_byteindex(nil, S("hello"), S("z"), 3)
+ assert_byteindex(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").byteindex(?z))
- assert_nil(S("hello").byteindex(S("z")))
- assert_nil(S("hello").byteindex(/z./))
+ assert_byteindex(nil, S("hello"), ?z)
+ assert_byteindex(nil, S("hello"), S("z"))
+ assert_byteindex(nil, S("hello"), /z./)
- assert_equal(0, S("").byteindex(S("")))
- assert_equal(0, S("").byteindex(//))
- assert_nil(S("").byteindex(S("hello")))
- assert_nil(S("").byteindex(/hello/))
- assert_equal(0, S("hello").byteindex(S("")))
- assert_equal(0, S("hello").byteindex(//))
+ assert_byteindex(0, S(""), S(""))
+ assert_byteindex(0, S(""), //)
+ assert_byteindex(nil, S(""), S("hello"))
+ assert_byteindex(nil, S(""), /hello/)
+ assert_byteindex(0, S("hello"), S(""))
+ assert_byteindex(0, S("hello"), //)
s = S("long") * 1000 << "x"
- assert_nil(s.byteindex(S("y")))
- assert_equal(4 * 1000, s.byteindex(S("x")))
+ assert_byteindex(nil, s, S("y"))
+ assert_byteindex(4 * 1000, s, S("x"))
s << "yx"
- assert_equal(4 * 1000, s.byteindex(S("x")))
- assert_equal(4 * 1000, s.byteindex(S("xyx")))
+ assert_byteindex(4 * 1000, s, S("x"))
+ assert_byteindex(4 * 1000, s, S("xyx"))
o = Object.new
def o.to_str; "bar"; end
- assert_equal(3, S("foobarbarbaz").byteindex(o))
+ assert_byteindex(3, S("foobarbarbaz"), o)
assert_raise(TypeError) { S("foo").byteindex(Object.new) }
- assert_nil(S("foo").byteindex(//, -100))
- assert_nil($~)
+ assert_byteindex(nil, S("foo"), //, -100)
+ assert_byteindex(nil, S("foo"), //, -4)
- assert_equal(2, S("abcdbce").byteindex(/b\Kc/))
+ assert_byteindex(2, S("abcdbce"), /b\Kc/)
- assert_equal(0, S("こんにちは").byteindex(?こ))
- assert_equal(3, S("こんにちは").byteindex(S("んにち")))
- assert_equal(6, S("こんにちは").byteindex(/にち./))
+ assert_byteindex(0, S("こんにちは"), ?こ)
+ assert_byteindex(3, S("こんにちは"), S("んにち"))
+ assert_byteindex(6, S("こんにちは"), /にち./)
- assert_equal(0, S("にんにちは").byteindex(?に, 0))
+ assert_byteindex(0, S("にんにちは"), ?に, 0)
assert_raise(IndexError) { S("にんにちは").byteindex(?に, 1) }
assert_raise(IndexError) { S("にんにちは").byteindex(?に, 5) }
- assert_equal(6, S("にんにちは").byteindex(?に, 6))
- assert_equal(6, S("にんにちは").byteindex(S("に"), 6))
- assert_equal(6, S("にんにちは").byteindex(/に./, 6))
+ assert_byteindex(6, S("にんにちは"), ?に, 6)
+ assert_byteindex(6, S("にんにちは"), S("に"), 6)
+ assert_byteindex(6, S("にんにちは"), /に./, 6)
assert_raise(IndexError) { S("にんにちは").byteindex(?に, 7) }
+
+ s = S("foobarbarbaz")
+ assert !1000.times.any? {s.byteindex("", 100_000_000)}
end
def test_byterindex
- assert_equal(3, S("hello").byterindex(?l))
- assert_equal(6, S("ell, hello").byterindex(S("ell")))
- assert_equal(7, S("ell, hello").byterindex(/ll./))
+ assert_byterindex(3, S("hello"), ?l)
+ assert_byterindex(6, S("ell, hello"), S("ell"))
+ assert_byterindex(7, S("ell, hello"), /ll./)
- assert_equal(3, S("hello,lo").byterindex(?l, 3))
- assert_equal(3, S("hello,lo").byterindex(S("l"), 3))
- assert_equal(3, S("hello,lo").byterindex(/l./, 3))
+ assert_byterindex(3, S("hello,lo"), ?l, 3)
+ assert_byterindex(3, S("hello,lo"), S("l"), 3)
+ assert_byterindex(3, S("hello,lo"), /l./, 3)
- assert_nil(S("hello").byterindex(?z, 3))
- assert_nil(S("hello").byterindex(S("z"), 3))
- assert_nil(S("hello").byterindex(/z./, 3))
+ assert_byterindex(nil, S("hello"), ?z, 3)
+ assert_byterindex(nil, S("hello"), S("z"), 3)
+ assert_byterindex(nil, S("hello"), /z./, 3)
- assert_nil(S("hello").byterindex(?z))
- assert_nil(S("hello").byterindex(S("z")))
- assert_nil(S("hello").byterindex(/z./))
+ assert_byterindex(nil, S("hello"), ?z)
+ assert_byterindex(nil, S("hello"), S("z"))
+ assert_byterindex(nil, S("hello"), /z./)
- assert_equal(5, S("hello").byterindex(S("")))
- assert_equal(5, S("hello").byterindex(S(""), 5))
- assert_equal(4, S("hello").byterindex(S(""), 4))
- assert_equal(0, S("hello").byterindex(S(""), 0))
+ assert_byterindex(5, S("hello"), S(""))
+ assert_byterindex(5, S("hello"), S(""), 5)
+ assert_byterindex(4, S("hello"), S(""), 4)
+ assert_byterindex(0, S("hello"), S(""), 0)
o = Object.new
def o.to_str; "bar"; end
- assert_equal(6, S("foobarbarbaz").byterindex(o))
+ assert_byterindex(6, S("foobarbarbaz"), o)
assert_raise(TypeError) { S("foo").byterindex(Object.new) }
- assert_nil(S("foo").byterindex(//, -100))
- assert_nil($~)
+ assert_byterindex(nil, S("foo"), //, -100)
- assert_equal(3, S("foo").byterindex(//))
- assert_equal([3, 3], $~.offset(0))
+ m = assert_byterindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_byterindex(3, S("foo"), //, 4)
- assert_equal(5, S("abcdbce").byterindex(/b\Kc/))
+ assert_byterindex(5, S("abcdbce"), /b\Kc/)
- assert_equal(6, S("こんにちは").byterindex(?に))
- assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは")))
- assert_equal(18, S("にちは、こんにちは").byterindex(/にち./))
+ assert_byterindex(6, S("こんにちは"), ?に)
+ assert_byterindex(18, S("にちは、こんにちは"), S("にちは"))
+ assert_byterindex(18, S("にちは、こんにちは"), /にち./)
assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 19) }
assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -2) }
- assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), 18))
- assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), -3))
+ assert_byterindex(18, S("にちは、こんにちは"), S("にちは"), 18)
+ assert_byterindex(18, S("にちは、こんにちは"), S("にちは"), -3)
assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 17) }
assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -4) }
assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 1) }
- assert_equal(0, S("にちは、こんにちは").byterindex(S("にちは"), 0))
+ assert_byterindex(0, S("にちは、こんにちは"), S("にちは"), 0)
- assert_equal(0, S("こんにちは").byterindex(S("こんにちは")))
- assert_nil(S("こんにち").byterindex(S("こんにちは")))
- assert_nil(S("こ").byterindex(S("こんにちは")))
- assert_nil(S("").byterindex(S("こんにちは")))
+ assert_byterindex(0, S("こんにちは"), S("こんにちは"))
+ assert_byterindex(nil, S("こんにち"), S("こんにちは"))
+ assert_byterindex(nil, S("こ"), S("こんにちは"))
+ assert_byterindex(nil, S(""), S("こんにちは"))
end
def test_bytesplice
- assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "xxx")
- assert_bytesplice_result("xxxhello", S("hello"), -5, 0, "xxx")
- assert_bytesplice_result("xxxhello", S("hello"), 0, 0, "xxx")
- assert_bytesplice_result("xxxello", S("hello"), 0, 1, "xxx")
- assert_bytesplice_result("xxx", S("hello"), 0, 5, "xxx")
- assert_bytesplice_result("xxx", S("hello"), 0, 6, "xxx")
-
- assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "xxx")
- assert_bytesplice_result("xxxhello", S("hello"), -5...-5, "xxx")
- assert_bytesplice_result("xxxhello", S("hello"), 0...0, "xxx")
- assert_bytesplice_result("xxxello", S("hello"), 0..0, "xxx")
- assert_bytesplice_result("xxxello", S("hello"), 0...1, "xxx")
- assert_bytesplice_result("xxxllo", S("hello"), 0..1, "xxx")
- assert_bytesplice_result("xxx", S("hello"), 0..-1, "xxx")
- assert_bytesplice_result("xxx", S("hello"), 0...5, "xxx")
- assert_bytesplice_result("xxx", S("hello"), 0...6, "xxx")
-
- assert_bytesplice_raise(TypeError, S("hello"), 0, "xxx")
-
- assert_bytesplice_raise(IndexError, S("こんにちは"), -16, 0, "xxx")
- assert_bytesplice_result("xxxこんにちは", S("こんにちは"), -15, 0, "xxx")
- assert_bytesplice_result("xxxこんにちは", S("こんにちは"), 0, 0, "xxx")
- assert_bytesplice_raise(IndexError, S("こんにちは"), 1, 0, "xxx")
- assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 1, "xxx")
- assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 2, "xxx")
- assert_bytesplice_result("xxxんにちは", S("こんにちは"), 0, 3, "xxx")
- assert_bytesplice_result("こんにちはxxx", S("こんにちは"), 15, 0, "xxx")
+ assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "bye")
+ assert_bytesplice_result("byehello", S("hello"), -5, 0, "bye")
+ assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0, 1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0, 5, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0, 6, "bye")
+
+ assert_bytesplice_raise(IndexError, S("hello"), -5, 0, "bye", -4, 0)
+ assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye", 0, 3)
+ assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 3)
+ assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 2)
+ assert_bytesplice_result("ehello", S("hello"), 0, 0, "bye", 2, 1)
+ assert_bytesplice_result("hello", S("hello"), 0, 0, "bye", 3, 0)
+ assert_bytesplice_result("hello", s = S("hello"), 0, 5, s, 0, 5)
+ assert_bytesplice_result("elloo", s = S("hello"), 0, 4, s, 1, 4)
+ assert_bytesplice_result("llolo", s = S("hello"), 0, 3, s, 2, 3)
+ assert_bytesplice_result("lollo", s = S("hello"), 0, 2, s, 3, 2)
+ assert_bytesplice_result("oello", s = S("hello"), 0, 1, s, 4, 1)
+ assert_bytesplice_result("hhell", s = S("hello"), 1, 4, s, 0, 4)
+ assert_bytesplice_result("hehel", s = S("hello"), 2, 3, s, 0, 3)
+ assert_bytesplice_result("helhe", s = S("hello"), 3, 2, s, 0, 2)
+ assert_bytesplice_result("hellh", s = S("hello"), 4, 1, s, 0, 1)
+
+ assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "bye")
+ assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye")
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0..0, "bye")
+ assert_bytesplice_result("byeello", S("hello"), 0...1, "bye")
+ assert_bytesplice_result("byello", S("hello"), 0..1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0..-1, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0...5, "bye")
+ assert_bytesplice_result("bye", S("hello"), 0...6, "bye")
+ assert_bytesplice_result("llolo", s = S("hello"), 0..2, s, 2..4)
+
+ assert_bytesplice_raise(RangeError, S("hello"), -5...-5, "bye", -6...-6)
+ assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye", 0..-1)
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..-1)
+ assert_bytesplice_result("bhello", S("hello"), 0...0, "bye", 0..0)
+ assert_bytesplice_result("byhello", S("hello"), 0...0, "bye", 0..1)
+ assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..2)
+ assert_bytesplice_result("yehello", S("hello"), 0...0, "bye", 1..2)
+
+ assert_bytesplice_raise(TypeError, S("hello"), 0, "bye")
+
+ assert_bytesplice_raise(IndexError, S("こんにちは"), -16, 0, "bye")
+ assert_bytesplice_result("byeこんにちは", S("こんにちは"), -15, 0, "bye")
+ assert_bytesplice_result("byeこんにちは", S("こんにちは"), 0, 0, "bye")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 1, 0, "bye")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 1, "bye")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 2, "bye")
+ assert_bytesplice_result("byeんにちは", S("こんにちは"), 0, 3, "bye")
+ assert_bytesplice_result("こんにちはbye", S("こんにちは"), 15, 0, "bye")
+
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 0, "さようなら", -16, 0)
+ assert_bytesplice_result("こんにちはさようなら", S("こんにちは"), 15, 0, "さようなら", 0, 15)
+ assert_bytesplice_result("さようなら", S("こんにちは"), 0, 15, "さようなら", 0, 15)
+ assert_bytesplice_result("さんにちは", S("こんにちは"), 0, 3, "さようなら", 0, 3)
+ assert_bytesplice_result("さようちは", S("こんにちは"), 0, 9, "さようなら", 0, 9)
+ assert_bytesplice_result("ようなちは", S("こんにちは"), 0, 9, "さようなら", 3, 9)
+ assert_bytesplice_result("ようちは", S("こんにちは"), 0, 9, "さようなら", 3, 6)
+ assert_bytesplice_result("ようならちは", S("こんにちは"), 0, 9, "さようなら", 3, 12)
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 15, "さようなら", -16, 0)
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 15, "さようなら", 1, 0)
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 15, "さようなら", 2, 0)
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 15, "さようなら", 0, 1)
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 15, "さようなら", 0, 2)
+ assert_bytesplice_result("にちはちは", s = S("こんにちは"), 0, 9, s, 6, 9)
assert_bytesplice_result("", S(""), 0, 0, "")
assert_bytesplice_result("xxx", S(""), 0, 0, "xxx")
+
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0)
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0..-1)
+ assert_bytesplice_raise(ArgumentError, S("hello"), 0..-1, "bye", 0, 3)
+ end
+
+ def test_chilled_string
+ chilled_string = eval('"chilled"')
+
+ # Chilled strings pretend to be frozen
+ assert_predicate chilled_string, :frozen?
+
+ assert_not_predicate chilled_string.dup, :frozen?
+ assert_predicate chilled_string.clone, :frozen?
+
+ # @+ treat the original string as frozen
+ assert_not_predicate(+chilled_string, :frozen?)
+ assert_not_same chilled_string, +chilled_string
+
+ # @- the original string as mutable
+ assert_predicate(-chilled_string, :frozen?)
+ assert_not_same chilled_string, -chilled_string
+ end
+
+ def test_chilled_string_setivar
+ deprecated = Warning[:deprecated]
+ Warning[:deprecated] = false
+
+ String.class_eval <<~RUBY, __FILE__, __LINE__ + 1
+ def setivar!
+ @ivar = 42
+ @ivar
+ end
+ RUBY
+ chilled_string = eval('"chilled"')
+ begin
+ assert_equal 42, chilled_string.setivar!
+ ensure
+ String.undef_method(:setivar!)
+ end
+ ensure
+ Warning[:deprecated] = deprecated
+ end
+
+ def test_chilled_string_substring
+ deprecated = Warning[:deprecated]
+ Warning[:deprecated] = false
+ chilled_string = eval('"a chilled string."')
+ substring = chilled_string[0..-1]
+ assert_equal("a chilled string.", substring)
+ chilled_string[0..-1] = "This string is defrosted."
+ assert_equal("a chilled string.", substring)
+ ensure
+ Warning[:deprecated] = deprecated
end
private
def assert_bytesplice_result(expected, s, *args)
- assert_equal(args.last, s.send(:bytesplice, *args))
+ assert_equal(expected, s.send(:bytesplice, *args))
assert_equal(expected, s)
end
def assert_bytesplice_raise(e, s, *args)
assert_raise(e) { s.send(:bytesplice, *args) }
end
+
+ def assert_index_like(method, expected, string, match, *rest)
+ message = "#{method} with string does not affect $~"
+ /.*/ =~ message
+ md_before = $~
+ assert_equal(expected, string.__send__(method, match, *rest))
+ md_after = $~
+ case match
+ when Regexp
+ if expected
+ assert_not_nil(md_after)
+ assert_not_same(md_before, md_after)
+ else
+ assert_nil(md_after)
+ end
+ else
+ assert_same(md_before, md_after)
+ end
+ md_after
+ end
+
+ def assert_index(expected, string, match, *rest)
+ assert_index_like(:index, expected, string, match, *rest)
+ end
+
+ def assert_rindex(expected, string, match, *rest)
+ assert_index_like(:rindex, expected, string, match, *rest)
+ end
+
+ def assert_byteindex(expected, string, match, *rest)
+ assert_index_like(:byteindex, expected, string, match, *rest)
+ end
+
+ def assert_byterindex(expected, string, match, *rest)
+ assert_index_like(:byterindex, expected, string, match, *rest)
+ end
end
class TestString2 < TestString
diff --git a/test/ruby/test_string_memory.rb b/test/ruby/test_string_memory.rb
new file mode 100644
index 0000000000..3b4694f36f
--- /dev/null
+++ b/test/ruby/test_string_memory.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'objspace'
+
+class TestStringMemory < Test::Unit::TestCase
+ def capture_allocations(klass)
+ allocations = []
+
+ GC.start
+ GC.disable
+ generation = GC.count
+
+ ObjectSpace.trace_object_allocations do
+ yield
+
+ ObjectSpace.each_object(klass) do |instance|
+ allocations << instance if ObjectSpace.allocation_generation(instance) == generation
+ end
+ end
+
+ return allocations
+ ensure
+ GC.enable
+ end
+
+ def test_byteslice_prefix
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(0, 50_000)
+ end
+
+ assert_equal 1, allocations.size
+ end
+
+ def test_byteslice_postfix
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(50_000, 100_000)
+ end
+
+ assert_equal 1, allocations.size
+ end
+
+ def test_byteslice_postfix_twice
+ string = ("a" * 100_000).freeze
+
+ allocations = capture_allocations(String) do
+ string.byteslice(50_000, 100_000).byteslice(25_000, 50_000)
+ end
+
+ assert_equal 2, allocations.size
+ end
+end
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 78a81c5200..3d727adf04 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -41,6 +41,14 @@ module TestStruct
end
end
+ def test_larger_than_largest_pool
+ count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1
+ list = Array(0..count)
+ klass = @Struct.new(*list.map { |i| :"a_#{i}"})
+ struct = klass.new(*list)
+ assert_equal 0, struct.a_0
+ end
+
def test_small_structs
names = [:a, :b, :c, :d]
1.upto(4) {|n|
@@ -526,6 +534,20 @@ module TestStruct
assert_equal [[:req, :_]], klass.instance_method(:c=).parameters
end
+ def test_named_structs_are_not_rooted
+ # [Bug #20311]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Struct.new("A")
+ Struct.send(:remove_const, :A)
+ end
+
+ 1_000.times(&code)
+ PREP
+ 50_000.times(&code)
+ CODE
+ end
+
class TopStruct < Test::Unit::TestCase
include TestStruct
diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb
index 6a575b88c5..ce78e66c52 100644
--- a/test/ruby/test_super.rb
+++ b/test/ruby/test_super.rb
@@ -558,6 +558,18 @@ class TestSuper < Test::Unit::TestCase
end
end
+ def test_zsuper_kw_splat_not_mutable
+ extend(Module.new{def a(**k) k[:a] = 1 end})
+ extend(Module.new do
+ def a(**k)
+ before = k.dup
+ super
+ [before, k]
+ end
+ end)
+ assert_equal(*a)
+ end
+
def test_from_eval
bug10263 = '[ruby-core:65122] [Bug #10263a]'
a = Class.new do
diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb
index f7f17b8d67..41b71097d1 100644
--- a/test/ruby/test_symbol.rb
+++ b/test/ruby/test_symbol.rb
@@ -36,6 +36,19 @@ class TestSymbol < Test::Unit::TestCase
assert_eval_inspected(:"@@1", false)
assert_eval_inspected(:"@", false)
assert_eval_inspected(:"@@", false)
+ assert_eval_inspected(:"[]=")
+ assert_eval_inspected(:"[][]", false)
+ assert_eval_inspected(:"[][]=", false)
+ assert_eval_inspected(:"@=", false)
+ assert_eval_inspected(:"@@=", false)
+ assert_eval_inspected(:"@x=", false)
+ assert_eval_inspected(:"@@x=", false)
+ assert_eval_inspected(:"$$=", false)
+ assert_eval_inspected(:"$==", false)
+ assert_eval_inspected(:"$x=", false)
+ assert_eval_inspected(:"$$$=", false)
+ assert_eval_inspected(:"foo?=", false)
+ assert_eval_inspected(:"foo!=", false)
end
def assert_inspect_evaled(n)
@@ -105,6 +118,15 @@ class TestSymbol < Test::Unit::TestCase
end
end
+ def test_inspect_under_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ omit "very flaky on many platforms, more so with YJIT enabled" if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+ omit "very flaky on many platforms, more so with RJIT enabled" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
+ EnvUtil.under_gc_compact_stress do
+ assert_inspect_evaled(':testing')
+ end
+ end
+
def test_name
assert_equal("foo", :foo.name)
assert_same(:foo.name, :foo.name)
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index b1fa3e5227..44162f06cb 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -13,8 +13,7 @@ class TestSyntax < Test::Unit::TestCase
def assert_syntax_files(test)
srcdir = File.expand_path("../../..", __FILE__)
srcdir = File.join(srcdir, test)
- assert_separately(%W[--disable-gem - #{srcdir}],
- __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY)
+ assert_separately(%W[- #{srcdir}], __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY)
dir = ARGV.shift
for script in Dir["#{dir}/**/*.rb"].sort
assert_valid_syntax(IO::read(script), script)
@@ -66,100 +65,134 @@ class TestSyntax < Test::Unit::TestCase
f&.close!
end
+ def test_script_lines_encoding
+ require 'tmpdir'
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n")
+ assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name",
+ %w"EUC-JP", /-K is specified/, chdir: dir)
+ end
+ end
+
def test_anonymous_block_forwarding
assert_syntax_error("def b; c(&); end", /no anonymous block parameter/)
+ assert_syntax_error("def b(&) ->(&) {c(&)} end", /anonymous block parameter is also used/)
+ assert_valid_syntax("def b(&) ->() {c(&)} end")
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
begin;
- def b(&); c(&) end
- def c(&); yield 1 end
- a = nil
- b{|c| a = c}
- assert_equal(1, a)
-
- def inner
- yield
- end
+ def b(&); c(&) end
+ def c(&); yield 1 end
+ a = nil
+ b{|c| a = c}
+ assert_equal(1, a)
+
+ def inner
+ yield
+ end
- def block_only(&)
- inner(&)
- end
- assert_equal(1, block_only{1})
+ def block_only(&)
+ inner(&)
+ end
+ assert_equal(1, block_only{1})
- def pos(arg1, &)
- inner(&)
- end
- assert_equal(2, pos(nil){2})
+ def pos(arg1, &)
+ inner(&)
+ end
+ assert_equal(2, pos(nil){2})
- def pos_kwrest(arg1, **kw, &)
- inner(&)
- end
- assert_equal(3, pos_kwrest(nil){3})
+ def pos_kwrest(arg1, **kw, &)
+ inner(&)
+ end
+ assert_equal(3, pos_kwrest(nil){3})
- def no_kw(arg1, **nil, &)
- inner(&)
- end
- assert_equal(4, no_kw(nil){4})
+ def no_kw(arg1, **nil, &)
+ inner(&)
+ end
+ assert_equal(4, no_kw(nil){4})
- def rest_kw(*a, kwarg: 1, &)
- inner(&)
- end
- assert_equal(5, rest_kw{5})
+ def rest_kw(*a, kwarg: 1, &)
+ inner(&)
+ end
+ assert_equal(5, rest_kw{5})
- def kw(kwarg:1, &)
- inner(&)
- end
- assert_equal(6, kw{6})
+ def kw(kwarg:1, &)
+ inner(&)
+ end
+ assert_equal(6, kw{6})
- def pos_kw_kwrest(arg1, kwarg:1, **kw, &)
- inner(&)
- end
- assert_equal(7, pos_kw_kwrest(nil){7})
+ def pos_kw_kwrest(arg1, kwarg:1, **kw, &)
+ inner(&)
+ end
+ assert_equal(7, pos_kw_kwrest(nil){7})
- def pos_rkw(arg1, kwarg1:, &)
- inner(&)
- end
- assert_equal(8, pos_rkw(nil, kwarg1: nil){8})
+ def pos_rkw(arg1, kwarg1:, &)
+ inner(&)
+ end
+ assert_equal(8, pos_rkw(nil, kwarg1: nil){8})
- def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &)
- inner(&)
- end
- assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9})
+ def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &)
+ inner(&)
+ end
+ assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9})
- def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &)
- inner(&)
- end
- assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10})
+ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &)
+ inner(&)
+ end
+ assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10})
end;
end
def test_anonymous_rest_forwarding
assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/)
assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/)
+ assert_syntax_error("def b(*) ->(*) {c(*)} end", /anonymous rest parameter is also used/)
+ assert_syntax_error("def b(a, *) ->(*) {c(1, *)} end", /anonymous rest parameter is also used/)
+ assert_syntax_error("def b(*) ->(a, *) {c(*)} end", /anonymous rest parameter is also used/)
+ assert_valid_syntax("def b(*) ->() {c(*)} end")
+ assert_valid_syntax("def b(a, *) ->() {c(1, *)} end")
+ assert_valid_syntax("def b(*) ->(a) {c(*)} end")
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
begin;
- def b(*); c(*) end
- def c(*a); a end
- def d(*); b(*, *) end
- assert_equal([1, 2], b(1, 2))
- assert_equal([1, 2, 1, 2], d(1, 2))
+ def b(*); c(*) end
+ def c(*a); a end
+ def d(*); b(*, *) end
+ assert_equal([1, 2], b(1, 2))
+ assert_equal([1, 2, 1, 2], d(1, 2))
end;
end
def test_anonymous_keyword_rest_forwarding
assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/)
assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def b(**) ->(**) {c(**)} end", /anonymous keyword rest parameter is also used/)
+ assert_syntax_error("def b(k:, **) ->(**) {c(k: 1, **)} end", /anonymous keyword rest parameter is also used/)
+ assert_syntax_error("def b(**) ->(k:, **) {c(**)} end", /anonymous keyword rest parameter is also used/)
+ assert_valid_syntax("def b(**) ->() {c(**)} end")
+ assert_valid_syntax("def b(k:, **) ->() {c(k: 1, **)} end")
+ assert_valid_syntax("def b(**) ->(k:) {c(**)} end")
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
begin;
- def b(**); c(**) end
- def c(**kw); kw end
- def d(**); b(k: 1, **) end
- def e(**); b(**, k: 1) end
- assert_equal({a: 1, k: 3}, b(a: 1, k: 3))
- assert_equal({a: 1, k: 3}, d(a: 1, k: 3))
- assert_equal({a: 1, k: 1}, e(a: 1, k: 3))
+ def b(**); c(**) end
+ def c(**kw); kw end
+ def d(**); b(k: 1, **) end
+ def e(**); b(**, k: 1) end
+ def f(a: nil, **); b(**) end
+ assert_equal({a: 1, k: 3}, b(a: 1, k: 3))
+ assert_equal({a: 1, k: 3}, d(a: 1, k: 3))
+ assert_equal({a: 1, k: 1}, e(a: 1, k: 3))
+ assert_equal({k: 3}, f(a: 1, k: 3))
end;
end
+ def test_argument_forwarding_with_anon_rest_kwrest_and_block
+ assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./)
+ assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/)
+ end
+
def test_newline_in_block_parameters
bug = '[ruby-dev:45292]'
["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|
@@ -201,6 +234,7 @@ class TestSyntax < Test::Unit::TestCase
def test_array_kwsplat_hash
kw = {}
h = {a: 1}
+ a = []
assert_equal([], [**{}])
assert_equal([], [**kw])
assert_equal([h], [**h])
@@ -215,6 +249,20 @@ class TestSyntax < Test::Unit::TestCase
assert_equal([1, kw], [1, kw])
assert_equal([1, h], [1, h])
+ assert_equal([], [*a, **{}])
+ assert_equal([], [*a, **kw])
+ assert_equal([h], [*a, **h])
+ assert_equal([{}], [*a, {}])
+ assert_equal([kw], [*a, kw])
+ assert_equal([h], [*a, h])
+
+ assert_equal([1], [1, *a, **{}])
+ assert_equal([1], [1, *a, **kw])
+ assert_equal([1, h], [1, *a, **h])
+ assert_equal([1, {}], [1, *a, {}])
+ assert_equal([1, kw], [1, *a, kw])
+ assert_equal([1, h], [1, *a, h])
+
assert_equal([], [**kw, **kw])
assert_equal([], [**kw, **{}, **kw])
assert_equal([1], [1, **kw, **{}, **kw])
@@ -317,6 +365,12 @@ class TestSyntax < Test::Unit::TestCase
assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")}
assert_equal(4, r)
assert_equal([1, 2, 3, 4], a)
+ a.clear
+ r = nil
+ _z = {}
+ assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), **_z, k: a.add(2))")}
+ assert_equal(2, r)
+ assert_equal([1, 2], a)
end
def test_keyword_empty_splat
@@ -343,6 +397,7 @@ class TestSyntax < Test::Unit::TestCase
assert_syntax_error("def foo(var: var) var end", message)
assert_syntax_error("def foo(var: bar(var)) var end", message)
assert_syntax_error("def foo(var: bar {var}) var end", message)
+ assert_syntax_error("def foo(var: (1 in ^var)); end", message)
o = Object.new
assert_warn("") do
@@ -408,6 +463,7 @@ class TestSyntax < Test::Unit::TestCase
assert_syntax_error("def foo(var = bar {var}) var end", message)
assert_syntax_error("def foo(var = (def bar;end; var)) var end", message)
assert_syntax_error("def foo(var = (def self.bar;end; var)) var end", message)
+ assert_syntax_error("def foo(var = (1 in ^var)); end", message)
o = Object.new
assert_warn("") do
@@ -451,7 +507,7 @@ class TestSyntax < Test::Unit::TestCase
def test_warn_balanced
warning = <<WARN
-test:1: warning: `%s' after local variable or literal is interpreted as binary operator
+test:1: warning: '%s' after local variable or literal is interpreted as binary operator
test:1: warning: even though it seems like %s
WARN
[
@@ -618,6 +674,8 @@ WARN
assert_equal(42, obj.foo(42))
assert_equal(42, obj.foo(2, _: 0))
assert_equal(2, obj.foo(x: 2, _: 0))
+ ensure
+ self.class.remove_method(:foo)
end
def test_duplicated_opt_kw
@@ -657,7 +715,7 @@ WARN
end
def test_duplicated_when
- w = 'warning: duplicated `when\' clause with line 3 is ignored'
+ w = 'warning: duplicated \'when\' clause with line 3 is ignored'
assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m) {
eval %q{
case 1
@@ -677,10 +735,28 @@ WARN
end
}
}
+ assert_warning(/3: #{w}/m) {
+ eval %q{
+ case 1
+ when __LINE__, __LINE__
+ when 3, 3
+ when 3, 3
+ end
+ }
+ }
+ assert_warning(/3: #{w}/m) {
+ eval %q{
+ case 1
+ when __FILE__, __FILE__
+ when "filename", "filename"
+ when "filename", "filename"
+ end
+ }, binding, "filename"
+ }
end
def test_duplicated_when_check_option
- w = /duplicated `when\' clause with line 3 is ignored/
+ w = /duplicated \'when\' clause with line 3 is ignored/
assert_in_out_err(%[-wc], "#{<<~"begin;"}\n#{<<~'end;'}", ["Syntax OK"], w)
begin;
case 1
@@ -959,6 +1035,14 @@ eom
assert_not_match(/end-of-input/, e.message)
end
+ def test_invalid_regexp
+ bug20295 = '[ruby-core:116913] [Bug #20295]'
+
+ assert_syntax_error("/[/=~s", /premature end of char-class/, bug20295)
+ assert_syntax_error("/(?<>)/=~s", /group name is empty/, bug20295)
+ assert_syntax_error("/(?<a>[)/=~s", /premature end of char-class/, bug20295)
+ end
+
def test_lineno_operation_brace_block
expected = __LINE__ + 1
actual = caller_lineno\
@@ -971,7 +1055,7 @@ eom
["p ", ""], # no-pop
["", "p Foo::Bar"], # pop
].each do |p1, p2|
- src = <<-EOM.gsub(/^\s*\n/, '')
+ src = <<~EOM
class Foo
#{"Bar = " + preset if preset}
end
@@ -1003,7 +1087,7 @@ eom
["p ", ""], # no-pop
["", "p ::Bar"], # pop
].each do |p1, p2|
- src = <<-EOM.gsub(/^\s*\n/, '')
+ src = <<~EOM
#{"Bar = " + preset if preset}
class Foo
#{p1}::Bar #{op}= 42
@@ -1170,12 +1254,18 @@ eom
assert_warn(/string literal in condition/) do
eval('1 if ""')
end
+ assert_warning(/string literal in condition/) do
+ eval('1 if __FILE__')
+ end
assert_warn(/regex literal in condition/) do
eval('1 if //')
end
assert_warning(/literal in condition/) do
eval('1 if 1')
end
+ assert_warning(/literal in condition/) do
+ eval('1 if __LINE__')
+ end
assert_warning(/symbol literal in condition/) do
eval('1 if :foo')
end
@@ -1249,7 +1339,7 @@ eom
end
def test_parenthesised_statement_argument
- assert_syntax_error("foo(bar rescue nil)", /unexpected `rescue' modifier/)
+ assert_syntax_error("foo(bar rescue nil)", /unexpected 'rescue' modifier/)
assert_valid_syntax("foo (bar rescue nil)")
end
@@ -1299,6 +1389,25 @@ eom
assert_valid_syntax 'p :foo, {proc do end => proc do end, b: proc do end}', bug13073
end
+ def test_invalid_encoding_symbol
+ assert_syntax_error('{"\xC3": 1}', "invalid symbol")
+ end
+
+ def test_invalid_symbol_in_hash_memory_leak
+ assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true)
+ str = '{"\xC3": 1}'.force_encoding("UTF-8")
+ code = proc do
+ eval(str)
+ raise "unreachable"
+ rescue SyntaxError
+ end
+
+ 1_000.times(&code)
+ begin;
+ 1_000_000.times(&code)
+ end;
+ end
+
def test_do_after_local_variable
obj = Object.new
def obj.m; yield; end
@@ -1347,9 +1456,10 @@ eom
"#{return}"
raise((return; "should not raise"))
begin raise; ensure return; end; self
- begin raise; ensure return; end and self
nil&defined?0--begin e=no_method_error(); return; 0;end
return puts('ignored') #=> ignored
+ BEGIN {return}
+ END {return if false}
end;
.split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
failed = proc do |n, s|
@@ -1379,6 +1489,54 @@ eom
end
end
+ def test_eval_return_toplevel
+ feature4840 = '[ruby-core:36785] [Feature #4840]'
+ line = __LINE__+2
+ code = "#{<<~"begin;"}#{<<~'end;'}"
+ begin;
+ eval "return"; raise
+ begin eval "return"; rescue SystemExit; exit false; end
+ begin eval "return"; ensure puts "ensured"; end #=> ensured
+ begin ensure eval "return"; end
+ begin raise; ensure; eval "return"; end
+ begin raise; rescue; eval "return"; end
+ eval "return false"; raise
+ eval "return 1"; raise
+ "#{eval "return"}"
+ raise((eval "return"; "should not raise"))
+ begin raise; ensure eval "return"; end; self
+ begin raise; ensure eval "return"; end and self
+ eval "return puts('ignored')" #=> ignored
+ BEGIN {eval "return"}
+ end;
+ .split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
+ failed = proc do |n, s|
+ RubyVM::InstructionSequence.compile(s, __FILE__, nil, n).disasm
+ end
+ Tempfile.create(%w"test_return_ .rb") do |lib|
+ lib.close
+ args = %W[-W0 -r#{lib.path}]
+ all_assertions_foreach(feature4840, *[:main, :lib].product([:class, :top], code)) do |main, klass, (n, s, *ex)|
+ if klass == :class
+ s = "class X; #{s}; end"
+ if main == :main
+ assert_in_out_err(%[-W0], s, ex, /return/, proc {failed[n, s]}, success: false)
+ else
+ File.write(lib, s)
+ assert_in_out_err(args, "", ex, /return/, proc {failed[n, s]}, success: false)
+ end
+ else
+ if main == :main
+ assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true)
+ else
+ File.write(lib, s)
+ assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true)
+ end
+ end
+ end
+ end
+ end
+
def test_return_toplevel_with_argument
assert_warn(/argument of top-level return is ignored/) {eval("return 1")}
end
@@ -1387,6 +1545,20 @@ eom
assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/)
end
+ def test_return_in_END
+ assert_normal_exit('END {return}')
+ end
+
+ def test_return_in_BEGIN_in_eval
+ # `BEGIN` in `eval` is allowed, even inside a method, and `return`
+ # from that block exits from that method without `LocalJumpError`.
+ obj = Object.new
+ def obj.ok
+ eval("BEGIN {return :ok}")
+ end
+ assert_equal :ok, assert_nothing_raised(LocalJumpError) {obj.ok}
+ end
+
def test_syntax_error_in_rescue
bug12613 = '[ruby-core:76531] [Bug #12613]'
assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613)
@@ -1606,6 +1778,29 @@ eom
def test_command_with_cmd_brace_block
assert_valid_syntax('obj.foo (1) {}')
assert_valid_syntax('obj::foo (1) {}')
+ assert_valid_syntax('bar {}')
+ assert_valid_syntax('Bar {}')
+ assert_valid_syntax('bar() {}')
+ assert_valid_syntax('Bar() {}')
+ assert_valid_syntax('Foo::bar {}')
+ assert_valid_syntax('Foo::Bar {}')
+ assert_valid_syntax('Foo::bar() {}')
+ assert_valid_syntax('Foo::Bar() {}')
+ end
+
+ def test_command_newline_in_tlparen_args
+ assert_valid_syntax("p (1\n2\n),(3),(4)")
+ assert_valid_syntax("p (\n),(),()")
+ assert_valid_syntax("a.b (1\n2\n),(3),(4)")
+ assert_valid_syntax("a.b (\n),(),()")
+ end
+
+ def test_command_semicolon_in_tlparen_at_the_first_arg
+ bug19281 = '[ruby-core:111499] [Bug #19281]'
+ assert_valid_syntax('p (1;2),(3),(4)', bug19281)
+ assert_valid_syntax('p (;),(),()', bug19281)
+ assert_valid_syntax('a.b (1;2),(3),(4)', bug19281)
+ assert_valid_syntax('a.b (;),(),()', bug19281)
end
def test_numbered_parameter
@@ -1636,7 +1831,7 @@ eom
assert_syntax_error('def x(_4) end', /_4 is reserved for numbered parameter/)
assert_syntax_error('def _5; end', /_5 is reserved for numbered parameter/)
assert_syntax_error('def self._6; end', /_6 is reserved for numbered parameter/)
- assert_raise_with_message(NameError, /undefined local variable or method `_1'/) {
+ assert_raise_with_message(NameError, /undefined local variable or method '_1'/) {
eval('_1')
}
['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c|
@@ -1651,6 +1846,61 @@ eom
assert_raise(NameError) {eval("_1")},
]
}
+
+ assert_valid_syntax("proc {def foo(_);end;_1}")
+ assert_valid_syntax("p { [_1 **2] }")
+ assert_valid_syntax("proc {_1;def foo();end;_1}")
+ end
+
+ def test_it
+ assert_valid_syntax('proc {it}')
+ assert_syntax_error('[1,2].then {it+_2}', /'it' is already used/)
+ assert_syntax_error('[1,2].then {_2+it}', /numbered parameter is already used/)
+ assert_equal([1, 2], eval('[1,2].then {it}'))
+ assert_syntax_error('[1,2].then {"#{it}#{_2}"}', /'it' is already used/)
+ assert_syntax_error('[1,2].then {"#{_2}#{it}"}', /numbered parameter is already used/)
+ assert_syntax_error('->{it+_2}.call(1,2)', /'it' is already used/)
+ assert_syntax_error('->{_2+it}.call(1,2)', /numbered parameter is already used/)
+ assert_equal(4, eval('->(a=->{it}){a}.call.call(4)'))
+ assert_equal(5, eval('-> a: ->{it} {a}.call.call(5)'))
+ assert_syntax_error('proc {|| it}', /ordinary parameter is defined/)
+ assert_syntax_error('proc {|;a| it}', /ordinary parameter is defined/)
+ assert_syntax_error("proc {|\n| it}", /ordinary parameter is defined/)
+ assert_syntax_error('proc {|x| it}', /ordinary parameter is defined/)
+ assert_equal([1, 2], eval('1.then {[it, 2.then {_1}]}'))
+ assert_equal([2, 1], eval('1.then {[2.then {_1}, it]}'))
+ assert_syntax_error('->(){it}', /ordinary parameter is defined/)
+ assert_syntax_error('->(x){it}', /ordinary parameter is defined/)
+ assert_syntax_error('->x{it}', /ordinary parameter is defined/)
+ assert_syntax_error('->x:_1{}', /ordinary parameter is defined/)
+ assert_syntax_error('->x=it{}', /ordinary parameter is defined/)
+ assert_valid_syntax('-> {it; -> {_2}}')
+ assert_valid_syntax('-> {-> {it}; _2}')
+ assert_equal([1, nil], eval('proc {that=it; it=nil; [that, it]}.call(1)'))
+ assert_equal(1, eval('proc {it = 1}.call'))
+ assert_warning(/1: warning: assigned but unused variable - it/) {
+ assert_equal(2, eval('a=Object.new; def a.foo; it = 2; end; a.foo'))
+ }
+ assert_equal(3, eval('proc {|it| it}.call(3)'))
+ assert_equal(4, eval('a=Object.new; def a.foo(it); it; end; a.foo(4)'))
+ assert_equal(5, eval('a=Object.new; def a.it; 5; end; a.it'))
+ assert_equal(6, eval('a=Class.new; a.class_eval{ def it; 6; end }; a.new.it'))
+ assert_raise_with_message(NameError, /undefined local variable or method 'it'/) do
+ eval('it')
+ end
+ ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c|
+ assert_valid_syntax("->{#{c};->{it};end;it}\n")
+ assert_valid_syntax("->{it;#{c};->{it};end}\n")
+ end
+ 1.times do
+ [
+ assert_equal(0, it),
+ assert_equal([:a], eval('[:a].map{it}')),
+ assert_raise(NameError) {eval('it')},
+ ]
+ end
+ assert_valid_syntax('proc {def foo(_);end;it}')
+ assert_syntax_error('p { [it **2] }', /unexpected \*\*arg/)
end
def test_value_expr_in_condition
@@ -1661,6 +1911,11 @@ eom
assert_valid_syntax("tap {a = (break unless true)}")
end
+ def test_value_expr_in_singleton
+ mesg = /void value expression/
+ assert_syntax_error("class << (return); end", mesg)
+ end
+
def test_tautological_condition
assert_valid_syntax("def f() return if false and invalid; nil end")
assert_valid_syntax("def f() return unless true or invalid; nil end")
@@ -1698,6 +1953,8 @@ eom
assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/)
assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/)
assert_syntax_error('def foo(...) defined?(...); end', /unexpected/)
+ assert_syntax_error('def foo(*rest, ...) end', '... after rest argument')
+ assert_syntax_error('def foo(*, ...) end', '... after rest argument')
obj1 = Object.new
def obj1.bar(*args, **kws, &block)
@@ -1898,6 +2155,28 @@ eom
assert_equal 0...1, exp.call(a: 0)
end
+ def test_argument_forwarding_with_super
+ assert_valid_syntax('def foo(...) super {}; end')
+ assert_valid_syntax('def foo(...) super() {}; end')
+ assert_syntax_error('def foo(...) super(...) {}; end', /both block arg and actual block/)
+ assert_syntax_error('def foo(...) super(1, ...) {}; end', /both block arg and actual block/)
+ end
+
+ def test_class_module_Object_ancestors
+ assert_separately([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ class Bug18832; end
+ RUBY
+ assert_separately([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ module Bug18832; end
+ RUBY
+ end
+
def test_cdhash
assert_separately([], <<-RUBY)
n = case 1 when 2r then false else true end
diff --git a/test/ruby/test_system.rb b/test/ruby/test_system.rb
index 31c9cd7654..3fcdaa6aad 100644
--- a/test/ruby/test_system.rb
+++ b/test/ruby/test_system.rb
@@ -146,6 +146,19 @@ class TestSystem < Test::Unit::TestCase
end
end
+ def test_system_closed
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ios = []
+ ObjectSpace.each_object(IO) {|io| ios << io}
+ `echo`
+ ObjectSpace.each_object(IO) do |io|
+ next if ios.include?(io)
+ assert_nothing_raised {io.close}
+ end
+ end;
+ end
+
def test_empty_evstr
assert_equal("", eval('"#{}"', nil, __FILE__, __LINE__), "[ruby-dev:25113]")
end
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index ddb4516b3c..da14c429e6 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -3,6 +3,7 @@
require 'test/unit'
require "rbconfig/sizeof"
require "timeout"
+require "fiddle"
class TestThread < Test::Unit::TestCase
class Thread < ::Thread
@@ -29,13 +30,19 @@ class TestThread < Test::Unit::TestCase
end
def test_inspect
+ m = Thread::Mutex.new
+ m.lock
line = __LINE__+1
- th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{}
+ th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do
+ m.synchronize {}
+ end
+ Thread.pass until th.stop?
s = th.inspect
assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:")
assert_include(s, " #{__FILE__}:#{line} ")
assert_equal(s, th.to_s)
ensure
+ m.unlock
th.join
end
@@ -317,7 +324,7 @@ class TestThread < Test::Unit::TestCase
s += 1
end
Thread.pass until t.stop?
- sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
+ sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
assert_equal(1, s)
t.wakeup
Thread.pass while t.alive?
@@ -389,7 +396,7 @@ class TestThread < Test::Unit::TestCase
end
INPUT
- assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+")
+ assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), %r".+")
p Thread.abort_on_exception
begin
t = Thread.new { raise }
@@ -966,6 +973,8 @@ _eom
end
def test_thread_timer_and_interrupt
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5757 = '[ruby-dev:44985]'
pid = nil
cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read'
@@ -1244,6 +1253,21 @@ q.pop
assert_predicate(status, :success?, bug9751)
end if Process.respond_to?(:fork)
+ def test_fork_value
+ bug18902 = "[Bug #18902]"
+ th = Thread.start { sleep 2 }
+ begin
+ pid = fork do
+ th.value
+ end
+ _, status = Process.wait2(pid)
+ assert_predicate(status, :success?, bug18902)
+ ensure
+ th.kill
+ th.join
+ end
+ end if Process.respond_to?(:fork)
+
def test_fork_while_locked
m = Thread::Mutex.new
thrs = []
@@ -1411,7 +1435,8 @@ q.pop
Thread.pass until th1.stop?
# After a thread starts (and execute `sleep`), it returns native_thread_id
- assert_instance_of Integer, th1.native_thread_id
+ native_tid = th1.native_thread_id
+ assert_instance_of Integer, native_tid if native_tid # it can be nil
th1.wakeup
Thread.pass while th1.alive?
@@ -1420,11 +1445,40 @@ q.pop
assert_nil th1.native_thread_id
end
+ def test_thread_native_thread_id_across_fork_on_linux
+ rtld_default = Fiddle.dlopen(nil)
+ omit "this test is only for Linux" unless rtld_default.sym_defined?('gettid')
+
+ gettid = Fiddle::Function.new(rtld_default['gettid'], [], Fiddle::TYPE_INT)
+
+ parent_thread_id = Thread.main.native_thread_id
+ real_parent_thread_id = gettid.call
+
+ assert_equal real_parent_thread_id, parent_thread_id
+
+ child_lines = nil
+ IO.popen('-') do |pipe|
+ if pipe
+ # parent
+ child_lines = pipe.read.lines
+ else
+ # child
+ puts Thread.main.native_thread_id
+ puts gettid.call
+ end
+ end
+ child_thread_id = child_lines[0].chomp.to_i
+ real_child_thread_id = child_lines[1].chomp.to_i
+
+ assert_equal real_child_thread_id, child_thread_id
+ refute_equal parent_thread_id, child_thread_id
+ end
+
def test_thread_interrupt_for_killed_thread
opts = { timeout: 5, timeout_error: nil }
- # prevent SIGABRT from slow shutdown with MJIT
- opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ # prevent SIGABRT from slow shutdown with RJIT
+ opts[:reprieve] = 3 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
Thread.report_on_exception = false
@@ -1442,6 +1496,11 @@ q.pop
omit "can't trap a signal from another process on Windows"
# opt = {new_pgroup: true}
end
+
+ if /freebsd/ =~ RUBY_PLATFORM
+ omit "[Bug #18613]"
+ end
+
assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120)
{#
n = 1000
@@ -1488,4 +1547,12 @@ q.pop
end
};
end
+
+ def test_pending_interrupt?
+ t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } }
+ t.raise(StandardError)
+ assert_equal(true, t.pending_interrupt?)
+ assert_equal(true, t.pending_interrupt?(Exception))
+ assert_equal(false, t.pending_interrupt?(ArgumentError))
+ end
end
diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb
index 88733419da..eb88b9606c 100644
--- a/test/ruby/test_thread_cv.rb
+++ b/test/ruby/test_thread_cv.rb
@@ -6,12 +6,6 @@ class TestThreadConditionVariable < Test::Unit::TestCase
ConditionVariable = Thread::ConditionVariable
Mutex = Thread::Mutex
- def test_initialized
- assert_raise(TypeError) {
- ConditionVariable.allocate.wait(nil)
- }
- end
-
def test_condvar_signal_and_wait
mutex = Thread::Mutex.new
condvar = Thread::ConditionVariable.new
diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb
index 69cb40711c..545bf98888 100644
--- a/test/ruby/test_thread_queue.rb
+++ b/test/ruby/test_thread_queue.rb
@@ -8,17 +8,26 @@ class TestThreadQueue < Test::Unit::TestCase
SizedQueue = Thread::SizedQueue
def test_queue_initialized
- assert_raise(TypeError) {
+ assert_raise_with_message(TypeError, /\bQueue.* not initialized/) {
Queue.allocate.push(nil)
}
end
def test_sized_queue_initialized
- assert_raise(TypeError) {
+ assert_raise_with_message(TypeError, /\bSizedQueue.* not initialized/) {
SizedQueue.allocate.push(nil)
}
end
+ def test_freeze
+ assert_raise(TypeError) {
+ Queue.new.freeze
+ }
+ assert_raise(TypeError) {
+ SizedQueue.new(5).freeze
+ }
+ end
+
def test_queue
grind(5, 1000, 15, Queue)
end
@@ -111,6 +120,23 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal(0, q.num_waiting)
end
+ def test_queue_pop_timeout
+ q = Thread::Queue.new
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_queue_pop_non_block
q = Thread::Queue.new
assert_raise_with_message(ThreadError, /empty/) do
@@ -126,6 +152,24 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal(0, q.num_waiting)
end
+ def test_sized_queue_pop_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_sized_queue_pop_non_block
q = Thread::SizedQueue.new(1)
assert_raise_with_message(ThreadError, /empty/) do
@@ -133,6 +177,24 @@ class TestThreadQueue < Test::Unit::TestCase
end
end
+ def test_sized_queue_push_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.size
+
+ t1 = Thread.new { q.push(2, timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.push(2, timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_sized_queue_push_interrupt
q = Thread::SizedQueue.new(1)
q.push(1)
@@ -151,16 +213,18 @@ class TestThreadQueue < Test::Unit::TestCase
end
def test_thr_kill
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5343 = '[ruby-core:39634]'
Dir.mktmpdir {|d|
- timeout = EnvUtil.apply_timeout_scale(60)
+ timeout = 60
total_count = 250
begin
- assert_normal_exit(<<-"_eom", bug5343, **{:timeout => timeout, :chdir=>d})
+ assert_normal_exit(<<-"_eom", bug5343, timeout: timeout, chdir: d)
+ r, w = IO.pipe
#{total_count}.times do |i|
- open("test_thr_kill_count", "w") {|f| f.puts i }
+ File.open("test_thr_kill_count", "w") {|f| f.puts i }
queue = Thread::Queue.new
- r, w = IO.pipe
th = Thread.start {
queue.push(nil)
r.read 1
@@ -530,9 +594,14 @@ class TestThreadQueue < Test::Unit::TestCase
count_items = rand(3000..5000)
count_producers = rand(10..20)
+ # ensure threads do not start running too soon and complete before we check status
+ mutex = Mutex.new
+ mutex.lock
+
producers = count_producers.times.map do
Thread.new do
- sleep(rand / 100)
+ mutex.lock
+ mutex.unlock
count_items.times{|i| q << [i,"#{i} for #{Thread.current.inspect}"]}
end
end
@@ -550,9 +619,11 @@ class TestThreadQueue < Test::Unit::TestCase
# No dead or finished threads, give up to 10 seconds to start running
t = Time.now
- Thread.pass until Time.now - t > 10 || (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/}
+ Thread.pass until Time.now - t > 10 || (consumers + producers).all?{|thr| thr.status.to_s =~ /\A(?:run|sleep)\z/}
+
+ assert (consumers + producers).all?{|thr| thr.status.to_s =~ /\A(?:run|sleep)\z/}, 'no threads running'
- assert (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/}, 'no threads running'
+ mutex.unlock
# just exercising the concurrency of the support methods.
counter = Thread.new do
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index 8300681fe5..2a541bbe8c 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -53,6 +53,105 @@ class TestTime < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, 1, "+09:99") }
assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, "+09:99") }
assert_raise_with_message(ArgumentError, msg) { Time.new(2021, "+09:99") }
+
+ assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], Time.new(2000, 1, 1, 0, 0, 0, "-00:00").to_a)
+ assert_equal([0, 0, 0, 2, 1, 2000, 0, 2, false, "UTC"], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a)
+ end
+
+ def test_new_from_string
+ assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") }
+
+ t = Time.utc(2020, 12, 24, 15, 56, 17)
+ assert_equal(t, Time.new("2020-12-24T15:56:17Z"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +0900"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +090130"))
+ assert_equal(t, Time.new("2020-12-25T00:56:17+09:00"))
+ assert_raise_with_message(ArgumentError, /missing sec part/) {
+ Time.new("2020-12-25 00:56 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /missing min part/) {
+ Time.new("2020-12-25 00 +09:00")
+ }
+
+ assert_equal(Time.new(2021), Time.new("2021"))
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00"))
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00", in: "-01:00"))
+
+ assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec)
+ assert_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec)
+ assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec)
+ assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec)
+ assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") {
+ Time.new("2020-12-25 00:56:17. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /year must be 4 or more/) {
+ Time.new("021-12-25 00:00:00.123456 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /fraction min is.*56\./) {
+ Time.new("2020-12-25 00:56. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /fraction hour is.*00\./) {
+ Time.new("2020-12-25 00. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:017\b/) {
+ Time.new("2020-12-25 00:56:017 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:9\b/) {
+ Time.new("2020-12-25 00:56:9 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /sec out of range/) {
+ Time.new("2020-12-25 00:56:64 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:056\b/) {
+ Time.new("2020-12-25 00:056:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:5\b/) {
+ Time.new("2020-12-25 00:5:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /min out of range/) {
+ Time.new("2020-12-25 00:64:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b000\b/) {
+ Time.new("2020-12-25 000:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b0\b/) {
+ Time.new("2020-12-25 0:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /hour out of range/) {
+ Time.new("2020-12-25 33:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b025\b/) {
+ Time.new("2020-12-025 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b5\b/) {
+ Time.new("2020-12-5 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mday out of range/) {
+ Time.new("2020-12-33 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b012\b/) {
+ Time.new("2020-012-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b1\b/) {
+ Time.new("2020-1-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mon out of range/) {
+ Time.new("2020-17-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12-02")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new(" 2020-12-02 00:00:00")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new("2020-12-02 00:00:00 ")
+ }
end
def test_time_add()
@@ -340,7 +439,7 @@ class TestTime < Test::Unit::TestCase
end
def test_marshal_zone_gc
- assert_separately(%w(--disable-gems), <<-'end;', timeout: 30)
+ assert_separately([], <<-'end;', timeout: 30)
ENV["TZ"] = "JST-9"
s = Marshal.dump(Time.now)
t = Marshal.load(s)
@@ -1309,7 +1408,10 @@ class TestTime < Test::Unit::TestCase
def test_memsize
# Time objects are common in some code, try to keep them small
omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
- omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:DEBUG]
+ omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] > 0
+ omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit "Only run this test on 64-bit" if RbConfig::SIZEOF["void*"] != 8
+
require 'objspace'
t = Time.at(0)
sizeof_timew =
@@ -1318,13 +1420,28 @@ class TestTime < Test::Unit::TestCase
else
RbConfig::SIZEOF["void*"] # Same size as VALUE
end
- expect =
- GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] +
- sizeof_timew +
- RbConfig::SIZEOF["void*"] * 4 + 5 + # vtm
- 1 # tzmode, tm_got
- assert_equal expect, ObjectSpace.memsize_of(t)
+ sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8
+ expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm
+ assert_operator ObjectSpace.memsize_of(t), :<=, expect
rescue LoadError => e
omit "failed to load objspace: #{e.message}"
end
+
+ def test_deconstruct_keys
+ t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) }
+ assert_equal(
+ {year: 2022, month: 10, day: 16, wday: 0, yday: 289,
+ hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'},
+ t.deconstruct_keys(nil)
+ )
+
+ assert_equal(
+ {year: 2022, month: 10, sec: 30},
+ t.deconstruct_keys(%i[year month sec nonexistent])
+ )
+ end
+
+ def test_parse_zero_bigint
+ assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]'
+ end
end
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index 6ae12dea5d..f66cd9bec2 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -7,9 +7,9 @@ class TestTimeTZ < Test::Unit::TestCase
has_lisbon_tz = true
force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes"
case RUBY_PLATFORM
- when /linux/
+ when /darwin|linux/
force_tz_test = true
- when /darwin|freebsd|openbsd/
+ when /freebsd|openbsd/
has_lisbon_tz = false
force_tz_test = true
end
@@ -95,6 +95,9 @@ class TestTimeTZ < Test::Unit::TestCase
CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") {
Time.local(1994, 12, 31, 0, 0, 0).year == 1995
}
+ CORRECT_SINGAPORE_1982 = with_tz("Asia/Singapore") {
+ "2022g" if Time.local(1981, 12, 31, 23, 59, 59).utc_offset == 8*3600
+ }
def time_to_s(t)
t.to_s
@@ -140,9 +143,12 @@ class TestTimeTZ < Test::Unit::TestCase
def test_asia_singapore
with_tz(tz="Asia/Singapore") {
- assert_time_constructor(tz, "1981-12-31 23:59:59 +0730", :local, [1981,12,31,23,59,59])
- assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,0,0])
- assert_time_constructor(tz, "1982-01-01 00:59:59 +0800", :local, [1982,1,1,0,29,59])
+ assert_time_constructor(tz, "1981-12-31 23:29:59 +0730", :local, [1981,12,31,23,29,59])
+ if CORRECT_SINGAPORE_1982
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1981,12,31,23,30,00])
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1982,1,1,0,0,0])
+ assert_time_constructor(tz, "1982-01-01 00:29:59 +0800", :local, [1982,1,1,0,29,59])
+ end
assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0])
}
end
@@ -196,7 +202,7 @@ class TestTimeTZ < Test::Unit::TestCase
def test_europe_lisbon
with_tz("Europe/Lisbon") {
- assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone)
+ assert_include(%w"LMT CET", Time.new(-0x1_0000_0000_0000_0000).zone)
}
end if has_lisbon_tz
@@ -450,9 +456,12 @@ America/Managua Fri Jan 1 06:00:00 1993 UTC = Fri Jan 1 01:00:00 1993 EST isd
America/Managua Wed Jan 1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000
America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600
Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0 gmtoff=27000
-Asia/Singapore Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000
+Asia/Singapore Thu Dec 31 15:59:59 1981 UTC = Thu Dec 31 23:29:59 1981 SGT isdst=0 gmtoff=27000
Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0 gmtoff=28800
End
+ gen_zdump_test <<'End' if CORRECT_SINGAPORE_1982
+Asia/Singapore Thu Dec 31 16:00:00 1981 UTC = Fri Jan 1 00:00:00 1982 SGT isdst=0 gmtoff=28800
+End
gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End'
Asia/Tokyo Sat May 5 14:59:59 1951 UTC = Sat May 5 23:59:59 1951 JST isdst=0 gmtoff=32400
Asia/Tokyo Sat May 5 15:00:00 1951 UTC = Sun May 6 01:00:00 1951 JDT isdst=1 gmtoff=36000
@@ -612,6 +621,11 @@ module TestTimeTZ::WithTZ
assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)}
end
+ def subtest_hour24(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2000, 1, 1, 24, 0, 0, tzarg)
+ assert_equal([0, 0, 0, 2, 1, 2000], [t.sec, t.min, t.hour, t.mday, t.mon, t.year])
+ end
+
def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset)
t = time_class.now(in: tzarg)
assert_equal(tz, t.zone)
@@ -681,6 +695,13 @@ module TestTimeTZ::WithTZ
assert_equal(t.dst?, t2.dst?)
end
+ def subtest_fractional_second(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2024, 1, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ t = time_class.new(2024, 7, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ end
+
def test_invalid_zone
make_timezone("INVALID", "INV", 0)
rescue => e
@@ -705,6 +726,7 @@ module TestTimeTZ::WithTZ
"Asia/Tokyo" => ["JST", +9*3600],
"America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600],
"Africa/Ndjamena" => ["WAT", +1*3600],
+ "Etc/UTC" => ["UTC", 0],
}
def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb
index c8b0034e06..ceef19e7ea 100644
--- a/test/ruby/test_transcode.rb
+++ b/test/ruby/test_transcode.rb
@@ -10,9 +10,9 @@ class TestTranscode < Test::Unit::TestCase
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.encode!('foo', 'bar') }
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode('foo') }
assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode!('foo') }
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode('utf-8','ASCII-8BIT') }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x80".encode('utf-8','US-ASCII') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA5".encode('utf-8','iso-8859-3') }
+ assert_undefined_in("\x80", 'ASCII-8BIT')
+ assert_invalid_in("\x80", 'US-ASCII')
+ assert_undefined_in("\xA5", 'iso-8859-3')
assert_raise(FrozenError) { 'hello'.freeze.encode!('iso-8859-1') }
assert_raise(FrozenError) { '\u3053\u3093\u306b\u3061\u306f'.freeze.encode!('iso-8859-1') } # こんにちは
end
@@ -52,16 +52,6 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u20AC"*200000, ("\xA4"*200000).encode!('utf-8', 'iso-8859-15'))
end
- def check_both_ways(utf8, raw, encoding)
- assert_equal(utf8.force_encoding('utf-8'), raw.encode('utf-8', encoding),utf8.dump+raw.dump)
- assert_equal(raw.force_encoding(encoding), utf8.encode(encoding, 'utf-8'))
- end
-
- def check_both_ways2(str1, enc1, str2, enc2)
- assert_equal(str1.force_encoding(enc1), str2.encode(enc1, enc2))
- assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1))
- end
-
def test_encoding_of_ascii_originating_from_binary
binary_string = [0x82, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20,
0x61, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x6f,
@@ -188,16 +178,16 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_874
check_both_ways("\u20AC", "\x80", 'windows-874') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x84".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x81", 'windows-874')
+ assert_undefined_in("\x84", 'windows-874')
check_both_ways("\u2026", "\x85", 'windows-874') # …
- assert_raise(Encoding::UndefinedConversionError) { "\x86".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x86", 'windows-874')
+ assert_undefined_in("\x8F", 'windows-874')
+ assert_undefined_in("\x90", 'windows-874')
check_both_ways("\u2018", "\x91", 'windows-874') # ‘
check_both_ways("\u2014", "\x97", 'windows-874') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\x98", 'windows-874')
+ assert_undefined_in("\x9F", 'windows-874')
check_both_ways("\u00A0", "\xA0", 'windows-874') # non-breaking space
check_both_ways("\u0E0F", "\xAF", 'windows-874') # ฏ
check_both_ways("\u0E10", "\xB0", 'windows-874') # ฐ
@@ -206,31 +196,31 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0E2F", "\xCF", 'windows-874') # ฯ
check_both_ways("\u0E30", "\xD0", 'windows-874') # ะ
check_both_ways("\u0E3A", "\xDA", 'windows-874') # ฺ
- assert_raise(Encoding::UndefinedConversionError) { "\xDB".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDE".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\xDB", 'windows-874')
+ assert_undefined_in("\xDE", 'windows-874')
check_both_ways("\u0E3F", "\xDF", 'windows-874') # ฿
check_both_ways("\u0E40", "\xE0", 'windows-874') # เ
check_both_ways("\u0E4F", "\xEF", 'windows-874') # ๏
check_both_ways("\u0E50", "\xF0", 'windows-874') # ๐
check_both_ways("\u0E5B", "\xFB", 'windows-874') # ๛
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'windows-874') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-874') }
+ assert_undefined_in("\xFC", 'windows-874')
+ assert_undefined_in("\xFF", 'windows-874')
end
def test_windows_1250
check_both_ways("\u20AC", "\x80", 'windows-1250') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x81", 'windows-1250')
check_both_ways("\u201A", "\x82", 'windows-1250') # ‚
- assert_raise(Encoding::UndefinedConversionError) { "\x83".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x83", 'windows-1250')
check_both_ways("\u201E", "\x84", 'windows-1250') # „
check_both_ways("\u2021", "\x87", 'windows-1250') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x88", 'windows-1250')
check_both_ways("\u2030", "\x89", 'windows-1250') # ‰
check_both_ways("\u0179", "\x8F", 'windows-1250') # Ź
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x90", 'windows-1250')
check_both_ways("\u2018", "\x91", 'windows-1250') # ‘
check_both_ways("\u2014", "\x97", 'windows-1250') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1250') }
+ assert_undefined_in("\x98", 'windows-1250')
check_both_ways("\u2122", "\x99", 'windows-1250') # ™
check_both_ways("\u00A0", "\xA0", 'windows-1250') # non-breaking space
check_both_ways("\u017B", "\xAF", 'windows-1250') # Ż
@@ -251,7 +241,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u20AC", "\x88", 'windows-1251') # €
check_both_ways("\u040F", "\x8F", 'windows-1251') # Џ
check_both_ways("\u0452", "\x90", 'windows-1251') # ђ
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1251') }
+ assert_undefined_in("\x98", 'windows-1251')
check_both_ways("\u045F", "\x9F", 'windows-1251') # џ
check_both_ways("\u00A0", "\xA0", 'windows-1251') # non-breaking space
check_both_ways("\u0407", "\xAF", 'windows-1251') # Ї
@@ -269,16 +259,16 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1252
check_both_ways("\u20AC", "\x80", 'windows-1252') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x81", 'windows-1252')
check_both_ways("\u201A", "\x82", 'windows-1252') # ‚
check_both_ways("\u0152", "\x8C", 'windows-1252') # >Œ
- assert_raise(Encoding::UndefinedConversionError) { "\x8D".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x8D", 'windows-1252')
check_both_ways("\u017D", "\x8E", 'windows-1252') # Ž
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1252') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x8F", 'windows-1252')
+ assert_undefined_in("\x90", 'windows-1252')
check_both_ways("\u2018", "\x91", 'windows-1252') #‘
check_both_ways("\u0153", "\x9C", 'windows-1252') # œ
- assert_raise(Encoding::UndefinedConversionError) { "\x9D".encode("utf-8", 'windows-1252') }
+ assert_undefined_in("\x9D", 'windows-1252')
check_both_ways("\u017E", "\x9E", 'windows-1252') # ž
check_both_ways("\u00A0", "\xA0", 'windows-1252') # non-breaking space
check_both_ways("\u00AF", "\xAF", 'windows-1252') # ¯
@@ -296,24 +286,24 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1253
check_both_ways("\u20AC", "\x80", 'windows-1253') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x81", 'windows-1253')
check_both_ways("\u201A", "\x82", 'windows-1253') # ‚
check_both_ways("\u2021", "\x87", 'windows-1253') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x88", 'windows-1253')
check_both_ways("\u2030", "\x89", 'windows-1253') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x8A", 'windows-1253')
check_both_ways("\u2039", "\x8B", 'windows-1253') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x8C", 'windows-1253')
+ assert_undefined_in("\x8F", 'windows-1253')
+ assert_undefined_in("\x90", 'windows-1253')
check_both_ways("\u2018", "\x91", 'windows-1253') # ‘
check_both_ways("\u2014", "\x97", 'windows-1253') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x98", 'windows-1253')
check_both_ways("\u2122", "\x99", 'windows-1253') # ™
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x9A", 'windows-1253')
check_both_ways("\u203A", "\x9B", 'windows-1253') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1253') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\x9C", 'windows-1253')
+ assert_undefined_in("\x9F", 'windows-1253')
check_both_ways("\u00A0", "\xA0", 'windows-1253') # non-breaking space
check_both_ways("\u2015", "\xAF", 'windows-1253') # ―
check_both_ways("\u00B0", "\xB0", 'windows-1253') # °
@@ -322,28 +312,28 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u039F", "\xCF", 'windows-1253') # Ο
check_both_ways("\u03A0", "\xD0", 'windows-1253') # Π
check_both_ways("\u03A1", "\xD1", 'windows-1253') # Ρ
- assert_raise(Encoding::UndefinedConversionError) { "\xD2".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\xD2", 'windows-1253')
check_both_ways("\u03A3", "\xD3", 'windows-1253') # Σ
check_both_ways("\u03AF", "\xDF", 'windows-1253') # ί
check_both_ways("\u03B0", "\xE0", 'windows-1253') # ΰ
check_both_ways("\u03BF", "\xEF", 'windows-1253') # ο
check_both_ways("\u03C0", "\xF0", 'windows-1253') # π
check_both_ways("\u03CE", "\xFE", 'windows-1253') # ώ
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-1253') }
+ assert_undefined_in("\xFF", 'windows-1253')
end
def test_windows_1254
check_both_ways("\u20AC", "\x80", 'windows-1254') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x81", 'windows-1254')
check_both_ways("\u201A", "\x82", 'windows-1254') # ‚
check_both_ways("\u0152", "\x8C", 'windows-1254') # Œ
- assert_raise(Encoding::UndefinedConversionError) { "\x8D".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x8D", 'windows-1254')
+ assert_undefined_in("\x8F", 'windows-1254')
+ assert_undefined_in("\x90", 'windows-1254')
check_both_ways("\u2018", "\x91", 'windows-1254') # ‘
check_both_ways("\u0153", "\x9C", 'windows-1254') # œ
- assert_raise(Encoding::UndefinedConversionError) { "\x9D".encode("utf-8", 'windows-1254') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9E".encode("utf-8", 'windows-1254') }
+ assert_undefined_in("\x9D", 'windows-1254')
+ assert_undefined_in("\x9E", 'windows-1254')
check_both_ways("\u0178", "\x9F", 'windows-1254') # Ÿ
check_both_ways("\u00A0", "\xA0", 'windows-1254') # non-breaking space
check_both_ways("\u00AF", "\xAF", 'windows-1254') # ¯
@@ -361,20 +351,20 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1255
check_both_ways("\u20AC", "\x80", 'windows-1255') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x81", 'windows-1255')
check_both_ways("\u201A", "\x82", 'windows-1255') # ‚
check_both_ways("\u2030", "\x89", 'windows-1255') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x8A", 'windows-1255')
check_both_ways("\u2039", "\x8B", 'windows-1255') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x8C", 'windows-1255')
+ assert_undefined_in("\x8F", 'windows-1255')
+ assert_undefined_in("\x90", 'windows-1255')
check_both_ways("\u2018", "\x91", 'windows-1255') # ‘
check_both_ways("\u2122", "\x99", 'windows-1255') # ™
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x9A", 'windows-1255')
check_both_ways("\u203A", "\x9B", 'windows-1255') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\x9C", 'windows-1255')
+ assert_undefined_in("\x9F", 'windows-1255')
check_both_ways("\u00A0", "\xA0", 'windows-1255') # non-breaking space
check_both_ways("\u00A1", "\xA1", 'windows-1255') # ¡
check_both_ways("\u00D7", "\xAA", 'windows-1255') # ×
@@ -391,17 +381,17 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u05C0", "\xD0", 'windows-1255') # ׀
check_both_ways("\u05F3", "\xD7", 'windows-1255') # ׳
check_both_ways("\u05F4", "\xD8", 'windows-1255') # ״
- assert_raise(Encoding::UndefinedConversionError) { "\xD9".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDF".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xD9", 'windows-1255')
+ assert_undefined_in("\xDF", 'windows-1255')
check_both_ways("\u05D0", "\xE0", 'windows-1255') # א
check_both_ways("\u05DF", "\xEF", 'windows-1255') # ן
check_both_ways("\u05E0", "\xF0", 'windows-1255') # נ
check_both_ways("\u05EA", "\xFA", 'windows-1255') # ת
- assert_raise(Encoding::UndefinedConversionError) { "\xFB".encode("utf-8", 'windows-1255') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xFB", 'windows-1255')
+ assert_undefined_in("\xFC", 'windows-1255')
check_both_ways("\u200E", "\xFD", 'windows-1255') # left-to-right mark
check_both_ways("\u200F", "\xFE", 'windows-1255') # right-to-left mark
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'windows-1255') }
+ assert_undefined_in("\xFF", 'windows-1255')
end
def test_windows_1256
@@ -429,35 +419,35 @@ class TestTranscode < Test::Unit::TestCase
def test_windows_1257
check_both_ways("\u20AC", "\x80", 'windows-1257') # €
- assert_raise(Encoding::UndefinedConversionError) { "\x81".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x81", 'windows-1257')
check_both_ways("\u201A", "\x82", 'windows-1257') # ‚
- assert_raise(Encoding::UndefinedConversionError) { "\x83".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x83", 'windows-1257')
check_both_ways("\u201E", "\x84", 'windows-1257') # „
check_both_ways("\u2021", "\x87", 'windows-1257') # ‡
- assert_raise(Encoding::UndefinedConversionError) { "\x88".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x88", 'windows-1257')
check_both_ways("\u2030", "\x89", 'windows-1257') # ‰
- assert_raise(Encoding::UndefinedConversionError) { "\x8A".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x8A", 'windows-1257')
check_both_ways("\u2039", "\x8B", 'windows-1257') # ‹
- assert_raise(Encoding::UndefinedConversionError) { "\x8C".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x8C", 'windows-1257')
check_both_ways("\u00A8", "\x8D", 'windows-1257') # ¨
check_both_ways("\u02C7", "\x8E", 'windows-1257') # ˇ
check_both_ways("\u00B8", "\x8F", 'windows-1257') # ¸
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x90", 'windows-1257')
check_both_ways("\u2018", "\x91", 'windows-1257') # ‘
check_both_ways("\u2014", "\x97", 'windows-1257') # —
- assert_raise(Encoding::UndefinedConversionError) { "\x98".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x98", 'windows-1257')
check_both_ways("\u2122", "\x99", 'windows-1257') # ™
- assert_raise(Encoding::UndefinedConversionError) { "\x9A".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9A", 'windows-1257')
check_both_ways("\u203A", "\x9B", 'windows-1257') # ›
- assert_raise(Encoding::UndefinedConversionError) { "\x9C".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9C", 'windows-1257')
check_both_ways("\u00AF", "\x9D", 'windows-1257') # ¯
check_both_ways("\u02DB", "\x9E", 'windows-1257') # ˛
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\x9F", 'windows-1257')
check_both_ways("\u00A0", "\xA0", 'windows-1257') # non-breaking space
- assert_raise(Encoding::UndefinedConversionError) { "\xA1".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\xA1", 'windows-1257')
check_both_ways("\u00A2", "\xA2", 'windows-1257') # ¢
check_both_ways("\u00A4", "\xA4", 'windows-1257') # ¤
- assert_raise(Encoding::UndefinedConversionError) { "\xA5".encode("utf-8", 'windows-1257') }
+ assert_undefined_in("\xA5", 'windows-1257')
check_both_ways("\u00A6", "\xA6", 'windows-1257') # ¦
check_both_ways("\u00C6", "\xAF", 'windows-1257') # Æ
check_both_ways("\u00B0", "\xB0", 'windows-1257') # °
@@ -492,9 +482,9 @@ class TestTranscode < Test::Unit::TestCase
end
def test_IBM720
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'IBM720') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'IBM720') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'IBM720') }
+ assert_undefined_in("\x80", 'IBM720')
+ assert_undefined_in("\x8F", 'IBM720')
+ assert_undefined_in("\x90", 'IBM720')
check_both_ways("\u0627", "\x9F", 'IBM720') # ا
check_both_ways("\u0628", "\xA0", 'IBM720') # ب
check_both_ways("\u00BB", "\xAF", 'IBM720') # »
@@ -580,17 +570,17 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00A4", "\xCF", 'IBM857') # ¤
check_both_ways("\u00BA", "\xD0", 'IBM857') # º
check_both_ways("\u00C8", "\xD4", 'IBM857') # È
- assert_raise(Encoding::UndefinedConversionError) { "\xD5".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xD5", 'IBM857')
check_both_ways("\u00CD", "\xD6", 'IBM857') # Í
check_both_ways("\u2580", "\xDF", 'IBM857') # ▀
check_both_ways("\u00D3", "\xE0", 'IBM857') # Ó
check_both_ways("\u00B5", "\xE6", 'IBM857') # µ
- assert_raise(Encoding::UndefinedConversionError) { "\xE7".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xE7", 'IBM857')
check_both_ways("\u00D7", "\xE8", 'IBM857') # ×
check_both_ways("\u00B4", "\xEF", 'IBM857') # ´
check_both_ways("\u00AD", "\xF0", 'IBM857') # soft hyphen
check_both_ways("\u00B1", "\xF1", 'IBM857') # ±
- assert_raise(Encoding::UndefinedConversionError) { "\xF2".encode("utf-8", 'IBM857') }
+ assert_undefined_in("\xF2", 'IBM857')
check_both_ways("\u00BE", "\xF3", 'IBM857') # ¾
check_both_ways("\u00A0", "\xFF", 'IBM857') # non-breaking space
end
@@ -710,16 +700,16 @@ class TestTranscode < Test::Unit::TestCase
end
def test_IBM869
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'IBM869') }
- assert_raise(Encoding::UndefinedConversionError) { "\x85".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x80", 'IBM869')
+ assert_undefined_in("\x85", 'IBM869')
check_both_ways("\u0386", "\x86", 'IBM869') # Ά
- assert_raise(Encoding::UndefinedConversionError) { "\x87".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x87", 'IBM869')
check_both_ways("\u00B7", "\x88", 'IBM869') # ·
check_both_ways("\u0389", "\x8F", 'IBM869') # Ή
check_both_ways("\u038A", "\x90", 'IBM869') # Ί
check_both_ways("\u038C", "\x92", 'IBM869') # Ό
- assert_raise(Encoding::UndefinedConversionError) { "\x93".encode("utf-8", 'IBM869') }
- assert_raise(Encoding::UndefinedConversionError) { "\x94".encode("utf-8", 'IBM869') }
+ assert_undefined_in("\x93", 'IBM869')
+ assert_undefined_in("\x94", 'IBM869')
check_both_ways("\u038E", "\x95", 'IBM869') # Ύ
check_both_ways("\u03AF", "\x9F", 'IBM869') # ί
check_both_ways("\u03CA", "\xA0", 'IBM869') # ϊ
@@ -808,7 +798,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u03BF", "\xEF", 'macGreek') # ο
check_both_ways("\u03C0", "\xF0", 'macGreek') # π
check_both_ways("\u03B0", "\xFE", 'macGreek') # ΰ
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'macGreek') }
+ assert_undefined_in("\xFF", 'macGreek')
end
def test_macIceland
@@ -887,7 +877,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00D4", "\xEF", 'macTurkish') # Ô
#check_both_ways("\uF8FF", "\xF0", 'macTurkish') # Apple logo
check_both_ways("\u00D9", "\xF4", 'macTurkish') # Ù
- assert_raise(Encoding::UndefinedConversionError) { "\xF5".encode("utf-8", 'macTurkish') }
+ assert_undefined_in("\xF5", 'macTurkish')
check_both_ways("\u02C6", "\xF6", 'macTurkish') # ˆ
check_both_ways("\u02C7", "\xFF", 'macTurkish') # ˇ
end
@@ -958,11 +948,11 @@ class TestTranscode < Test::Unit::TestCase
end
def test_TIS_620
- assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\x9F".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA0".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\x80", 'TIS-620')
+ assert_undefined_in("\x8F", 'TIS-620')
+ assert_undefined_in("\x90", 'TIS-620')
+ assert_undefined_in("\x9F", 'TIS-620')
+ assert_undefined_in("\xA0", 'TIS-620')
check_both_ways("\u0E01", "\xA1", 'TIS-620') # ก
check_both_ways("\u0E0F", "\xAF", 'TIS-620') # ฏ
check_both_ways("\u0E10", "\xB0", 'TIS-620') # ฐ
@@ -971,15 +961,15 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0E2F", "\xCF", 'TIS-620') # ฯ
check_both_ways("\u0E30", "\xD0", 'TIS-620') # ะ
check_both_ways("\u0E3A", "\xDA", 'TIS-620') # ฺ
- assert_raise(Encoding::UndefinedConversionError) { "\xDB".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xDE".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\xDB", 'TIS-620')
+ assert_undefined_in("\xDE", 'TIS-620')
check_both_ways("\u0E3F", "\xDF", 'TIS-620') # ฿
check_both_ways("\u0E40", "\xE0", 'TIS-620') # เ
check_both_ways("\u0E4F", "\xEF", 'TIS-620') # ๏
check_both_ways("\u0E50", "\xF0", 'TIS-620') # ๐
check_both_ways("\u0E5B", "\xFB", 'TIS-620') # ๛
- assert_raise(Encoding::UndefinedConversionError) { "\xFC".encode("utf-8", 'TIS-620') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFF".encode("utf-8", 'TIS-620') }
+ assert_undefined_in("\xFC", 'TIS-620')
+ assert_undefined_in("\xFF", 'TIS-620')
end
def test_CP850
@@ -1182,15 +1172,15 @@ class TestTranscode < Test::Unit::TestCase
expected = "\u{3042}\u{3044}\u{20bb7}"
assert_equal(expected, %w/fffe4230443042d8b7df/.pack("H*").encode("UTF-8","UTF-16"))
check_both_ways(expected, %w/feff30423044d842dfb7/.pack("H*"), "UTF-16")
- assert_raise(Encoding::InvalidByteSequenceError){%w/feffdfb7/.pack("H*").encode("UTF-8","UTF-16")}
- assert_raise(Encoding::InvalidByteSequenceError){%w/fffeb7df/.pack("H*").encode("UTF-8","UTF-16")}
+ assert_invalid_in(%w/feffdfb7/.pack("H*"), "UTF-16")
+ assert_invalid_in(%w/fffeb7df/.pack("H*"), "UTF-16")
end
def test_utf_32_bom
expected = "\u{3042}\u{3044}\u{20bb7}"
assert_equal(expected, %w/fffe00004230000044300000b70b0200/.pack("H*").encode("UTF-8","UTF-32"))
check_both_ways(expected, %w/0000feff000030420000304400020bb7/.pack("H*"), "UTF-32")
- assert_raise(Encoding::InvalidByteSequenceError){%w/0000feff00110000/.pack("H*").encode("UTF-8","UTF-32")}
+ assert_invalid_in(%w/0000feff00110000/.pack("H*"), "UTF-32")
end
def check_utf_32_both_ways(utf8, raw)
@@ -1372,24 +1362,24 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u71FC", "\xE0\x9E", 'shift_jis') # 燼
check_both_ways("\u71F9", "\xE0\x9F", 'shift_jis') # 燹
check_both_ways("\u73F1", "\xE0\xFC", 'shift_jis') # 珱
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x40".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xEF\xFC".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x40".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xF0\xFC".encode("utf-8", 'shift_jis') }
+ assert_undefined_in("\xEF\x40", 'shift_jis')
+ assert_undefined_in("\xEF\x7E", 'shift_jis')
+ assert_undefined_in("\xEF\x80", 'shift_jis')
+ assert_undefined_in("\xEF\x9E", 'shift_jis')
+ assert_undefined_in("\xEF\x9F", 'shift_jis')
+ assert_undefined_in("\xEF\xFC", 'shift_jis')
+ assert_undefined_in("\xF0\x40", 'shift_jis')
+ assert_undefined_in("\xF0\x7E", 'shift_jis')
+ assert_undefined_in("\xF0\x80", 'shift_jis')
+ assert_undefined_in("\xF0\x9E", 'shift_jis')
+ assert_undefined_in("\xF0\x9F", 'shift_jis')
+ assert_undefined_in("\xF0\xFC", 'shift_jis')
#check_both_ways("\u9ADC", "\xFC\x40", 'shift_jis') # 髜 (IBM extended)
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x7E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x80".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x9E".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\x9F".encode("utf-8", 'shift_jis') }
- assert_raise(Encoding::UndefinedConversionError) { "\xFC\xFC".encode("utf-8", 'shift_jis') }
+ assert_undefined_in("\xFC\x7E", 'shift_jis')
+ assert_undefined_in("\xFC\x80", 'shift_jis')
+ assert_undefined_in("\xFC\x9E", 'shift_jis')
+ assert_undefined_in("\xFC\x9F", 'shift_jis')
+ assert_undefined_in("\xFC\xFC", 'shift_jis')
check_both_ways("\u677E\u672C\u884C\u5F18", "\x8f\xbc\x96\x7b\x8d\x73\x8d\x4f", 'shift_jis') # 松本行弘
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\x90\xC2\x8E\x52\x8A\x77\x89\x40\x91\xE5\x8A\x77", 'shift_jis') # 青山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\x90\x5F\x97\xD1\x8B\x60\x94\x8E", 'shift_jis') # 神林義博
@@ -1409,34 +1399,34 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u00F7", "\xA1\xE0", 'euc-jp') # ÷
check_both_ways("\u25C7", "\xA1\xFE", 'euc-jp') # ◇
check_both_ways("\u25C6", "\xA2\xA1", 'euc-jp') # ◆
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xAF".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xC2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xC9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xD1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xDB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xEB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xFA".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xFD".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xA2\xAF", 'euc-jp')
+ assert_undefined_in("\xA2\xB9", 'euc-jp')
+ assert_undefined_in("\xA2\xC2", 'euc-jp')
+ assert_undefined_in("\xA2\xC9", 'euc-jp')
+ assert_undefined_in("\xA2\xD1", 'euc-jp')
+ assert_undefined_in("\xA2\xDB", 'euc-jp')
+ assert_undefined_in("\xA2\xEB", 'euc-jp')
+ assert_undefined_in("\xA2\xF1", 'euc-jp')
+ assert_undefined_in("\xA2\xFA", 'euc-jp')
+ assert_undefined_in("\xA2\xFD", 'euc-jp')
check_both_ways("\u25EF", "\xA2\xFE", 'euc-jp') # ◯
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xAF".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xBA".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xDB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xE0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xFB".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA4\xF4".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xF7".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xB9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xC0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xD9".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xC2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xD0".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA7\xF2".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC1".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xCF\xD4".encode("utf-8", 'euc-jp') }
- assert_raise(Encoding::UndefinedConversionError) { "\xCF\xFE".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xA3\xAF", 'euc-jp')
+ assert_undefined_in("\xA3\xBA", 'euc-jp')
+ assert_undefined_in("\xA3\xC0", 'euc-jp')
+ assert_undefined_in("\xA3\xDB", 'euc-jp')
+ assert_undefined_in("\xA3\xE0", 'euc-jp')
+ assert_undefined_in("\xA3\xFB", 'euc-jp')
+ assert_undefined_in("\xA4\xF4", 'euc-jp')
+ assert_undefined_in("\xA5\xF7", 'euc-jp')
+ assert_undefined_in("\xA6\xB9", 'euc-jp')
+ assert_undefined_in("\xA6\xC0", 'euc-jp')
+ assert_undefined_in("\xA6\xD9", 'euc-jp')
+ assert_undefined_in("\xA7\xC2", 'euc-jp')
+ assert_undefined_in("\xA7\xD0", 'euc-jp')
+ assert_undefined_in("\xA7\xF2", 'euc-jp')
+ assert_undefined_in("\xA8\xC1", 'euc-jp')
+ assert_undefined_in("\xCF\xD4", 'euc-jp')
+ assert_undefined_in("\xCF\xFE", 'euc-jp')
check_both_ways("\u6A97", "\xDD\xA1", 'euc-jp') # 檗
check_both_ways("\u6BEF", "\xDD\xDF", 'euc-jp') # 毯
check_both_ways("\u9EBE", "\xDD\xE0", 'euc-jp') # 麾
@@ -1449,7 +1439,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u71FC", "\xDF\xFE", 'euc-jp') # 燼
check_both_ways("\u71F9", "\xE0\xA1", 'euc-jp') # 燹
check_both_ways("\u73F1", "\xE0\xFE", 'euc-jp') # 珱
- assert_raise(Encoding::UndefinedConversionError) { "\xF4\xA7".encode("utf-8", 'euc-jp') }
+ assert_undefined_in("\xF4\xA7", 'euc-jp')
#check_both_ways("\u9ADC", "\xFC\xE3", 'euc-jp') # 髜 (IBM extended)
check_both_ways("\u677E\u672C\u884C\u5F18", "\xBE\xBE\xCB\xDC\xB9\xD4\xB9\xB0", 'euc-jp') # 松本行弘
@@ -1481,7 +1471,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u2127", "\xA3\xE0", 'euc-jis-2004') # ℧
check_both_ways("\u30A0", "\xA3\xFB", 'euc-jis-2004') # ゠
check_both_ways("\uFF54", "\xA3\xF4", 'euc-jis-2004') # t
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xF7".encode("utf-8", 'euc-jis-2004') }
+ assert_undefined_in("\xA5\xF7", 'euc-jis-2004')
check_both_ways("\u2664", "\xA6\xB9", 'euc-jis-2004') # ♤
check_both_ways("\u2663", "\xA6\xC0", 'euc-jis-2004') # ♣
check_both_ways("\u03C2", "\xA6\xD9", 'euc-jis-2004') # ς
@@ -1566,33 +1556,33 @@ class TestTranscode < Test::Unit::TestCase
end
def test_eucjp_sjis_undef
- assert_raise(Encoding::UndefinedConversionError) { "\x8e\xe0".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8e\xfe".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xa1\xa1".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xa1\xfe".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xfe\xa1".encode("Shift_JIS", "EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\x8f\xfe\xfe".encode("Shift_JIS", "EUC-JP") }
-
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x40".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x7e".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\x80".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xf0\xfc".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x40".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x7e".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\x80".encode("EUC-JP", "Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\xfc\xfc".encode("EUC-JP", "Shift_JIS") }
+ assert_undefined_conversion("\x8e\xe0", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8e\xfe", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xa1\xa1", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xa1\xfe", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xfe\xa1", "Shift_JIS", "EUC-JP")
+ assert_undefined_conversion("\x8f\xfe\xfe", "Shift_JIS", "EUC-JP")
+
+ assert_undefined_conversion("\xf0\x40", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\x7e", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\x80", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xf0\xfc", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x40", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x7e", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\x80", "EUC-JP", "Shift_JIS")
+ assert_undefined_conversion("\xfc\xfc", "EUC-JP", "Shift_JIS")
end
def test_iso_2022_jp
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b(A".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$(A".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$C".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x0e".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x80".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b$(Dd!\x1b(B".encode("utf-8", "iso-2022-jp") }
- assert_raise(Encoding::UndefinedConversionError) { "\u9299".encode("iso-2022-jp") }
- assert_raise(Encoding::UndefinedConversionError) { "\uff71\uff72\uff73\uff74\uff75".encode("iso-2022-jp") }
- assert_raise(Encoding::InvalidByteSequenceError) { "\x1b(I12345\x1b(B".encode("utf-8", "iso-2022-jp") }
+ assert_invalid_in("\x1b(A", "iso-2022-jp")
+ assert_invalid_in("\x1b$(A", "iso-2022-jp")
+ assert_invalid_in("\x1b$C", "iso-2022-jp")
+ assert_invalid_in("\x0e", "iso-2022-jp")
+ assert_invalid_in("\x80", "iso-2022-jp")
+ assert_invalid_in("\x1b$(Dd!\x1b(B", "iso-2022-jp")
+ assert_undefined_conversion("\u9299", "iso-2022-jp")
+ assert_undefined_conversion("\uff71\uff72\uff73\uff74\uff75", "iso-2022-jp")
+ assert_invalid_in("\x1b(I12345\x1b(B", "iso-2022-jp")
assert_equal("\xA1\xA1".force_encoding("euc-jp"),
"\e$B!!\e(B".encode("EUC-JP", "ISO-2022-JP"))
assert_equal("\e$B!!\e(B".force_encoding("ISO-2022-JP"),
@@ -1655,11 +1645,11 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u005C", "\e(J\x5C\e(B".encode("UTF-8", "ISO-2022-JP"))
assert_equal("\u005C", "\x5C".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
assert_equal("\u005C", "\e(J\x5C\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("Windows-31J") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("eucJP-ms") }
- assert_raise(Encoding::UndefinedConversionError) { "\u00A5".encode("CP51932") }
+ assert_undefined_conversion("\u00A5", "Shift_JIS")
+ assert_undefined_conversion("\u00A5", "Windows-31J")
+ assert_undefined_conversion("\u00A5", "EUC-JP")
+ assert_undefined_conversion("\u00A5", "eucJP-ms")
+ assert_undefined_conversion("\u00A5", "CP51932")
# FULLWIDTH REVERSE SOLIDUS
check_both_ways("\uFF3C", "\x81\x5F", "Shift_JIS")
@@ -1680,21 +1670,21 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("\u007E", "\e(J\x7E\e(B".encode("UTF-8", "ISO-2022-JP"))
assert_equal("\u007E", "\x7E".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
assert_equal("\u007E", "\e(J\x7E\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP"))
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("Shift_JIS") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("Windows-31J") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("EUC-JP") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("eucJP-ms") }
- assert_raise(Encoding::UndefinedConversionError) { "\u203E".encode("CP51932") }
+ assert_undefined_conversion("\u203E", "Shift_JIS")
+ assert_undefined_conversion("\u203E", "Windows-31J")
+ assert_undefined_conversion("\u203E", "EUC-JP")
+ assert_undefined_conversion("\u203E", "eucJP-ms")
+ assert_undefined_conversion("\u203E", "CP51932")
end
def test_gb2312
check_both_ways("\u3000", "\xA1\xA1", 'GB2312') # full-width space
check_both_ways("\u3013", "\xA1\xFE", 'GB2312') # 〓
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xB0", 'GB2312')
check_both_ways("\u2488", "\xA2\xB1", 'GB2312') # ⒈
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xE4", 'GB2312')
check_both_ways("\u3220", "\xA2\xE5", 'GB2312') # ㈠
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA2\xF0", 'GB2312')
check_both_ways("\u2160", "\xA2\xF1", 'GB2312') # Ⅰ
check_both_ways("\uFF01", "\xA3\xA1", 'GB2312') # !
check_both_ways("\uFFE3", "\xA3\xFE", 'GB2312') #  ̄
@@ -1705,9 +1695,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u0410", "\xA7\xA1", 'GB2312') # А
check_both_ways("\u0430", "\xA7\xD1", 'GB2312') # а
check_both_ways("\u0101", "\xA8\xA1", 'GB2312') # ā
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA8\xC4", 'GB2312')
check_both_ways("\u3105", "\xA8\xC5", 'GB2312') # ㄅ
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xA9\xA3", 'GB2312')
check_both_ways("\u2500", "\xA9\xA4", 'GB2312') # ─
check_both_ways("\u554A", "\xB0\xA1", 'GB2312') # 啊
check_both_ways("\u5265", "\xB0\xFE", 'GB2312') # 剥
@@ -1721,7 +1711,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u7384", "\xD0\xFE", 'GB2312') # 玄
check_both_ways("\u4F4F", "\xD7\xA1", 'GB2312') # 住
check_both_ways("\u5EA7", "\xD7\xF9", 'GB2312') # 座
- assert_raise(Encoding::UndefinedConversionError) { "\xD7\xFA".encode("utf-8", 'GB2312') }
+ assert_undefined_in("\xD7\xFA", 'GB2312')
check_both_ways("\u647A", "\xDF\xA1", 'GB2312') # 摺
check_both_ways("\u553C", "\xDF\xFE", 'GB2312') # 唼
check_both_ways("\u5537", "\xE0\xA1", 'GB2312') # 唷
@@ -1759,48 +1749,48 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u3000", "\xA1\xA1", 'GBK') # full-width space
check_both_ways("\u3001", "\xA1\xA2", 'GBK') # 、
check_both_ways("\u3013", "\xA1\xFE", 'GBK') # 〓
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xA0", 'GBK')
check_both_ways("\u2170", "\xA2\xA1", 'GBK') # ⅰ
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xB0", 'GBK')
check_both_ways("\u2488", "\xA2\xB1", 'GBK') # ⒈
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xE4", 'GBK')
check_both_ways("\u3220", "\xA2\xE5", 'GBK') # ㈠
- assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA2\xF0", 'GBK')
check_both_ways("\u2160", "\xA2\xF1", 'GBK') # Ⅰ
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA3\xA0", 'GBK')
check_both_ways("\uFF01", "\xA3\xA1", 'GBK') # !
check_both_ways("\uFFE3", "\xA3\xFE", 'GBK') #  ̄
- assert_raise(Encoding::UndefinedConversionError) { "\xA4\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA4\xA0", 'GBK')
check_both_ways("\u3041", "\xA4\xA1", 'GBK') # ぁ
- assert_raise(Encoding::UndefinedConversionError) { "\xA5\xA0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA5\xA0", 'GBK')
check_both_ways("\u30A1", "\xA5\xA1", 'GBK') # ァ
check_both_ways("\u0391", "\xA6\xA1", 'GBK') # Α
check_both_ways("\u03B1", "\xA6\xC1", 'GBK') # α
- assert_raise(Encoding::UndefinedConversionError) { "\xA6\xED".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA6\xED", 'GBK')
check_both_ways("\uFE3B", "\xA6\xEE", 'GBK') # ︻
check_both_ways("\u0410", "\xA7\xA1", 'GBK') # А
check_both_ways("\u0430", "\xA7\xD1", 'GBK') # а
check_both_ways("\u02CA", "\xA8\x40", 'GBK') # ˊ
check_both_ways("\u2587", "\xA8\x7E", 'GBK') # ▇
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\x96".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA8\x96", 'GBK')
check_both_ways("\u0101", "\xA8\xA1", 'GBK') # ā
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBC".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBF".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA8\xBC", 'GBK')
+ assert_undefined_in("\xA8\xBF", 'GBK')
+ assert_undefined_in("\xA8\xC4", 'GBK')
check_both_ways("\u3105", "\xA8\xC5", 'GBK') # ㄅ
check_both_ways("\u3021", "\xA9\x40", 'GBK') # 〡
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x58".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5B".encode("utf-8", 'GBK') }
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5D".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\x58", 'GBK')
+ assert_undefined_in("\xA9\x5B", 'GBK')
+ assert_undefined_in("\xA9\x5D", 'GBK')
check_both_ways("\u3007", "\xA9\x96", 'GBK') # 〇
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\xA3", 'GBK')
check_both_ways("\u2500", "\xA9\xA4", 'GBK') # ─
- assert_raise(Encoding::UndefinedConversionError) { "\xA9\xF0".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xA9\xF0", 'GBK')
check_both_ways("\u7588", "\xAF\x40", 'GBK') # 疈
check_both_ways("\u7607", "\xAF\x7E", 'GBK') # 瘇
check_both_ways("\u7608", "\xAF\x80", 'GBK') # 瘈
check_both_ways("\u7644", "\xAF\xA0", 'GBK') # 癄
- assert_raise(Encoding::UndefinedConversionError) { "\xAF\xA1".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xAF\xA1", 'GBK')
check_both_ways("\u7645", "\xB0\x40", 'GBK') # 癅
check_both_ways("\u769B", "\xB0\x7E", 'GBK') # 皛
check_both_ways("\u769C", "\xB0\x80", 'GBK') # 皜
@@ -1841,10 +1831,10 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F78", "\xFD\x7E", 'GBK') # 齸
check_both_ways("\u9F79", "\xFD\x80", 'GBK') # 齹
check_both_ways("\uF9F1", "\xFD\xA0", 'GBK') # 隣
- assert_raise(Encoding::UndefinedConversionError) { "\xFD\xA1".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xFD\xA1", 'GBK')
check_both_ways("\uFA0C", "\xFE\x40", 'GBK') # 兀
check_both_ways("\uFA29", "\xFE\x4F", 'GBK') # 﨩
- assert_raise(Encoding::UndefinedConversionError) { "\xFE\x50".encode("utf-8", 'GBK') }
+ assert_undefined_in("\xFE\x50", 'GBK')
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GBK') # 青山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GBK') # 神林義博
end
@@ -1880,48 +1870,48 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u3000", "\xA1\xA1", 'GB18030') # full-width space
check_both_ways("\u3001", "\xA1\xA2", 'GB18030') #
check_both_ways("\u3013", "\xA1\xFE", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xA0", 'GB18030')
check_both_ways("\u2170", "\xA2\xA1", 'GB18030') # ⅰ
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xB0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xB0", 'GB18030')
check_both_ways("\u2488", "\xA2\xB1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xE4".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xE4", 'GB18030')
check_both_ways("\u3220", "\xA2\xE5", 'GB18030') # ㈠
- #assert_raise(Encoding::UndefinedConversionError) { "\xA2\xF0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA2\xF0", 'GB18030')
check_both_ways("\u2160", "\xA2\xF1", 'GB18030') # Ⅰ
- #assert_raise(Encoding::UndefinedConversionError) { "\xA3\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA3\xA0", 'GB18030')
check_both_ways("\uFF01", "\xA3\xA1", 'GB18030') # E
check_both_ways("\uFFE3", "\xA3\xFE", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xA4\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA4\xA0", 'GB18030')
check_both_ways("\u3041", "\xA4\xA1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA5\xA0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA5\xA0", 'GB18030')
check_both_ways("\u30A1", "\xA5\xA1", 'GB18030') # ァ
check_both_ways("\u0391", "\xA6\xA1", 'GB18030') #
check_both_ways("\u03B1", "\xA6\xC1", 'GB18030') # α
- #assert_raise(Encoding::UndefinedConversionError) { "\xA6\xED".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA6\xED", 'GB18030')
check_both_ways("\uFE3B", "\xA6\xEE", 'GB18030') # E
check_both_ways("\u0410", "\xA7\xA1", 'GB18030') #
check_both_ways("\u0430", "\xA7\xD1", 'GB18030') # а
check_both_ways("\u02CA", "\xA8\x40", 'GB18030') #
check_both_ways("\u2587", "\xA8\x7E", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\x96".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA8\x96", 'GB18030')
check_both_ways("\u0101", "\xA8\xA1", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBC".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xBF".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA8\xC4".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA8\xBC", 'GB18030')
+ #assert_undefined_in("\xA8\xBF", 'GB18030')
+ #assert_undefined_in("\xA8\xC4", 'GB18030')
check_both_ways("\u3105", "\xA8\xC5", 'GB18030') #
check_both_ways("\u3021", "\xA9\x40", 'GB18030') # 〡
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x58".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5B".encode("utf-8", 'GB18030') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\x5D".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\x58", 'GB18030')
+ #assert_undefined_in("\xA9\x5B", 'GB18030')
+ #assert_undefined_in("\xA9\x5D", 'GB18030')
check_both_ways("\u3007", "\xA9\x96", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\xA3".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\xA3", 'GB18030')
check_both_ways("\u2500", "\xA9\xA4", 'GB18030') # ─
- #assert_raise(Encoding::UndefinedConversionError) { "\xA9\xF0".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xA9\xF0", 'GB18030')
check_both_ways("\u7588", "\xAF\x40", 'GB18030') #
check_both_ways("\u7607", "\xAF\x7E", 'GB18030') #
check_both_ways("\u7608", "\xAF\x80", 'GB18030') #
check_both_ways("\u7644", "\xAF\xA0", 'GB18030') #
- #assert_raise(Encoding::UndefinedConversionError) { "\xAF\xA1".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xAF\xA1", 'GB18030')
check_both_ways("\u7645", "\xB0\x40", 'GB18030') #
check_both_ways("\u769B", "\xB0\x7E", 'GB18030') #
check_both_ways("\u769C", "\xB0\x80", 'GB18030') #
@@ -1962,10 +1952,10 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F78", "\xFD\x7E", 'GB18030') # 齸
check_both_ways("\u9F79", "\xFD\x80", 'GB18030') # 齹
check_both_ways("\uF9F1", "\xFD\xA0", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xFD\xA1".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xFD\xA1", 'GB18030')
check_both_ways("\uFA0C", "\xFE\x40", 'GB18030') # E
check_both_ways("\uFA29", "\xFE\x4F", 'GB18030') # E
- #assert_raise(Encoding::UndefinedConversionError) { "\xFE\x50".encode("utf-8", 'GB18030') }
+ #assert_undefined_in("\xFE\x50", 'GB18030')
check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GB18030') # 青山学院大学
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GB18030') # 神林義
@@ -2020,7 +2010,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u310F", "\xA3\x7E", 'Big5') # ㄏ
check_both_ways("\u3110", "\xA3\xA1", 'Big5') # ㄐ
check_both_ways("\u02CB", "\xA3\xBF", 'Big5') # ˋ
- assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'Big5') }
+ assert_undefined_in("\xA3\xC0", 'Big5')
check_both_ways("\u6D6C", "\xAF\x40", 'Big5') # 浬
check_both_ways("\u7837", "\xAF\x7E", 'Big5') # 砷
check_both_ways("\u7825", "\xAF\xA1", 'Big5') # 砥
@@ -2039,9 +2029,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u77AC", "\xC0\xFE", 'Big5') # 瞬
check_both_ways("\u8B96", "\xC6\x40", 'Big5') # 讖
check_both_ways("\u7C72", "\xC6\x7E", 'Big5') # 籲
- #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5') }
+ #assert_undefined_in("\xC6\xA1", 'Big5')
+ #assert_undefined_in("\xC7\x40", 'Big5')
+ #assert_undefined_in("\xC8\x40", 'Big5')
check_both_ways("\u4E42", "\xC9\x40", 'Big5') # 乂
check_both_ways("\u6C15", "\xC9\x7E", 'Big5') # 氕
check_both_ways("\u6C36", "\xC9\xA1", 'Big5') # 氶
@@ -2074,7 +2064,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9F0A", "\xF9\x7E", 'Big5') # 鼊
check_both_ways("\u9FA4", "\xF9\xA1", 'Big5') # 龤
check_both_ways("\u9F98", "\xF9\xD5", 'Big5') # 龘
- #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5') }
+ #assert_undefined_in("\xF9\xD6", 'Big5')
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5') # 神林義博
end
@@ -2087,7 +2077,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u310F", "\xA3\x7E", 'Big5-HKSCS') # ㄏ
check_both_ways("\u3110", "\xA3\xA1", 'Big5-HKSCS') # ㄐ
check_both_ways("\u02CB", "\xA3\xBF", 'Big5-HKSCS') # ˋ
- #assert_raise(Encoding::UndefinedConversionError) { "\xA3\xC0".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xA3\xC0", 'Big5-HKSCS')
check_both_ways("\u6D6C", "\xAF\x40", 'Big5-HKSCS') # 浬
check_both_ways("\u7837", "\xAF\x7E", 'Big5-HKSCS') # 砷
check_both_ways("\u7825", "\xAF\xA1", 'Big5-HKSCS') # 砥
@@ -2106,9 +2096,9 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u77AC", "\xC0\xFE", 'Big5-HKSCS') # 瞬
check_both_ways("\u8B96", "\xC6\x40", 'Big5-HKSCS') # 讖
check_both_ways("\u7C72", "\xC6\x7E", 'Big5-HKSCS') # 籲
- #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5-HKSCS') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5-HKSCS') }
- #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xC6\xA1", 'Big5-HKSCS')
+ #assert_undefined_in("\xC7\x40", 'Big5-HKSCS')
+ #assert_undefined_in("\xC8\x40", 'Big5-HKSCS')
check_both_ways("\u4E42", "\xC9\x40", 'Big5-HKSCS') # 乂
check_both_ways("\u6C15", "\xC9\x7E", 'Big5-HKSCS') # 氕
check_both_ways("\u6C36", "\xC9\xA1", 'Big5-HKSCS') # 氶
@@ -2142,7 +2132,7 @@ class TestTranscode < Test::Unit::TestCase
check_both_ways("\u9FA4", "\xF9\xA1", 'Big5-HKSCS') # 龤
check_both_ways("\u9F98", "\xF9\xD5", 'Big5-HKSCS') # 龘
#check_both_ways("\u{23ED7}", "\x8E\x40", 'Big5-HKSCS') # 𣻗
- #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5-HKSCS') }
+ #assert_undefined_in("\xF9\xD6", 'Big5-HKSCS')
check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5-HKSCS') # 神林義博
end
@@ -2232,12 +2222,12 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback))
end
- bug8940 = '[ruby-core:57318] [Bug #8940]'
- %w[UTF-32 UTF-16].each do |enc|
- define_method("test_pseudo_encoding_inspect(#{enc})") do
- assert_normal_exit("'aaa'.encode('#{enc}').inspect", bug8940)
- assert_equal(4, 'aaa'.encode(enc).length, "should count in #{enc} with BOM")
- end
+ def test_pseudo_encoding_inspect
+ s = 'aaa'.encode "UTF-16"
+ assert_equal '"\xFE\xFF\x00\x61\x00\x61\x00\x61"', s.inspect
+
+ s = 'aaa'.encode "UTF-32"
+ assert_equal '"\x00\x00\xFE\xFF\x00\x00\x00\x61\x00\x00\x00\x61\x00\x00\x00\x61"', s.inspect
end
def test_encode_with_invalid_chars
@@ -2275,7 +2265,7 @@ class TestTranscode < Test::Unit::TestCase
result = th.map(&:value)
end
end
- expected = "\xa4\xa2".force_encoding(Encoding::EUC_JP)
+ expected = "\xa4\xa2".dup.force_encoding(Encoding::EUC_JP)
assert_equal([expected]*num, result, bug11277)
end;
end
@@ -2305,5 +2295,37 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("A\rB\r\rC", s.encode(usascii, newline: :cr))
assert_equal("A\r\nB\r\r\nC", s.encode(usascii, crlf_newline: true))
assert_equal("A\r\nB\r\r\nC", s.encode(usascii, newline: :crlf))
+ assert_equal("A\nB\nC", s.encode(usascii, lf_newline: true))
+ assert_equal("A\nB\nC", s.encode(usascii, newline: :lf))
+ end
+
+ private
+
+ def assert_conversion_both_ways_utf8(utf8, raw, encoding)
+ assert_conversion_both_ways(utf8, 'utf-8', raw, encoding)
+ end
+ alias check_both_ways assert_conversion_both_ways_utf8
+
+ def assert_conversion_both_ways(str1, enc1, str2, enc2)
+ message = str1.dump+str2.dump
+ assert_equal(str1.force_encoding(enc1), str2.encode(enc1, enc2), message)
+ assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1), message)
+ end
+ alias check_both_ways2 assert_conversion_both_ways
+
+ def assert_undefined_conversion(str, to, from = nil)
+ assert_raise(Encoding::UndefinedConversionError) { str.encode(to, from) }
+ end
+
+ def assert_undefined_in(str, encoding)
+ assert_undefined_conversion(str, 'utf-8', encoding)
+ end
+
+ def assert_invalid_byte_sequence(str, to, from = nil)
+ assert_raise(Encoding::InvalidByteSequenceError) { str.encode(to, from) }
+ end
+
+ def assert_invalid_in(str, encoding)
+ assert_invalid_byte_sequence(str, 'utf-8', encoding)
end
end
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index f8a7c68fd3..86f2e4bb84 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -33,6 +33,12 @@ class TestVariable < Test::Unit::TestCase
end
end
+ Athena = Gods.clone
+
+ def test_cloned_classes_copy_cvar_cache
+ assert_equal "Cronus", Athena.new.ruler0
+ end
+
def test_setting_class_variable_on_module_through_inheritance
mod = Module.new
mod.class_variable_set(:@@foo, 1)
@@ -43,6 +49,19 @@ class TestVariable < Test::Unit::TestCase
assert_equal(1, c.class_variable_get(:@@foo))
end
+ Zeus = Gods.clone
+
+ def test_cloned_allows_setting_cvar
+ Zeus.class_variable_set(:@@rule, "Athena")
+
+ god = Gods.new.ruler0
+ zeus = Zeus.new.ruler0
+
+ assert_equal "Cronus", god
+ assert_equal "Athena", zeus
+ assert_not_equal god.object_id, zeus.object_id
+ end
+
def test_singleton_class_included_class_variable
c = Class.new
c.extend(Olympians)
@@ -155,6 +174,21 @@ class TestVariable < Test::Unit::TestCase
end
end
+ def test_set_class_variable_on_frozen_object
+ set_cvar = EnvUtil.labeled_class("SetCVar")
+ set_cvar.class_eval "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ def self.set(val)
+ @@a = val # inline cache
+ end
+ end;
+ set_cvar.set(1) # fill write cache
+ set_cvar.freeze
+ assert_raise(FrozenError, "[Bug #19341]") do
+ set_cvar.set(2) # hit write cache, but should check frozen status
+ end
+ end
+
def test_variable
assert_instance_of(Integer, $$)
@@ -232,6 +266,84 @@ class TestVariable < Test::Unit::TestCase
assert_include(gv, :$12)
end
+ def prepare_klass_for_test_svar_with_ifunc
+ Class.new do
+ include Enumerable
+ def each(&b)
+ @b = b
+ end
+
+ def check1
+ check2.merge({check1: $1})
+ end
+
+ def check2
+ @b.call('foo')
+ {check2: $1}
+ end
+ end
+ end
+
+ def test_svar_with_ifunc
+ c = prepare_klass_for_test_svar_with_ifunc
+
+ expected_check1_result = {
+ check1: nil, check2: nil
+ }.freeze
+
+ obj = c.new
+ result = nil
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ assert_equal nil, result
+ assert_equal nil, $1
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # this frame was escaped so try it again
+ $~ = nil
+ obj = c.new
+ result = nil
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ assert_equal nil, result
+ assert_equal nil, $1
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # different context
+ result = nil
+ Fiber.new{
+ obj = c.new
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ }.resume # obj is created in antoher Fiber
+ assert_equal nil, result
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+
+ # different thread context
+ result = nil
+ Thread.new{
+ obj = c.new
+ obj.grep(/(f..)/){
+ result = $1
+ }
+ }.join # obj is created in another Thread
+
+ assert_equal nil, result
+ assert_equal expected_check1_result, obj.check1
+ assert_equal 'foo', result
+ assert_equal 'foo', $1
+ end
+
+
def test_global_variable_0
assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, [])
end
@@ -261,6 +373,12 @@ class TestVariable < Test::Unit::TestCase
v.instance_variable_set(:@foo, :bar)
end
+ assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do
+ v.instance_eval do
+ @a = 1
+ end
+ end
+
assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)}
assert_not_send([v, :instance_variable_defined?, :@foo])
@@ -295,6 +413,18 @@ class TestVariable < Test::Unit::TestCase
assert_equal(%i(v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11), v, bug11674)
end
+ def test_many_instance_variables
+ objects = [Object.new, Hash.new, Module.new]
+ objects.each do |obj|
+ 1000.times do |i|
+ obj.instance_variable_set("@var#{i}", i)
+ end
+ 1000.times do |i|
+ assert_equal(i, obj.instance_variable_get("@var#{i}"))
+ end
+ end
+ end
+
private
def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:)
local_variables
diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb
index 9c06ec14fb..c718f69316 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
require 'test/unit'
+return unless /darwin/ =~ RUBY_PLATFORM
+
class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args)
- omit if RUBY_PLATFORM !~ /darwin/
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/)
end
def test_darwin_invalid_call
- assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle::Function.new(Fiddle::Pointer.new(1), [], Fiddle::TYPE_VOID).call'])
+ assert_darwin_vm_dump_works(['-r-test-/fatal', '-eBug.invalid_call(1)'])
end
def test_darwin_segv_in_syscall
@@ -16,6 +17,6 @@ class TestVMDump < Test::Unit::TestCase
end
def test_darwin_invalid_access
- assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle.dlunwrap(100).inspect'])
+ assert_darwin_vm_dump_works(['-r-test-/fatal', '-eBug.invalid_access(100)'])
end
end
diff --git a/test/ruby/test_weakkeymap.rb b/test/ruby/test_weakkeymap.rb
new file mode 100644
index 0000000000..6b3ffbb81f
--- /dev/null
+++ b/test/ruby/test_weakkeymap.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestWeakKeyMap < Test::Unit::TestCase
+ def setup
+ @wm = ObjectSpace::WeakKeyMap.new
+ end
+
+ def test_map
+ x = Object.new
+ k = "foo"
+ @wm[k] = x
+ assert_same(x, @wm[k])
+ assert_same(x, @wm["FOO".downcase])
+ end
+
+ def test_aset_const
+ x = Object.new
+ assert_raise(ArgumentError) { @wm[true] = x }
+ assert_raise(ArgumentError) { @wm[false] = x }
+ assert_raise(ArgumentError) { @wm[nil] = x }
+ assert_raise(ArgumentError) { @wm[42] = x }
+ assert_raise(ArgumentError) { @wm[2**128] = x }
+ assert_raise(ArgumentError) { @wm[1.23] = x }
+ assert_raise(ArgumentError) { @wm[:foo] = x }
+ assert_raise(ArgumentError) { @wm["foo#{rand}".to_sym] = x }
+ end
+
+ def test_getkey
+ k = "foo"
+ @wm[k] = true
+ assert_same(k, @wm.getkey("FOO".downcase))
+ end
+
+ def test_key?
+ assert_weak_include(:key?, "foo")
+ assert_not_send([@wm, :key?, "bar"])
+ end
+
+ def test_delete
+ k1 = "foo"
+ x1 = Object.new
+ @wm[k1] = x1
+ assert_equal x1, @wm[k1]
+ assert_equal x1, @wm.delete(k1)
+ assert_nil @wm[k1]
+ assert_nil @wm.delete(k1)
+
+ fallback = @wm.delete(k1) do |key|
+ assert_equal k1, key
+ 42
+ end
+ assert_equal 42, fallback
+ end
+
+ def test_clear
+ k = "foo"
+ @wm[k] = true
+ assert @wm[k]
+ assert_same @wm, @wm.clear
+ refute @wm[k]
+ end
+
+ def test_inspect
+ x = Object.new
+ k = Object.new
+ @wm[k] = x
+ assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
+
+ 1000.times do |i|
+ @wm[i.to_s] = Object.new
+ @wm.inspect
+ end
+ assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
+ end
+
+ def test_no_hash_method
+ k = BasicObject.new
+ assert_raise NoMethodError do
+ @wm[k] = 42
+ end
+ end
+
+ def test_frozen_object
+ o = Object.new.freeze
+ assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
+ assert_nothing_raised(FrozenError) {@wm['foo'] = o}
+ end
+
+ def test_inconsistent_hash_key_memory_leak
+ assert_no_memory_leak [], '', <<~RUBY
+ class BadHash
+ def initialize
+ @hash = 0
+ end
+
+ def hash
+ @hash += 1
+ end
+ end
+
+ k = BadHash.new
+ wm = ObjectSpace::WeakKeyMap.new
+
+ 100_000.times do |i|
+ wm[k] = i
+ end
+ RUBY
+ end
+
+ def test_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_separately(%w(-robjspace), <<-'end;')
+ wm = ObjectSpace::WeakKeyMap.new
+ key = Object.new
+ val = Object.new
+ wm[key] = val
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(val, wm[key])
+ end;
+ end
+
+ def test_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { ObjectSpace::WeakKeyMap.new }
+ end
+
+ private
+
+ def assert_weak_include(m, k, n = 100)
+ if n > 0
+ return assert_weak_include(m, k, n-1)
+ end
+ 1.times do
+ x = Object.new
+ @wm[k] = x
+ assert_send([@wm, m, k])
+ assert_send([@wm, m, "FOO".downcase])
+ x = Object.new
+ end
+ end
+end
diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb
index 4c91661f86..97d7197dbb 100644
--- a/test/ruby/test_weakmap.rb
+++ b/test/ruby/test_weakmap.rb
@@ -82,6 +82,22 @@ class TestWeakMap < Test::Unit::TestCase
@wm.inspect)
end
+ def test_delete
+ k1 = "foo"
+ x1 = Object.new
+ @wm[k1] = x1
+ assert_equal x1, @wm[k1]
+ assert_equal x1, @wm.delete(k1)
+ assert_nil @wm[k1]
+ assert_nil @wm.delete(k1)
+
+ fallback = @wm.delete(k1) do |key|
+ assert_equal k1, key
+ 42
+ end
+ assert_equal 42, fallback
+ end
+
def test_each
m = __callee__[/test_(.*)/, 1]
x1 = Object.new
@@ -167,4 +183,79 @@ class TestWeakMap < Test::Unit::TestCase
assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
assert_nothing_raised(FrozenError) {@wm['foo'] = o}
end
+
+ def test_no_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #19398]", rss: true, limit: 1.5, timeout: 60)
+ begin;
+ 1_000_000.times do
+ ObjectSpace::WeakMap.new
+ end
+ end;
+ end
+
+ def test_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ # [Bug #19529]
+ obj = Object.new
+ 100.times do |i|
+ GC.compact
+ @wm[i] = obj
+ end
+
+ assert_separately([], <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+ obj = Object.new
+ 100.times do
+ wm[Object.new] = obj
+ GC.start
+ end
+ GC.compact
+ end;
+
+ assert_separately(%w(-robjspace), <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+ key = Object.new
+ val = Object.new
+ wm[key] = val
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(val, wm[key])
+ end;
+
+ assert_separately(["-W0"], <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+
+ ary = 10_000.times.map do
+ o = Object.new
+ wm[o] = 1
+ o
+ end
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ end;
+ end
+
+ def test_gc_compact_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ EnvUtil.under_gc_compact_stress { ObjectSpace::WeakMap.new }
+ end
+
+ def test_replaced_values_bug_19531
+ a = "A".dup
+ b = "B".dup
+
+ @wm[1] = a
+ @wm[1] = a
+ @wm[1] = a
+
+ @wm[1] = b
+ assert_equal b, @wm[1]
+
+ a = nil
+ GC.start
+
+ assert_equal b, @wm[1]
+ end
end
diff --git a/test/ruby/test_whileuntil.rb b/test/ruby/test_whileuntil.rb
index 121c44817d..ff6d29ac4a 100644
--- a/test/ruby/test_whileuntil.rb
+++ b/test/ruby/test_whileuntil.rb
@@ -73,6 +73,24 @@ class TestWhileuntil < Test::Unit::TestCase
}
end
+ def test_begin_while
+ i = 0
+ sum = 0
+ begin
+ i += 1
+ sum += i
+ end while i < 10
+ assert_equal([10, 55], [i, sum])
+
+ i = 0
+ sum = 0
+ (
+ i += 1
+ sum += i
+ ) while false
+ assert_equal([0, 0], [i, sum])
+ end
+
def test_until
i = 0
until i>4
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 1d93ac9b59..796787e355 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -1,21 +1,25 @@
# frozen_string_literal: true
#
# This set of tests can be run with:
-# make test-all TESTS='test/ruby/test_yjit.rb' RUN_OPTS="--yjit-call-threshold=1"
+# make test-all TESTS='test/ruby/test_yjit.rb'
require 'test/unit'
require 'envutil'
require 'tmpdir'
require_relative '../lib/jit_support'
-return unless defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+return unless JITSupport.yjit_supported?
+
+require 'stringio'
# Tests for YJIT with assertions on compilation and side exits
-# insipired by the MJIT tests in test/ruby/test_mjit.rb
+# insipired by the RJIT tests in test/ruby/test_rjit.rb
class TestYJIT < Test::Unit::TestCase
+ running_with_yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+
def test_yjit_in_ruby_description
assert_includes(RUBY_DESCRIPTION, '+YJIT')
- end
+ end if running_with_yjit
# Check that YJIT is in the version string
def test_yjit_in_version
@@ -27,22 +31,20 @@ class TestYJIT < Test::Unit::TestCase
%w(--version --disable=yjit --yjit),
%w(--version --disable=yjit --enable-yjit),
%w(--version --disable=yjit --enable=yjit),
- *([
- %w(--version --jit),
- %w(--version --disable-jit --jit),
- %w(--version --disable-jit --enable-jit),
- %w(--version --disable-jit --enable=jit),
- %w(--version --disable=jit --yjit),
- %w(--version --disable=jit --enable-jit),
- %w(--version --disable=jit --enable=jit),
- ] if JITSupport.yjit_supported?),
+ %w(--version --jit),
+ %w(--version --disable-jit --jit),
+ %w(--version --disable-jit --enable-jit),
+ %w(--version --disable-jit --enable=jit),
+ %w(--version --disable=jit --yjit),
+ %w(--version --disable=jit --enable-jit),
+ %w(--version --disable=jit --enable=jit),
].each do |version_args|
assert_in_out_err(version_args) do |stdout, stderr|
assert_equal(RUBY_DESCRIPTION, stdout.first)
assert_equal([], stderr)
end
end
- end
+ end if running_with_yjit
def test_command_line_switches
assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
@@ -51,8 +53,85 @@ class TestYJIT < Test::Unit::TestCase
#assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
end
+ def test_yjit_enable
+ args = []
+ args << "--disable=yjit" if RubyVM::YJIT.enabled?
+ assert_separately(args, <<~RUBY)
+ assert_false RubyVM::YJIT.enabled?
+ assert_false RUBY_DESCRIPTION.include?("+YJIT")
+
+ RubyVM::YJIT.enable
+
+ assert_true RubyVM::YJIT.enabled?
+ assert_true RUBY_DESCRIPTION.include?("+YJIT")
+ RUBY
+ end
+
+ def test_yjit_enable_stats_false
+ assert_separately(["--yjit-disable", "--yjit-stats"], <<~RUBY, ignore_stderr: true)
+ assert_false RubyVM::YJIT.enabled?
+ assert_nil RubyVM::YJIT.runtime_stats
+
+ RubyVM::YJIT.enable
+
+ assert_true RubyVM::YJIT.enabled?
+ assert_true RubyVM::YJIT.runtime_stats[:all_stats]
+ RUBY
+ end
+
+ def test_yjit_enable_stats_true
+ args = []
+ args << "--disable=yjit" if RubyVM::YJIT.enabled?
+ assert_separately(args, <<~RUBY, ignore_stderr: true)
+ assert_false RubyVM::YJIT.enabled?
+ assert_nil RubyVM::YJIT.runtime_stats
+
+ RubyVM::YJIT.enable(stats: true)
+
+ assert_true RubyVM::YJIT.enabled?
+ assert_true RubyVM::YJIT.runtime_stats[:all_stats]
+ RUBY
+ end
+
+ def test_yjit_enable_stats_quiet
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: true)']) do |_stdout, stderr, _status|
+ assert_not_empty stderr
+ end
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: :quiet)']) do |_stdout, stderr, _status|
+ assert_empty stderr
+ end
+ end
+
+ def test_yjit_enable_with_call_threshold
+ assert_separately(%w[--yjit-disable --yjit-call-threshold=1], <<~RUBY)
+ def not_compiled = nil
+ def will_compile = nil
+ def compiled_counts = RubyVM::YJIT.runtime_stats&.dig(:compiled_iseq_count)
+
+ not_compiled
+ assert_nil compiled_counts
+ assert_false RubyVM::YJIT.enabled?
+
+ RubyVM::YJIT.enable
+
+ will_compile
+ assert compiled_counts > 0
+ assert_true RubyVM::YJIT.enabled?
+ RUBY
+ end
+
+ def test_yjit_enable_with_monkey_patch
+ assert_separately(%w[--yjit-disable], <<~RUBY)
+ # This lets rb_method_entry_at(rb_mKernel, ...) return NULL
+ Kernel.prepend(Module.new)
+
+ # This must not crash with "undefined optimized method!"
+ RubyVM::YJIT.enable
+ RUBY
+ end
+
def test_yjit_stats_and_v_no_error
- _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-stats), '', true, true)
+ _stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true)
refute_includes(stderr, "NoMethodError")
end
@@ -64,7 +143,7 @@ class TestYJIT < Test::Unit::TestCase
end
assert_in_out_err([yjit_child_env, '-e puts RUBY_DESCRIPTION'], '', [RUBY_DESCRIPTION])
assert_in_out_err([yjit_child_env, '-e p RubyVM::YJIT.enabled?'], '', ['true'])
- end
+ end if running_with_yjit
def test_compile_setclassvariable
script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo'
@@ -241,10 +320,10 @@ class TestYJIT < Test::Unit::TestCase
end
def test_compile_opt_aset
- assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset])
- assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset])
- assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset])
- assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset])
+ assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset], frozen_string_literal: false)
+ assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset], frozen_string_literal: false)
end
def test_compile_attr_set
@@ -389,8 +468,31 @@ class TestYJIT < Test::Unit::TestCase
assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil)
end
- def test_compile_opt_getinlinecache
- assert_compiles(<<~RUBY, insns: %i[opt_getinlinecache], result: 123, call_threshold: 2)
+ def test_compile_getconstant
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: [], call_threshold: 1)
+ def get_argv(klass)
+ klass::ARGV
+ end
+
+ get_argv(Object)
+ RUBY
+ end
+
+ def test_compile_getconstant_with_sp_offset
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: 2, call_threshold: 1)
+ class Foo
+ Bar = 1
+ end
+
+ 2.times do
+ s = Foo # this opt_getconstant_path needs warmup, so 2.times is needed
+ Class.new(Foo).const_set(:Bar, s::Bar)
+ end
+ RUBY
+ end
+
+ def test_compile_opt_getconstant_path
+ assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2)
def get_foo
FOO
end
@@ -402,8 +504,8 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
- def test_opt_getinlinecache_slowpath
- assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
+ def test_opt_getconstant_path_slowpath
+ assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
class A
FOO = 42
class << self
@@ -430,6 +532,32 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_opt_getconstant_path_general
+ assert_compiles(<<~RUBY, result: [1, 1])
+ module Base
+ Const = 1
+ end
+
+ class Sub
+ def const
+ _const = nil # make a non-entry block for opt_getconstant_path
+ Const
+ end
+
+ def self.const_missing(n)
+ Base.const_get(n)
+ end
+ end
+
+
+ sub = Sub.new
+ result = []
+ result << sub.const # generate the general case
+ result << sub.const # const_missing does not invalidate the block
+ result
+ RUBY
+ end
+
def test_string_interpolation
assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2)
def make_str(foo, bar)
@@ -501,8 +629,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_getblockparamproxy
- # Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented
- assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 })
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {})
def foo &blk
p blk.call
p blk.call
@@ -513,6 +640,203 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_ifunc_getblockparamproxy
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {})
+ class Foo
+ include Enumerable
+
+ def each(&block)
+ block.call 1
+ block.call 2
+ block.call 3
+ end
+ end
+
+ foo = Foo.new
+ foo.map { _1 * 2 }
+ foo.map { _1 * 2 }
+ RUBY
+ end
+
+ def test_send_blockarg
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {})
+ def bar
+ end
+
+ def foo &blk
+ bar(&blk)
+ bar(&blk)
+ end
+
+ foo
+ foo
+
+ foo { }
+ foo { }
+ RUBY
+ end
+
+ def test_send_splat
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {})
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, *[1, 2, 3]) + "/P"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_multiarg
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/Q")
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, 1, 2, 3) + "/Q"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs
+ # For now, this side-exits when calls include keyword args
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A")
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, a: 1, b: 2) + "/A"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_in_receiver_only
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ send(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_with_underscores
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ __send__(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_splat
+ # For now, this side-exits when calling with a splat
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B")
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, **{ a: 1, b: 2 }) + "/B"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block
+ # Setlocal_wc_0 sometimes side-exits on write barrier
+ assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n")
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_calling
+ assert_compiles(<<~'RUBY', result: "1a2", exits: {})
+ def internal_method
+ out = yield
+ "1" + out + "2"
+ end
+
+ def jit_method
+ __send__(:internal_method) { "a" }
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_receiver
+ assert_compiles(<<~'RUBY', result: "b:n", exits: {})
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ send(:internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_sender
+ assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {})
+ def internal_method
+ "Y"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_multisend
+ assert_compiles(<<~'RUBY', result: "77")
+ def internal_method
+ "7"
+ end
+
+ def jit_method
+ send(:send, :internal_method) + send(:send, :send, :internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
def test_getivar_opt_plus
assert_no_exits(<<~RUBY)
class TheClass
@@ -552,6 +876,25 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_super_with_alias
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15)
+ class A
+ def foo = 1 + 2
+ end
+
+ module M
+ def foo = super() * 5
+ alias bar foo
+
+ def foo = :bad
+ end
+
+ A.prepend M
+
+ A.new.bar
+ RUBY
+ end
+
def test_super_cfunc
assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Hello")
class Gnirts < String
@@ -648,12 +991,662 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_int_equal
+ assert_compiles(<<~'RUBY', exits: :any, result: [true, false, true, false, true, false, true, false])
+ def eq(a, b)
+ a == b
+ end
+
+ def eqq(a, b)
+ a === b
+ end
+
+ big1 = 2 ** 65
+ big2 = big1 + 1
+ [eq(1, 1), eq(1, 2), eq(big1, big1), eq(big1, big2), eqq(1, 1), eqq(1, 2), eqq(big1, big1), eqq(big1, big2)]
+ RUBY
+ end
+
+ def test_opt_case_dispatch
+ assert_compiles(<<~'RUBY', exits: :any, result: [:"1", "2", 3])
+ def case_dispatch(val)
+ case val
+ when 1
+ :"#{val}"
+ when 2
+ "#{val}"
+ else
+ val
+ end
+ end
+
+ [case_dispatch(1), case_dispatch(2), case_dispatch(3)]
+ RUBY
+ end
+
+ def test_code_gc
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ return :not_paged unless add_pages(100) # prepare freeable pages
+ RubyVM::YJIT.code_gc # first code GC
+ return :not_compiled1 unless compiles { nil } # should be JITable again
+
+ RubyVM::YJIT.code_gc # second code GC
+ return :not_compiled2 unless compiles { nil } # should be JITable again
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 2
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_call
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+ return :broken_resume2 if fiber.resume != 0 # The code should be still callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_twice
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while Fiber.yield(nil.to_i); end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+
+ return :not_paged2 unless add_pages(300) # add some stuff to be freed
+ # Not calling fiber.resume here to test the case that the YJIT payload loses some
+ # information at the previous code GC. The payload should still be there, and
+ # thus we could know the fiber ISEQ is still on stack on this second code GC.
+ RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page
+
+ return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't)
+ return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine
+
+ return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber
+ RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack
+
+ return :not_paged4 unless add_pages(100) # check everything still works
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 3
+
+ :ok
+ RUBY
+ end
+
+ def test_disable_code_gc_with_many_iseqs
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: false)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_with_many_iseqs
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: true)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count == 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_with_auto_compact
+ assert_compiles((code_gc_helpers + <<~'RUBY'), exits: :any, result: :ok, mem_size: 1, code_gc: true)
+ # Test ISEQ moves in the middle of code GC
+ GC.auto_compact = true
+
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count == 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_partial_last_page
+ # call_threshold: 2 to avoid JIT-ing code_gc itself. If code_gc were JITed right before
+ # code_gc is called, the last page would be on stack.
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok, call_threshold: 2)
+ # Leave a bunch of off-stack pages
+ i = 0
+ while i < 1000
+ eval("x = proc { 1.to_s }; x.call; x.call")
+ i += 1
+ end
+
+ # On Linux, memory page size != code page size. So the last code page could be partially
+ # mapped. This call tests that assertions and other things work fine under the situation.
+ RubyVM::YJIT.code_gc
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_script_compiled # not ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @eval_counter = 0
+ def eval_script
+ eval('@eval_counter += 1')
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:script_compiled) do |t|
+ @trace_counter += 1
+ end
+
+ eval_script # JIT without TracePoint
+ trace.enable
+ eval_script # call with TracePoint
+ trace.disable
+
+ return :"eval_#{@eval_counter}" if @eval_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_b_call # ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @call_counter = 0
+ def block_call
+ 1.times { @call_counter += 1 }
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:b_call) do |t|
+ @trace_counter += 1
+ end
+
+ block_call # JIT without TracePoint
+ trace.enable
+ block_call # call with TracePoint
+ trace.disable
+
+ return :"call_#{@call_counter}" if @call_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_send_to_call
+ assert_compiles(<<~'RUBY', result: :ok)
+ ->{ :ok }.send(:call)
+ RUBY
+ end
+
+ def test_invokeblock_many_locals
+ # [Bug #19299]
+ assert_compiles(<<~'RUBY', result: :ok)
+ def foo
+ yield
+ end
+
+ foo do
+ a1=a2=a3=a4=a5=a6=a7=a8=a9=a10=a11=a12=a13=a14=a15=a16=a17=a18=a19=a20=a21=a22=a23=a24=a25=a26=a27=a28=a29=a30 = :ok
+ a30
+ end
+ RUBY
+ end
+
+ def test_bug_19316
+ n = 2 ** 64
+ # foo's extra param and the splats are relevant
+ assert_compiles(<<~'RUBY', result: [[n, -n], [n, -n]], exits: :any)
+ def foo(_, a, b, c)
+ [a & b, ~c]
+ end
+
+ n = 2 ** 64
+ args = [0, -n, n, n-1]
+
+ GC.stress = true
+ [foo(*args), foo(*args)]
+ RUBY
+ end
+
+ def test_gc_compact_cyclic_branch
+ assert_compiles(<<~'RUBY', result: 2)
+ def foo
+ i = 0
+ while i < 2
+ i += 1
+ end
+ i
+ end
+
+ foo
+ GC.compact
+ foo
+ RUBY
+ end
+
+ def test_invalidate_cyclic_branch
+ assert_compiles(<<~'RUBY', result: 2, exits: { opt_plus: 1 })
+ def foo
+ i = 0
+ while i < 2
+ i += 1
+ end
+ i
+ end
+
+ foo
+ class Integer
+ def +(x) = self - -x
+ end
+ foo
+ RUBY
+ end
+
+ def test_tracing_str_uplus
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1, definemethod: 1 })
+ def str_uplus
+ _ = 1
+ _ = 2
+ ret = [+"frfr", __LINE__]
+ _ = 3
+ _ = 4
+
+ ret
+ end
+
+ str_uplus
+ require 'objspace'
+ ObjectSpace.trace_object_allocations_start
+
+ str, expected_line = str_uplus
+ alloc_line = ObjectSpace.allocation_sourceline(str)
+
+ if expected_line == alloc_line
+ :ok
+ else
+ [expected_line, alloc_line]
+ end
+ RUBY
+ end
+
+ def test_str_uplus_subclass
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :subclass)
+ class S < String
+ def encoding
+ :subclass
+ end
+ end
+
+ def test(str)
+ (+str).encoding
+ end
+
+ test ""
+ test S.new
+ RUBY
+ end
+
+ def test_return_to_invalidated_block
+ # [Bug #19463]
+ assert_compiles(<<~RUBY, result: [1, 1, :ugokanai], exits: { definesmethod: 1, getlocal_WC_0: 1 })
+ klass = Class.new do
+ def self.lookup(hash, key) = hash[key]
+
+ def self.foo(a, b) = []
+
+ def self.test(hash, key)
+ [lookup(hash, key), key, "".freeze]
+ # 05 opt_send_without_block :lookup
+ # 07 getlocal_WC_0 :hash
+ # 09 opt_str_freeze ""
+ # 12 newarray 3
+ # 14 leave
+ #
+ # YJIT will put instructions (07..14) into a block.
+ # When String#freeze is redefined from within lookup(),
+ # the return address to the block is still on-stack. We rely
+ # on invalidation patching the code at the return address
+ # to service this situation correctly.
+ end
+ end
+
+ # get YJIT to compile test()
+ hash = { 1 => [] }
+ 31.times { klass.test(hash, 1) }
+
+ # inject invalidation into lookup()
+ evil_hash = Hash.new do |_, key|
+ class String
+ undef :freeze
+ def freeze = :ugokanai
+ end
+
+ key
+ end
+ klass.test(evil_hash, 1)
+ RUBY
+ end
+
+ def test_return_to_invalidated_frame
+ assert_compiles(code_gc_helpers + <<~RUBY, exits: :any, result: :ok)
+ def jump
+ [] # something not inlined
+ end
+
+ def entry(code_gc)
+ jit_exception(code_gc)
+ jump # faulty jump after code GC. #jit_exception should not come back.
+ end
+
+ def jit_exception(code_gc)
+ if code_gc
+ tap do
+ RubyVM::YJIT.code_gc
+ break # jit_exec_exception catches TAG_BREAK and re-enters JIT code
+ end
+ end
+ end
+
+ add_pages(100)
+ jump # Compile #jump in a non-first page
+ add_pages(100)
+ entry(false) # Compile #entry and its call to #jump in another page
+ entry(true) # Free #jump but not #entry
+
+ :ok
+ RUBY
+ end
+
+ def test_setivar_on_class
+ # Bug in https://github.com/ruby/ruby/pull/8152
+ assert_compiles(<<~RUBY, result: :ok)
+ class Base
+ def self.or_equal
+ @or_equal ||= Object.new
+ end
+ end
+
+ Base.or_equal # ensure compiled
+
+ class Child < Base
+ end
+
+ 200.times do |iv| # Need to be more than MAX_IVAR
+ Child.instance_variable_set("@_iv_\#{iv}", Object.new)
+ end
+
+ Child.or_equal
+ :ok
+ RUBY
+ end
+
+ def test_nested_send
+ #[Bug #19464]
+ assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { defineclass: 1 })
+ klass = Class.new do
+ class << self
+ alias_method :my_send, :send
+
+ def bar = :ok
+
+ def foo = bar
+ end
+ end
+
+ with_break = -> { break klass.send(:my_send, :foo) }
+ wo_break = -> { klass.send(:my_send, :foo) }
+
+ [with_break[], wo_break[]]
+ RUBY
+ end
+
+ def test_str_concat_encoding_mismatch
+ assert_compiles(<<~'RUBY', result: "incompatible character encodings: BINARY (ASCII-8BIT) and EUC-JP")
+ def bar(a, b)
+ a << b
+ rescue => e
+ e.message
+ end
+
+ def foo(a, b, h)
+ h[nil]
+ bar(a, b) # Ruby call, not set cfp->pc
+ end
+
+ h = Hash.new { nil }
+ foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h)
+ foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h)
+ RUBY
+ end
+
+ def test_io_reopen_clobbering_singleton_class
+ assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { definesmethod: 1, opt_eq: 2 })
+ def $stderr.to_i = :i
+
+ def test = $stderr.to_i
+
+ [test, test]
+ $stderr.reopen($stderr.dup)
+ [test, test].map { :ok unless _1 == :i }
+ RUBY
+ end
+
+ def test_opt_aref_with
+ assert_compiles(<<~RUBY, insns: %i[opt_aref_with], result: "bar", frozen_string_literal: false)
+ h = {"foo" => "bar"}
+
+ h["foo"]
+ RUBY
+ end
+
+ def test_proc_block_arg
+ assert_compiles(<<~RUBY, result: [:proc, :no_block])
+ def yield_if_given = block_given? ? yield : :no_block
+
+ def call(block_arg = nil) = yield_if_given(&block_arg)
+
+ [call(-> { :proc }), call]
+ RUBY
+ end
+
+ def test_opt_mult_overflow
+ assert_no_exits('0xfff_ffff_ffff_ffff * 0x10')
+ end
+
+ def test_disable_stats
+ assert_in_out_err(%w[--yjit-stats --yjit-disable])
+ end
+
+ def test_odd_calls_to_attr_reader
+ # Use of delegate from ActiveSupport use these kind of calls to getter methods.
+ assert_compiles(<<~RUBY, result: [1, 1, 1], no_send_fallbacks: true)
+ class One
+ attr_reader :one
+ def initialize
+ @one = 1
+ end
+ end
+
+ def calls(obj, empty, &)
+ [obj.one(*empty), obj.one(&), obj.one(*empty, &)]
+ end
+
+ calls(One.new, [])
+ RUBY
+ end
+
+ def test_kwrest
+ assert_compiles(<<~RUBY, result: true, no_send_fallbacks: true)
+ def req_rest(r1:, **kwrest) = [r1, kwrest]
+ def opt_rest(r1: 1.succ, **kwrest) = [r1, kwrest]
+ def kwrest(**kwrest) = kwrest
+
+ def calls
+ [
+ [1, {}] == req_rest(r1: 1),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r1: 1, r2: 2, r3: 3),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r1:1, r3: 3),
+ [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r3: 3, r1: 1),
+
+ [2, {}] == opt_rest,
+ [2, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3),
+ [0, { r2: 2, r3: 3 }] == opt_rest(r1: 0, r3: 3, r2: 2),
+ [0, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r1: 0, r3: 3),
+ [1, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3, r1: 1),
+
+ {} == kwrest,
+ { r0: 88, r1: 99 } == kwrest(r0: 88, r1: 99),
+ ]
+ end
+
+ calls.all?
+ RUBY
+ end
+
+ def test_send_polymorphic_method_name
+ assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true)
+ mid = "dynamic_mid_#{rand(100..200)}"
+ mid_dsym = mid.to_sym
+
+ define_method(mid) { :ok }
+
+ define_method(:send_site) { send(_1) }
+
+ [send_site(mid), send_site(mid_dsym)]
+ RUBY
+ end
+
+ def test_kw_splat_nil
+ assert_compiles(<<~'RUBY', result: %i[ok ok ok], no_send_fallbacks: true)
+ def id(x) = x
+ def kw_fw(arg, **) = id(arg, **)
+ def fw(...) = id(...)
+ def use = [fw(:ok), kw_fw(:ok), :ok.itself(**nil)]
+
+ use
+ RUBY
+ end
+
+ def test_empty_splat
+ assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true)
+ def foo = :ok
+ def fw(...) = foo(...)
+ def use(empty) = [foo(*empty), fw]
+
+ use([])
+ RUBY
+ end
+
+ def test_byteslice_sp_invalidation
+ assert_compiles(<<~'RUBY', result: 'ok', no_send_fallbacks: true)
+ "okng".itself.byteslice(0, 2)
+ RUBY
+ end
+
+ def test_leaf_builtin
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: 1)
+ before = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf]
+ return 1 if before.nil?
+
+ def entry = self.class
+ entry
+
+ after = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf]
+ after - before
+ RUBY
+ end
+
+ private
+
+ def code_gc_helpers
+ <<~'RUBY'
+ def compiles(&block)
+ failures = RubyVM::YJIT.runtime_stats[:compilation_failure]
+ block.call
+ failures == RubyVM::YJIT.runtime_stats[:compilation_failure]
+ end
+
+ def add_pages(num_jits)
+ pages = RubyVM::YJIT.runtime_stats[:live_page_count]
+ num_jits.times { return false unless eval('compiles { nil.to_i }') }
+ pages.nil? || pages < RubyVM::YJIT.runtime_stats[:live_page_count]
+ end
+ RUBY
+ end
+
def assert_no_exits(script)
assert_compiles(script)
end
ANY = Object.new
- def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil)
+ def assert_compiles(
+ test_script, insns: [],
+ call_threshold: 1,
+ stdout: nil,
+ exits: {},
+ result: ANY,
+ frozen_string_literal: nil,
+ mem_size: nil,
+ code_gc: false,
+ no_send_fallbacks: false
+ )
reset_stats = <<~RUBY
RubyVM::YJIT.runtime_stats
RubyVM::YJIT.reset_stats!
@@ -678,7 +1671,7 @@ class TestYJIT < Test::Unit::TestCase
RUBY
script = <<~RUBY
- #{"# frozen_string_literal: true" if frozen_string_literal}
+ #{"# frozen_string_literal: " + frozen_string_literal.to_s unless frozen_string_literal.nil?}
_test_proc = -> {
#{test_script}
}
@@ -687,7 +1680,7 @@ class TestYJIT < Test::Unit::TestCase
#{write_results}
RUBY
- status, out, err, stats = eval_with_jit(script, call_threshold: call_threshold)
+ status, out, err, stats = eval_with_jit(script, call_threshold:, mem_size:, code_gc:)
assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}"
@@ -708,12 +1701,28 @@ class TestYJIT < Test::Unit::TestCase
recorded_exits = recorded_exits.reject { |k, v| v == 0 }
recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym }
- if exits != :any && exits != recorded_exits
- flunk "Expected #{exits.empty? ? "no" : exits.inspect} exits" \
- ", but got\n#{recorded_exits.inspect}"
+ # Exits can be specified as a hash of stat-name symbol to integer for exact exits.
+ # or stat-name symbol to range if the number of side exits might vary (e.g. write
+ # barriers, cache misses.)
+ if exits != :any &&
+ exits != recorded_exits &&
+ (exits.keys != recorded_exits.keys || !exits.all? { |k, v| v === recorded_exits[k] }) # triple-equal checks range membership or integer equality
+ stats_reasons = StringIO.new
+ ::RubyVM::YJIT.send(:_print_stats_reasons, runtime_stats, stats_reasons)
+ stats_reasons = stats_reasons.string
+ flunk <<~EOM
+ Expected #{exits.empty? ? "no" : exits.inspect} exits, but got:
+ #{recorded_exits.inspect}
+ Reasons:
+ #{stats_reasons}
+ EOM
end
end
+ if no_send_fallbacks
+ assert_equal(0, runtime_stats[:num_send_dynamic], "Expected no use of fallback implementation")
+ end
+
# Only available when --enable-yjit=dev
if runtime_stats[:all_stats]
missed_insns = insns.dup
@@ -736,21 +1745,38 @@ class TestYJIT < Test::Unit::TestCase
s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
end
- def eval_with_jit(script, call_threshold: 1, timeout: 1000)
+ def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil, code_gc: false)
args = [
"--disable-gems",
"--yjit-call-threshold=#{call_threshold}",
- "--yjit-stats"
+ "--yjit-stats=quiet"
]
+ args << "--yjit-exec-mem-size=#{mem_size}" if mem_size
+ args << "--yjit-code-gc" if code_gc
args << "-e" << script_shell_encode(script)
stats_r, stats_w = IO.pipe
- out, err, status = EnvUtil.invoke_ruby(args,
- '', true, true, timeout: timeout, ios: {3 => stats_w}
- )
+ # Separate thread so we don't deadlock when
+ # the child ruby blocks writing the stats to fd 3
+ stats = ''
+ stats_reader = Thread.new do
+ stats = stats_r.read
+ stats_r.close
+ end
+ out, err, status = invoke_ruby(args, '', true, true, timeout: timeout, ios: { 3 => stats_w })
stats_w.close
- stats = stats_r.read
+ stats_reader.join(timeout)
stats = Marshal.load(stats) if !stats.empty?
- stats_r.close
[status, out, err, stats]
+ ensure
+ stats_reader&.kill
+ stats_reader&.join(timeout)
+ stats_r&.close
+ stats_w&.close
+ end
+
+ # A wrapper of EnvUtil.invoke_ruby that uses RbConfig.ruby instead of EnvUtil.ruby
+ # that might use a wrong Ruby depending on your environment.
+ def invoke_ruby(*args, **kwargs)
+ EnvUtil.invoke_ruby(*args, rubybin: RbConfig.ruby, **kwargs)
end
end
diff --git a/test/ruby/test_yjit_exit_locations.rb b/test/ruby/test_yjit_exit_locations.rb
new file mode 100644
index 0000000000..816ab457ce
--- /dev/null
+++ b/test/ruby/test_yjit_exit_locations.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS='test/ruby/test_yjit_exit_locations.rb'
+
+require 'test/unit'
+require 'envutil'
+require 'tmpdir'
+require_relative '../lib/jit_support'
+
+return unless JITSupport.yjit_supported?
+
+# Tests for YJIT with assertions on tracing exits
+# insipired by the RJIT tests in test/ruby/test_yjit.rb
+class TestYJITExitLocations < Test::Unit::TestCase
+ def test_yjit_trace_exits_and_v_no_error
+ _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-trace-exits), '', true, true)
+ refute_includes(stderr, "NoMethodError")
+ end
+
+ def test_trace_exits_expandarray_splat
+ assert_exit_locations('*arr = []')
+ end
+
+ private
+
+ def assert_exit_locations(test_script)
+ write_results = <<~RUBY
+ IO.open(3).write Marshal.dump({
+ enabled: RubyVM::YJIT.trace_exit_locations_enabled?,
+ exit_locations: RubyVM::YJIT.exit_locations
+ })
+ RUBY
+
+ script = <<~RUBY
+ _test_proc = -> {
+ #{test_script}
+ }
+ result = _test_proc.call
+ #{write_results}
+ RUBY
+
+ run_script = eval_with_jit(script)
+ # If stats are disabled when configuring, --yjit-exit-locations
+ # can't be true. We don't want to check if exit_locations hash
+ # is not empty because that could indicate a bug in the exit
+ # locations collection.
+ return unless run_script[:enabled]
+ exit_locations = run_script[:exit_locations]
+
+ assert exit_locations.key?(:raw)
+ assert exit_locations.key?(:frames)
+ assert exit_locations.key?(:lines)
+ assert exit_locations.key?(:samples)
+ assert exit_locations.key?(:missed_samples)
+ assert exit_locations.key?(:gc_samples)
+
+ assert_equal 0, exit_locations[:missed_samples]
+ assert_equal 0, exit_locations[:gc_samples]
+
+ assert_not_empty exit_locations[:raw]
+ assert_not_empty exit_locations[:frames]
+ assert_not_empty exit_locations[:lines]
+
+ exit_locations[:frames].each do |frame_id, frame|
+ assert frame.key?(:name)
+ assert frame.key?(:file)
+ assert frame.key?(:samples)
+ assert frame.key?(:total_samples)
+ assert frame.key?(:edges)
+ end
+ end
+
+ def eval_with_jit(script)
+ args = [
+ "--disable-gems",
+ "--yjit-call-threshold=1",
+ "--yjit-trace-exits"
+ ]
+ args << "-e" << script_shell_encode(script)
+ stats_r, stats_w = IO.pipe
+ _out, _err, _status = EnvUtil.invoke_ruby(args,
+ '', true, true, timeout: 1000, ios: { 3 => stats_w }
+ )
+ stats_w.close
+ stats = stats_r.read
+ stats = Marshal.load(stats) if !stats.empty?
+ stats_r.close
+ stats
+ end
+
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+end