summaryrefslogtreecommitdiff
path: root/test/ruby/test_string.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_string.rb')
-rw-r--r--test/ruby/test_string.rb1147
1 files changed, 964 insertions, 183 deletions
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index 95fbf63702..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
@@ -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
@@ -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,6 +671,27 @@ CODE
assert_equal(Encoding::UTF_8, "#{s}x".encoding)
end
+ 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]
+ 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")))
@@ -841,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 }
@@ -875,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")
@@ -887,6 +934,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 +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
@@ -1121,14 +1196,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 +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
@@ -1254,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/, {}))
@@ -1281,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
@@ -1301,45 +1398,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_index(2, S("abcdbce"), /b\Kc/)
- assert_equal(2, S("abcdbce").index(/b\Kc/))
+ assert_index(0, S("こんにちは"), ?こ)
+ assert_index(1, S("こんにちは"), S("んにち"))
+ assert_index(2, S("こんにちは"), /にち./)
+
+ assert_index(0, S("にんにちは"), ?に, 0)
+ assert_index(2, S("にんにちは"), ?に, 1)
+ assert_index(2, S("にんにちは"), ?に, 2)
+ assert_index(nil, S("にんにちは"), ?に, 3)
end
def test_insert
@@ -1486,34 +1592,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_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_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(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, 3))
- assert_nil(S("hello").rindex(S("z"), 3))
- assert_nil(S("hello").rindex(/z./, 3))
+ assert_rindex(nil, S("hello"), ?z)
+ assert_rindex(nil, S("hello"), S("z"))
+ assert_rindex(nil, S("hello"), /z./)
- assert_nil(S("hello").rindex(?z))
- assert_nil(S("hello").rindex(S("z")))
- assert_nil(S("hello").rindex(/z./))
+ 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)
+
+ m = assert_rindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_rindex(3, S("foo"), //, 4)
+
+ assert_rindex(5, S("abcdbce"), /b\Kc/)
- assert_equal(3, S("foo").rindex(//))
- assert_equal([3, 3], $~.offset(0))
+ assert_rindex(2, S("こんにちは"), ?に)
+ assert_rindex(6, S("にちは、こんにちは"), S("にちは"))
+ assert_rindex(6, S("にちは、こんにちは"), /にち./)
- assert_equal(5, S("abcdbce").rindex(/b\Kc/))
+ 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_rindex(0, S("こんにちは"), S("こんにちは"))
+ assert_rindex(nil, S("こんにち"), S("こんにちは"))
+ assert_rindex(nil, S("こ"), S("こんにちは"))
+ assert_rindex(nil, S(""), S("こんにちは"))
end
def test_rjust
@@ -1552,6 +1681,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)
@@ -1603,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")
@@ -1628,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")
@@ -1650,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")
@@ -1672,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")
@@ -1763,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
@@ -1770,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'
@@ -1859,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)
@@ -1900,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>')))
@@ -1965,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 &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
@@ -2222,6 +2496,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!
@@ -2246,6 +2522,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
@@ -2253,6 +2531,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!
@@ -2265,6 +2545,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
@@ -2317,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
@@ -2352,6 +2608,7 @@ CODE
assert_equal(S("HELLO"), S("HELLO").upcase)
assert_equal(S("ABC HELLO 123"), S("abc HELLO 123").upcase)
assert_equal(S("H\0""ELLO"), S("H\0""ello").upcase)
+ assert_equal(S("\u{10574}"), S("\u{1059B}").upcase)
end
def test_upcase!
@@ -2576,6 +2833,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"))
@@ -2629,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
@@ -2687,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?
@@ -2720,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
@@ -2762,6 +3056,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
@@ -2790,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)
@@ -2817,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)
@@ -2827,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)
@@ -2863,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")}
@@ -2892,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)
@@ -2916,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"
@@ -2943,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')}
@@ -2958,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)
@@ -2978,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)
@@ -2987,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"
@@ -3054,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
@@ -3122,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)
@@ -3189,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?)
@@ -3197,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
@@ -3235,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)
@@ -3254,6 +3608,433 @@ CODE
assert_not_predicate(data, :valid_encoding?)
assert_predicate(data[100..-1], :valid_encoding?)
end
+
+ def test_byteindex
+ assert_byteindex(0, S("hello"), ?h)
+ assert_byteindex(1, S("hello"), S("ell"))
+ assert_byteindex(2, S("hello"), /ll./)
+
+ assert_byteindex(3, S("hello"), ?l, 3)
+ assert_byteindex(3, S("hello"), S("l"), 3)
+ assert_byteindex(3, S("hello"), /l./, 3)
+
+ assert_byteindex(nil, S("hello"), ?z, 3)
+ assert_byteindex(nil, S("hello"), S("z"), 3)
+ assert_byteindex(nil, S("hello"), /z./, 3)
+
+ assert_byteindex(nil, S("hello"), ?z)
+ assert_byteindex(nil, S("hello"), S("z"))
+ assert_byteindex(nil, S("hello"), /z./)
+
+ 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_byteindex(nil, s, S("y"))
+ assert_byteindex(4 * 1000, s, S("x"))
+ s << "yx"
+ assert_byteindex(4 * 1000, s, S("x"))
+ assert_byteindex(4 * 1000, s, S("xyx"))
+
+ o = Object.new
+ def o.to_str; "bar"; end
+ assert_byteindex(3, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").byteindex(Object.new) }
+
+ assert_byteindex(nil, S("foo"), //, -100)
+ assert_byteindex(nil, S("foo"), //, -4)
+
+ assert_byteindex(2, S("abcdbce"), /b\Kc/)
+
+ assert_byteindex(0, S("こんにちは"), ?こ)
+ assert_byteindex(3, S("こんにちは"), S("んにち"))
+ assert_byteindex(6, S("こんにちは"), /にち./)
+
+ assert_byteindex(0, S("にんにちは"), ?に, 0)
+ assert_raise(IndexError) { S("にんにちは").byteindex(?に, 1) }
+ assert_raise(IndexError) { S("にんにちは").byteindex(?に, 5) }
+ 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_byterindex(3, S("hello"), ?l)
+ assert_byterindex(6, S("ell, hello"), S("ell"))
+ assert_byterindex(7, S("ell, hello"), /ll./)
+
+ 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_byterindex(nil, S("hello"), ?z, 3)
+ assert_byterindex(nil, S("hello"), S("z"), 3)
+ assert_byterindex(nil, S("hello"), /z./, 3)
+
+ assert_byterindex(nil, S("hello"), ?z)
+ assert_byterindex(nil, S("hello"), S("z"))
+ assert_byterindex(nil, S("hello"), /z./)
+
+ 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_byterindex(6, S("foobarbarbaz"), o)
+ assert_raise(TypeError) { S("foo").byterindex(Object.new) }
+
+ assert_byterindex(nil, S("foo"), //, -100)
+
+ m = assert_byterindex(3, S("foo"), //)
+ assert_equal([3, 3], m.offset(0))
+ assert_byterindex(3, S("foo"), //, 4)
+
+ assert_byterindex(5, S("abcdbce"), /b\Kc/)
+
+ 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_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_byterindex(0, S("にちは、こんにちは"), S("にちは"), 0)
+
+ 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, "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_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)
+ 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
+
+ def assert_undump(str, *rest)
+ assert_equal(str, str.dump.undump, *rest)
+ end
end
class TestString2 < TestString