diff options
Diffstat (limited to 'test/ruby/test_string.rb')
| -rw-r--r-- | test/ruby/test_string.rb | 714 |
1 files changed, 581 insertions, 133 deletions
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 6cc958332f..2458d38ef4 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 @@ -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 @@ -162,6 +164,15 @@ CODE assert_raise(ArgumentError) { "foo"[] } end + def test_AREF_underflow + require "rbconfig/sizeof" + assert_equal(nil, S("\u{3042 3044 3046}")[RbConfig::LIMITS["LONG_MIN"], 1]) + end + + def test_AREF_invalid_encoding + assert_equal(S("\x80"), S("A"*39+"\x80")[-1, 1]) + end + def test_ASET # '[]=' s = S("FooBar") s[0] = S('A') @@ -199,24 +210,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 +306,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 +595,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,8 +671,8 @@ 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 + def test_string_interpolations_across_heaps_get_embedded + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 require 'objspace' base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] @@ -862,6 +872,10 @@ CODE assert_equal('\#', S('"\\\\#"').undump) assert_equal('\#{', S('"\\\\\#{"').undump) + assert_undump("\0\u{ABCD}") + assert_undump(S('"\x00\u3042"'.force_encoding("SJIS"))) + assert_undump(S('"\u3042\x7E"'.force_encoding("SJIS"))) + assert_raise(RuntimeError) { S('\u3042').undump } assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump } assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump } @@ -896,6 +910,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") @@ -1095,6 +1121,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 @@ -1249,6 +1291,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 @@ -1292,6 +1339,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/, {})) @@ -1319,6 +1375,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 @@ -1622,6 +1681,11 @@ 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 @@ -1682,20 +1746,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") @@ -1707,17 +1762,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") @@ -1729,17 +1776,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") @@ -1751,17 +1790,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") @@ -1842,6 +1873,13 @@ CODE result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")} assert_equal(["AAA"]*4, result) + + s = S("abc ") * 20 + assert_raise(RuntimeError) { + 10.times do + s.split {s.prepend("xxx" * 100)} + end + } ensure EnvUtil.suppress_warning {$; = fs} end @@ -1849,9 +1887,24 @@ CODE def test_fs return unless @cls == String - assert_raise_with_message(TypeError, /\$;/) { - $; = [] - } + begin + fs = $; + assert_deprecated_warning(/non-nil '\$;'/) {$; = "x"} + assert_raise_with_message(TypeError, /\$;/) {$; = []} + ensure + EnvUtil.suppress_warning {$; = fs} + end + name = "\u{5206 5217}" + assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") + do; + alias $#{name} $; + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } + assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } + end; + end + + def test_fs_gc + return unless @cls == String assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}") bug = '[ruby-core:79582] $; must not be GCed' @@ -1938,16 +1991,37 @@ 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/)) assert_nil($&) end + def test_start_with_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20653]", rss: true) + regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001) + str = "a" * 1_000_000 + "x" + + code = proc do + str.start_with?(regex) + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + def test_strip assert_equal(S("x"), S(" x ").strip) assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip) @@ -1979,6 +2053,117 @@ CODE assert_equal(S("x") ,a) end + def test_strip_with_selectors + assert_equal(S("abc"), S("---abc+++").strip("-+")) + assert_equal(S("abc"), S("+++abc---").strip("-+")) + assert_equal(S("abc"), S("+-+abc-+-").strip("-+")) + assert_equal(S(""), S("---+++").strip("-+")) + assert_equal(S("abc "), S("---abc ").strip("-")) + assert_equal(S(" abc"), S(" abc+++").strip("+")) + + # Test with multibyte characters + assert_equal(S("abc"), S("あああabcいいい").strip("あい")) + assert_equal(S("abc"), S("いいいabcあああ").strip("あい")) + + # Test with NUL characters + assert_equal(S("abc\0"), S("---abc\0--").strip("-")) + assert_equal(S("\0abc"), S("--\0abc---").strip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").strip("-+")) + assert_equal(S("abc"), S("abc").strip("")) + + # Test with range + assert_equal(S("abc"), S("012abc345").strip("0-9")) + assert_equal(S("abc"), S("012abc345").strip("^a-z")) + + # Test with multiple selectors + assert_equal(S("4abc56"), S("01234abc56789").strip("0-9", "^4-6")) + end + + def test_strip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("+++abc---") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("abc") + assert_nil(a.strip!("-+")) + assert_equal(S("abc"), a) + + # Test with multibyte characters + a = S("あああabcいいい") + assert_equal(S("abc"), a.strip!("あい")) + assert_equal(S("abc"), a) + end + + def test_lstrip_with_selectors + assert_equal(S("abc+++"), S("---abc+++").lstrip("-")) + assert_equal(S("abc---"), S("+++abc---").lstrip("+")) + assert_equal(S("abc"), S("---abc").lstrip("-")) + assert_equal(S(""), S("---").lstrip("-")) + + # Test with multibyte characters + assert_equal(S("abcいいい"), S("あああabcいいい").lstrip("あ")) + + # Test with NUL characters + assert_equal(S("\0abc+++"), S("--\0abc+++").lstrip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").lstrip("-")) + + # Test with range + assert_equal(S("abc345"), S("012abc345").lstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("4abc56789"), S("01234abc56789").lstrip("0-9", "^4-6")) + end + + def test_lstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc+++"), a.lstrip!("-")) + assert_equal(S("abc+++"), a) + + a = S("abc") + assert_nil(a.lstrip!("-")) + assert_equal(S("abc"), a) + end + + def test_rstrip_with_selectors + assert_equal(S("---abc"), S("---abc+++").rstrip("+")) + assert_equal(S("+++abc"), S("+++abc---").rstrip("-")) + assert_equal(S("abc"), S("abc+++").rstrip("+")) + assert_equal(S(""), S("+++").rstrip("+")) + + # Test with multibyte characters + assert_equal(S("あああabc"), S("あああabcいいい").rstrip("い")) + + # Test with NUL characters + assert_equal(S("---abc\0"), S("---abc\0++").rstrip("+")) + + # Test without modification + assert_equal(S("abc"), S("abc").rstrip("-")) + + # Test with range + assert_equal(S("012abc"), S("012abc345").rstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("01234abc56"), S("01234abc56789").rstrip("0-9", "^4-6")) + end + + def test_rstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("---abc"), a.rstrip!("+")) + assert_equal(S("---abc"), a) + + a = S("abc") + assert_nil(a.rstrip!("+")) + assert_equal(S("abc"), a) + end + def test_sub assert_equal(S("h*llo"), S("hello").sub(/[aeiou]/, S('*'))) assert_equal(S("h<e>llo"), S("hello").sub(/([aeiou])/, S('<\1>'))) @@ -2044,6 +2229,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 & yyy")) + assert_equal("amp", m["foo"]) + + assert_equal("aaa [amp] yyy", S("aaa & yyy").sub(/&(?<foo>.*?);/, S('[\k<foo>]'))) + end + end + def test_sub! a = S("hello") b = a.dup @@ -2404,33 +2599,7 @@ CODE assert_equal([0xa9, 0x42, 0x2260], S("\xc2\xa9B\xe2\x89\xa0").unpack(S("U*"))) -=begin - skipping "Not tested: - D,d & double-precision float, native format\\ - E & double-precision float, little-endian byte order\\ - e & single-precision float, little-endian byte order\\ - F,f & single-precision float, native format\\ - G & double-precision float, network (big-endian) byte order\\ - g & single-precision float, network (big-endian) byte order\\ - I & unsigned integer\\ - i & integer\\ - L & unsigned long\\ - l & long\\ - - m & string encoded in base64 (uuencoded)\\ - N & long, network (big-endian) byte order\\ - n & short, network (big-endian) byte-order\\ - P & pointer to a structure (fixed-length string)\\ - p & pointer to a null-terminated string\\ - S & unsigned short\\ - s & short\\ - V & long, little-endian byte order\\ - v & short, little-endian byte order\\ - X & back up a byte\\ - x & null byte\\ - Z & ASCII string (null padded, count is width)\\ -" -=end + # more comprehensive tests are in test_pack.rb end def test_upcase @@ -2722,14 +2891,21 @@ CODE assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/)) end - def test_fs_setter + def test_rs return unless @cls == String - assert_raise(TypeError) { $/ = 1 } + begin + rs = $/ + assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" } + assert_raise(TypeError) { $/ = 1 } + ensure + EnvUtil.suppress_warning { $/ = rs } + end name = "\u{5206 884c}" assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") do; alias $#{name} $/ + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } end; end @@ -2780,27 +2956,45 @@ CODE assert_equal("\u3042", ("\u3042" * 100)[-1]) end -=begin def test_compare_different_encoding_string s1 = S("\xff".force_encoding("UTF-8")) s2 = S("\xff".force_encoding("ISO-2022-JP")) assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort) + + s3 = S("あ".force_encoding("UTF-16LE")) + s4 = S("a".force_encoding("IBM437")) + assert_equal([-1, 1], [s3 <=> s4, s4 <=> s3].sort) end -=end def test_casecmp assert_equal(0, S("FoO").casecmp("fOO")) assert_equal(1, S("FoO").casecmp("BaR")) + assert_equal(-1, S("foo").casecmp("FOOBAR")) assert_equal(-1, S("baR").casecmp("FoO")) assert_equal(1, S("\u3042B").casecmp("\u3042a")) assert_equal(-1, S("foo").casecmp("foo\0")) + assert_equal(1, S("FOOBAR").casecmp("foo")) + assert_equal(0, S("foo\0bar").casecmp("FOO\0BAR")) assert_nil(S("foo").casecmp(:foo)) assert_nil(S("foo").casecmp(Object.new)) + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + o = Object.new def o.to_str; "fOO"; end assert_equal(0, S("FoO").casecmp(o)) + + assert_equal(0, S("#" * 128 + "A" * 256 + "b").casecmp("#" * 128 + "a" * 256 + "B")) + assert_equal(0, S("a" * 256 + "B").casecmp("A" * 256 + "b")) + + assert_equal(-1, S("@").casecmp("`")) + assert_equal(0, S("hello\u00E9X").casecmp("HELLO\u00E9x")) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp(s2)) end def test_casecmp? @@ -2813,9 +3007,16 @@ CODE assert_nil(S("foo").casecmp?(:foo)) assert_nil(S("foo").casecmp?(Object.new)) + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + o = Object.new def o.to_str; "fOO"; end assert_equal(true, S("FoO").casecmp?(o)) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp?(s2)) end def test_upcase2 @@ -2888,14 +3089,15 @@ CODE s5 = S("\u0000\u3042") assert_equal("\u3042", s5.lstrip!) assert_equal("\u3042", s5) - 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) @@ -2915,8 +3117,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) @@ -2925,23 +3128,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) @@ -2961,23 +3172,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")} @@ -2990,11 +3210,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) @@ -3014,23 +3236,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" @@ -3041,11 +3268,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')} @@ -3056,7 +3285,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) @@ -3076,8 +3307,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) @@ -3085,18 +3317,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" @@ -3152,18 +3388,12 @@ CODE assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect) assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081) end - begin - verbose, $VERBOSE = $VERBOSE, nil - ext = Encoding.default_external - Encoding.default_external = "us-ascii" - $VERBOSE = verbose + + EnvUtil.with_default_external(Encoding::US_ASCII) do i = S("abc\"\\".force_encoding("utf-8")).inspect - ensure - $VERBOSE = nil - Encoding.default_external = ext - $VERBOSE = verbose + + assert_equal('"abc\\"\\\\"', i, bug4081) end - assert_equal('"abc\\"\\\\"', i, bug4081) end def test_dummy_inspect @@ -3220,6 +3450,11 @@ CODE assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28)) + assert_equal("\xE3", S("こんにちは").byteslice(0)) + assert_equal("こんにちは", S("こんにちは").byteslice(0, 15)) + assert_equal("こ", S("こんにちは").byteslice(0, 3)) + assert_equal("は", S("こんにちは").byteslice(12, 15)) + bug7954 = '[ruby-dev:47108]' assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954) assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954) @@ -3287,7 +3522,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?) @@ -3295,8 +3534,14 @@ 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_dedup_in_place + dynamic = "this string is unique and frozen #{rand}".freeze + assert_same dynamic, -dynamic + assert_same dynamic, -dynamic.dup end def test_uminus_frozen @@ -3333,6 +3578,17 @@ CODE assert_equal(false, str.frozen?) end + def test_uminus_no_embed_gc + pad = "a"*2048 + File.open(IO::NULL, "w") do |dev_null| + ("aa".."zz").each do |c| + fstr = -(c + pad).freeze + dev_null.write(fstr) + end + end + GC.start + end + def test_ord assert_equal(97, S("a").ord) assert_equal(97, S("abc").ord) @@ -3541,6 +3797,194 @@ CODE assert_bytesplice_raise(ArgumentError, S("hello"), 0..-1, "bye", 0, 3) end + def test_append_bytes_into_binary + buf = S("".b) + assert_equal Encoding::BINARY, buf.encoding + + buf.append_as_bytes(S("hello")) + assert_equal "hello".b, buf + assert_equal Encoding::BINARY, buf.encoding + + buf.append_as_bytes(S("こんにちは")) + assert_equal S("helloこんにちは".b), buf + assert_equal Encoding::BINARY, buf.encoding + end + + def test_append_bytes_into_utf8 + buf = S("") + assert_equal Encoding::UTF_8, buf.encoding + + buf.append_as_bytes(S("hello")) + assert_equal S("hello"), buf + assert_equal Encoding::UTF_8, buf.encoding + assert_predicate buf, :ascii_only? + assert_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("こんにちは")) + assert_equal S("helloこんにちは"), buf + assert_equal Encoding::UTF_8, buf.encoding + refute_predicate buf, :ascii_only? + assert_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("\xE2\x82".b)) + assert_equal S("helloこんにちは\xE2\x82"), buf + assert_equal Encoding::UTF_8, buf.encoding + refute_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("\xAC".b)) + assert_equal S("helloこんにちは€"), buf + assert_equal Encoding::UTF_8, buf.encoding + assert_predicate buf, :valid_encoding? + end + + def test_append_bytes_into_utf32 + buf = S("abc".encode(Encoding::UTF_32LE)) + assert_equal Encoding::UTF_32LE, buf.encoding + + buf.append_as_bytes("def") + assert_equal Encoding::UTF_32LE, buf.encoding + refute_predicate buf, :valid_encoding? + end + + def test_chilled_string + chilled_string = eval('"chilled"') + + assert_not_predicate chilled_string, :frozen? + + assert_not_predicate chilled_string.dup, :frozen? + assert_not_predicate chilled_string.clone, :frozen? + + # @+ treat the original string as frozen + assert_not_predicate(+chilled_string, :frozen?) + assert_not_same chilled_string, +chilled_string + + # @- treat 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 + + def test_encode_fallback_raise_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { raise MyError } + RUBY + "proc" => <<~RUBY, + fallback = proc { raise MyError } + RUBY + "method" => <<~RUBY, + def my_method(_str) = raise MyError + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = raise MyError + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue MyError + end + RUBY + end + end + + def test_encode_fallback_too_big_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { "\\uffee" } + RUBY + "proc" => <<~RUBY, + fallback = proc { "\\uffee" } + RUBY + "method" => <<~RUBY, + def my_method(_str) = "\\uffee" + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = "\\uffee" + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue ArgumentError + end + RUBY + end + end + + def test_encode_fallback_not_string_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { Object.new } + RUBY + "proc" => <<~RUBY, + fallback = proc { Object.new } + RUBY + "method" => <<~RUBY, + def my_method(_str) = Object.new + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = Object.new + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue TypeError + end + RUBY + end + end + private def assert_bytesplice_result(expected, s, *args) @@ -3587,6 +4031,10 @@ CODE def assert_byterindex(expected, string, match, *rest) assert_index_like(:byterindex, expected, string, match, *rest) end + + def assert_undump(str, *rest) + assert_equal(str, str.dump.undump, *rest) + end end class TestString2 < TestString |
