summaryrefslogtreecommitdiff
path: root/test/ruby/test_io_buffer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_io_buffer.rb')
-rw-r--r--test/ruby/test_io_buffer.rb275
1 files changed, 261 insertions, 14 deletions
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 55296c1f23..706ce16c42 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'tempfile'
+require 'rbconfig/sizeof'
class TestIOBuffer < Test::Unit::TestCase
experimental = Warning[:experimental]
@@ -45,22 +46,22 @@ class TestIOBuffer < Test::Unit::TestCase
def test_new_internal
buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
assert_equal 1024, buffer.size
- refute buffer.external?
- assert buffer.internal?
- refute buffer.mapped?
+ refute_predicate buffer, :external?
+ assert_predicate buffer, :internal?
+ refute_predicate buffer, :mapped?
end
def test_new_mapped
buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
assert_equal 1024, buffer.size
- refute buffer.external?
- refute buffer.internal?
- assert buffer.mapped?
+ refute_predicate buffer, :external?
+ refute_predicate buffer, :internal?
+ assert_predicate buffer, :mapped?
end
def test_new_readonly
buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
assert_raise IO::Buffer::AccessError do
buffer.set_string("")
@@ -73,12 +74,64 @@ class TestIOBuffer < Test::Unit::TestCase
def test_file_mapped
buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
- contents = buffer.get_string
+ assert_equal File.size(__FILE__), buffer.size
+ contents = buffer.get_string
assert_include contents, "Hello World"
assert_equal Encoding::BINARY, contents.encoding
end
+ def test_file_mapped_with_size
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)}
+ assert_equal 30, buffer.size
+
+ contents = buffer.get_string
+ assert_equal "# frozen_string_literal: false", contents
+ assert_equal Encoding::BINARY, contents.encoding
+ end
+
+ def test_file_mapped_size_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_size_just_enough
+ File.open(__FILE__) {|file|
+ assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size
+ }
+ end
+
+ def test_file_mapped_offset_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_zero_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_offset
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)}
+ end
+ end
+
def test_file_mapped_invalid
assert_raise TypeError do
IO::Buffer.map("foobar")
@@ -88,19 +141,19 @@ class TestIOBuffer < Test::Unit::TestCase
def test_string_mapped
string = "Hello World"
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_frozen
string = "Hello World".freeze
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_mutable
string = "Hello World"
IO::Buffer.for(string) do |buffer|
- refute buffer.readonly?
+ refute_predicate buffer, :readonly?
buffer.set_value(:U8, 0, "h".ord)
@@ -121,6 +174,16 @@ class TestIOBuffer < Test::Unit::TestCase
end
end
+ def test_string_mapped_buffer_frozen
+ string = "Hello World".freeze
+ IO::Buffer.for(string) do |buffer|
+ assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do
+ buffer.set_string("abc")
+ end
+ assert_equal "H".ord, buffer.get_value(:U8, 0)
+ end
+ end
+
def test_non_string
not_string = Object.new
@@ -343,10 +406,17 @@ class TestIOBuffer < Test::Unit::TestCase
:u64 => [0, 2**64-1],
:s64 => [-2**63, 0, 2**63-1],
+ :U128 => [0, 2**64, 2**127-1, 2**128-1],
+ :S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+ :u128 => [0, 2**64, 2**127-1, 2**128-1],
+ :s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+
:F32 => [-1.0, 0.0, 0.5, 1.0, 128.0],
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
}
+ SIZE_MAX = RbConfig::LIMITS["SIZE_MAX"]
+
def test_get_set_value
buffer = IO::Buffer.new(128)
@@ -355,6 +425,16 @@ class TestIOBuffer < Test::Unit::TestCase
buffer.set_value(data_type, 0, value)
assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
end
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 128)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 128, 0)}
+ case data_type
+ when :U8, :S8
+ else
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 127)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 127, 0)}
+ assert_raise(ArgumentError) {buffer.get_value(data_type, SIZE_MAX)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, SIZE_MAX, 0)}
+ end
end
end
@@ -411,6 +491,7 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.for(string)
assert_equal string.bytes, buffer.each_byte.to_a
+ assert_equal string.bytes[3, 5], buffer.each_byte(3, 5).to_a
end
def test_zero_length_each_byte
@@ -421,7 +502,21 @@ class TestIOBuffer < Test::Unit::TestCase
def test_clear
buffer = IO::Buffer.new(16)
- buffer.set_string("Hello World!")
+ assert_equal "\0" * 16, buffer.get_string
+ buffer.clear(1)
+ assert_equal "\1" * 16, buffer.get_string
+ buffer.clear(2, 1, 2)
+ assert_equal "\1" + "\2"*2 + "\1"*13, buffer.get_string
+ buffer.clear(2, 1)
+ assert_equal "\1" + "\2"*15, buffer.get_string
+ buffer.clear(260)
+ assert_equal "\4" * 16, buffer.get_string
+ assert_raise(TypeError) {buffer.clear("x")}
+
+ assert_raise(ArgumentError) {buffer.clear(0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 10, 10)}
+ assert_raise(ArgumentError) {buffer.clear(0, SIZE_MAX-7, 10)}
end
def test_invalidation
@@ -620,8 +715,8 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
begin
- assert buffer.private?
- refute buffer.readonly?
+ assert_predicate buffer, :private?
+ refute_predicate buffer, :readonly?
buffer.set_string("J")
@@ -683,4 +778,156 @@ class TestIOBuffer < Test::Unit::TestCase
buf.set_string('a', 0, 0)
assert_predicate buf, :empty?
end
+
+ # https://bugs.ruby-lang.org/issues/21210
+ def test_bug_21210
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ str = +"hello"
+ buf = IO::Buffer.for(str)
+ assert_predicate buf, :valid?
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_predicate buf, :valid?
+ end
+
+ def test_128_bit_integers
+ buffer = IO::Buffer.new(32)
+
+ # Test unsigned 128-bit integers
+ test_values_u128 = [
+ 0,
+ 1,
+ 2**64 - 1,
+ 2**64,
+ 2**127 - 1,
+ 2**128 - 1,
+ ]
+
+ test_values_u128.each do |value|
+ buffer.set_value(:u128, 0, value)
+ assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}"
+
+ buffer.set_value(:U128, 0, value)
+ assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}"
+ end
+
+ # Test signed 128-bit integers
+ test_values_s128 = [
+ -2**127,
+ -2**63 - 1,
+ -1,
+ 0,
+ 1,
+ 2**63,
+ 2**127 - 1,
+ ]
+
+ test_values_s128.each do |value|
+ buffer.set_value(:s128, 0, value)
+ assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}"
+
+ buffer.set_value(:S128, 0, value)
+ assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}"
+ end
+
+ # Test size_of
+ assert_equal 16, IO::Buffer.size_of(:u128)
+ assert_equal 16, IO::Buffer.size_of(:U128)
+ assert_equal 16, IO::Buffer.size_of(:s128)
+ assert_equal 16, IO::Buffer.size_of(:S128)
+ assert_equal 32, IO::Buffer.size_of([:u128, :u128])
+ end
+
+ def test_integer_endianness_swapping
+ # Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte
+ host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN
+ host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN
+
+ # Test values that will produce different byte patterns when swapped
+ # Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value]
+ # expected_swapped_value is the result when writing as le_type and reading as be_type
+ # (or vice versa) on a little-endian host
+ test_cases = [
+ [:u16, :U16, 0x1234, 0x3412],
+ [:s16, :S16, 0x1234, 0x3412],
+ [:u32, :U32, 0x12345678, 0x78563412],
+ [:s32, :S32, 0x12345678, 0x78563412],
+ [:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301],
+ [:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991],
+ [:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301],
+ [:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ [:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE],
+ [:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412],
+ [:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831],
+ [:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ ]
+
+ test_cases.each do |le_type, be_type, value, expected_swapped|
+ buffer_size = IO::Buffer.size_of(le_type)
+ buffer = IO::Buffer.new(buffer_size * 2)
+
+ # Test little-endian round-trip
+ buffer.set_value(le_type, 0, value)
+ result_le = buffer.get_value(le_type, 0)
+ assert_equal value, result_le, "#{le_type}: round-trip failed"
+
+ # Test big-endian round-trip
+ buffer.set_value(be_type, buffer_size, value)
+ result_be = buffer.get_value(be_type, buffer_size)
+ assert_equal value, result_be, "#{be_type}: round-trip failed"
+
+ # Verify byte patterns are different when endianness differs from host
+ if host_is_le
+ # On little-endian host: le_type should match host, be_type should be swapped
+ # So the byte patterns should be different (unless value is symmetric)
+ # Read back with opposite endianness to verify swapping
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ # For most values, this will be different
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host"
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)"
+ elsif host_is_be
+ # On big-endian host: be_type should match host, le_type should be swapped
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host"
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)"
+ end
+
+ # Verify that when we write with one endianness and read with the opposite,
+ # we get the expected swapped value
+ buffer.set_value(le_type, 0, value)
+ swapped_value_le_to_be = buffer.get_value(be_type, 0)
+ assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value"
+
+ # Also verify the reverse direction
+ buffer.set_value(be_type, buffer_size, value)
+ swapped_value_be_to_le = buffer.get_value(le_type, buffer_size)
+ assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value"
+
+ # Verify that writing the swapped value back and reading with original endianness
+ # gives us the original value (double-swap should restore original)
+ buffer.set_value(be_type, 0, swapped_value_le_to_be)
+ round_trip_value = buffer.get_value(le_type, 0)
+ assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value"
+ end
+ end
end