Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions mrbgems/mruby-io/test/io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,26 @@ def to_s; @a.replace(Array.new(64, 0)); "x"; end
io.close
end

assert('IO#sysread into a shared buffer') do
# `sysread` resizes the buffer to the requested length and then reads into
# it from offset 0, and it is one of the callers of that resize which does
# not prepare the buffer for modification itself. Were the resize to leave
# the buffer shared, the read would write over bytes another string still
# holds. `BasicSocket#recv` resizes the same way and is not covered here.
fd = IO.sysopen $mrbtest_io_rfname
io = IO.new(fd)
begin
a = "a" * 100 + "z" * 100
a.bytesplice(100, 100, "") # shorten it, to leave spare capacity behind
buf = a.dup # shares that buffer, and ends where `a` ends
io.sysread(150, buf) # a different length, so the buffer grows first
assert_equal $mrbtest_io_msg, buf
assert_equal "a" * 100, a
ensure
io.close
end
end

assert('IO.sysopen, IO#syswrite') do
fd = IO.sysopen $mrbtest_io_wfname, "w"
io = IO.new(fd, "w")
Expand Down
54 changes: 54 additions & 0 deletions mrbgems/mruby-string-ext/test/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,60 @@ def assert_upto(exp, receiver, *args)
assert_equal "l" * 100 + "m" * 100, n
end

assert('String growth on a shared buffer') do
# An append writes only above the length every other sharer of the buffer
# can see, which is what lets `String#<<` above stay in the buffer. Growing
# a string any other way has no such guarantee, and a growth that kept the
# buffer shared would be seen through the other string. Every string here is
# appended to before it is shared, since one with no spare capacity has
# nothing to be grown into in place anyway.

# `insert` at the front memmoves from offset 0, over the whole slice.
a = "a" * 100
a << "z" * 100
a_slice = a[0, 150]
a.insert(0, "1234")
assert_equal "1234" + "a" * 100 + "z" * 100, a
assert_equal "a" * 100 + "z" * 50, a_slice

# `prepend`, the same shape as `insert` at 0.
c = "c" * 100
c << "x" * 100
c_slice = c[0, 150]
c.prepend("1234")
assert_equal "1234" + "c" * 100 + "x" * 100, c
assert_equal "c" * 100 + "x" * 50, c_slice

# The rest are contract rather than detection: what they write happens to
# land above the slice, or they take the buffer for reasons of their own.

# `insert` at the end writes above the slice, and moves only the length and
# the terminator of the buffer the slice reads.
b = "b" * 100
b << "y" * 100
b_slice = b[0, 150]
b.insert(-1, "1234")
assert_equal "b" * 100 + "y" * 100 + "1234", b
assert_equal "b" * 100 + "y" * 50, b_slice

# `succ!` writes a terminator over the string before it grows, so it has to
# hold the buffer by then whatever the growth does.
d = "z" * 100
d << "z" * 100
d_slice = d[0, 150]
d.succ!
assert_equal "a" * 201, d
assert_equal "z" * 150, d_slice

# The sharer is the one that grows, ending below what its parent holds.
e = "e" * 100
e << "w" * 100
e_slice = e[0, 150]
e_slice.insert(0, "1234")
assert_equal "1234" + "e" * 100 + "w" * 50, e_slice
assert_equal "e" * 100 + "w" * 100, e
end

assert('String#casecmp') do
assert_equal 1, "abcdef".casecmp("abcde")
assert_equal 0, "aBcDeF".casecmp("abcdef")
Expand Down
24 changes: 24 additions & 0 deletions test/t/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1200,3 +1200,27 @@ def [](*args)
# with an empty string
assert_equal "012789", "0123456789".bytesplice(3, 4, "")
end

assert('String#bytesplice on a shared buffer') do
# A longer replacement grows the string and then writes from `idx1`, which
# a string sharing the same buffer still reads, so growing has to take the
# buffer away from it. Each string is shortened before it is shared, to
# leave spare capacity behind: one that has none cannot be grown in place
# anyway, so it would not tell the two behaviours apart.
a = "a" * 100 + "z" * 100
a.bytesplice(100, 100, "")
a_slice = a[0, 60]
a.bytesplice(0, 1, "1234567890")
assert_equal "1234567890" + "a" * 99, a
assert_equal "a" * 60, a_slice

# The sharer is the one that grows, ending below what its parent holds.
# Contract rather than detection: that is already the case where a growth
# keeping the buffer would have to take a copy regardless.
b = "b" * 100 + "y" * 100
b.bytesplice(100, 100, "")
b_slice = b[0, 60]
b_slice.bytesplice(0, 1, "1234567890")
assert_equal "1234567890" + "b" * 59, b_slice
assert_equal "b" * 100, b
end
Loading