diff options
Diffstat (limited to 'test/ruby/test_io_buffer.rb')
-rw-r--r-- | test/ruby/test_io_buffer.rb | 472 |
1 files changed, 426 insertions, 46 deletions
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index f7c175b589..7a58ec0c5a 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: false +require 'tempfile' + class TestIOBuffer < Test::Unit::TestCase experimental = Warning[:experimental] begin @@ -18,14 +20,14 @@ class TestIOBuffer < Test::Unit::TestCase end def test_flags - assert_equal 0, IO::Buffer::EXTERNAL - assert_equal 1, IO::Buffer::INTERNAL - assert_equal 2, IO::Buffer::MAPPED + assert_equal 1, IO::Buffer::EXTERNAL + assert_equal 2, IO::Buffer::INTERNAL + assert_equal 4, IO::Buffer::MAPPED - assert_equal 16, IO::Buffer::LOCKED - assert_equal 32, IO::Buffer::PRIVATE + assert_equal 32, IO::Buffer::LOCKED + assert_equal 64, IO::Buffer::PRIVATE - assert_equal 64, IO::Buffer::IMMUTABLE + assert_equal 128, IO::Buffer::READONLY end def test_endian @@ -36,6 +38,10 @@ class TestIOBuffer < Test::Unit::TestCase assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN end + def test_default_size + assert_equal IO::Buffer::DEFAULT_SIZE, IO::Buffer.new.size + end + def test_new_internal buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL) assert_equal 1024, buffer.size @@ -52,41 +58,67 @@ class TestIOBuffer < Test::Unit::TestCase assert buffer.mapped? end - def test_new_immutable - buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::IMMUTABLE) - assert buffer.immutable? + def test_new_readonly + buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY) + assert buffer.readonly? - assert_raise RuntimeError do - buffer.copy("", 0) + assert_raise IO::Buffer::AccessError do + buffer.set_string("") end - assert_raise RuntimeError do - buffer.copy("!", 1) + assert_raise IO::Buffer::AccessError do + buffer.set_string("!", 1) end end def test_file_mapped - buffer = File.open(__FILE__) {|file| IO::Buffer.map(file)} - assert_include buffer.to_str, "Hello World" + buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)} + contents = buffer.get_string + + assert_include contents, "Hello World" + assert_equal Encoding::BINARY, contents.encoding + end + + def test_file_mapped_invalid + assert_raise TypeError do + IO::Buffer.map("foobar") + end end def test_string_mapped string = "Hello World" buffer = IO::Buffer.for(string) + assert buffer.readonly? + end - # Cannot modify string as it's locked by the buffer: - assert_raise RuntimeError do - string[0] = "h" - end + def test_string_mapped_frozen + string = "Hello World".freeze + buffer = IO::Buffer.for(string) + assert buffer.readonly? + end + + def test_string_mapped_mutable + string = "Hello World" + IO::Buffer.for(string) do |buffer| + refute buffer.readonly? - buffer.set(:U8, 0, "h".ord) + buffer.set_value(:U8, 0, "h".ord) - # Buffer releases it's ownership of the string: - buffer.free + # Buffer releases it's ownership of the string: + buffer.free - assert_equal "hello World", string - string[0] = "H" - assert_equal "Hello World", string + assert_equal "hello World", string + end + end + + def test_string_mapped_buffer_locked + string = "Hello World" + IO::Buffer.for(string) do |buffer| + # Cannot modify string as it's locked by the buffer: + assert_raise RuntimeError do + string[0] = "h" + end + end end def test_non_string @@ -97,18 +129,54 @@ class TestIOBuffer < Test::Unit::TestCase end end - def test_resize - buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED) - buffer.resize(2048, 0) + def test_string + result = IO::Buffer.string(12) do |buffer| + buffer.set_string("Hello World!") + end + + assert_equal "Hello World!", result + end + + def test_string_negative + assert_raise ArgumentError do + IO::Buffer.string(-1) + end + end + + def test_resize_mapped + buffer = IO::Buffer.new + + buffer.resize(2048) assert_equal 2048, buffer.size + + buffer.resize(4096) + assert_equal 4096, buffer.size end def test_resize_preserve message = "Hello World" - buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED) - buffer.copy(message, 0) - buffer.resize(2048, 1024) - assert_equal message, buffer.to_str(0, message.bytesize) + buffer = IO::Buffer.new(1024) + buffer.set_string(message) + buffer.resize(2048) + assert_equal message, buffer.get_string(0, message.bytesize) + end + + def test_resize_zero_internal + buffer = IO::Buffer.new(1) + + buffer.resize(0) + assert_equal 0, buffer.size + + buffer.resize(1) + assert_equal 1, buffer.size + end + + def test_resize_zero_external + buffer = IO::Buffer.for('1') + + assert_raise IO::Buffer::AccessError do + buffer.resize(0) + end end def test_compare_same_size @@ -116,8 +184,8 @@ class TestIOBuffer < Test::Unit::TestCase assert_equal buffer1, buffer1 buffer2 = IO::Buffer.new(1) - buffer1.set(:U8, 0, 0x10) - buffer2.set(:U8, 0, 0x20) + buffer1.set_value(:U8, 0, 0x10) + buffer2.set_value(:U8, 0, 0x20) assert_negative buffer1 <=> buffer2 assert_positive buffer2 <=> buffer1 @@ -131,42 +199,194 @@ class TestIOBuffer < Test::Unit::TestCase assert_positive buffer2 <=> buffer1 end + def test_compare_zero_length + buffer1 = IO::Buffer.new(0) + buffer2 = IO::Buffer.new(1) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + def test_slice buffer = IO::Buffer.new(128) slice = buffer.slice(8, 32) - slice.copy("Hello World", 0) - assert_equal("Hello World", buffer.to_str(8, 11)) + slice.set_string("Hello World") + assert_equal("Hello World", buffer.get_string(8, 11)) + end + + def test_slice_arguments + buffer = IO::Buffer.for("Hello World") + + slice = buffer.slice + assert_equal "Hello World", slice.get_string + + slice = buffer.slice(2) + assert_equal("llo World", slice.get_string) end - def test_slice_bounds + def test_slice_bounds_error buffer = IO::Buffer.new(128) - # What is best exception class? - assert_raise RuntimeError do + assert_raise ArgumentError do buffer.slice(128, 10) end - # assert_raise RuntimeError do - # pp buffer.slice(-10, 10) - # end + assert_raise ArgumentError do + buffer.slice(-10, 10) + end end def test_locked buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::LOCKED) - assert_raise RuntimeError do - buffer.resize(256, 0) + assert_raise IO::Buffer::LockedError do + buffer.resize(256) end assert_equal 128, buffer.size - assert_raise RuntimeError do + assert_raise IO::Buffer::LockedError do buffer.free end assert_equal 128, buffer.size end + def test_get_string + message = "Hello World 🤓" + + buffer = IO::Buffer.new(128) + buffer.set_string(message) + + chunk = buffer.get_string(0, message.bytesize, Encoding::UTF_8) + assert_equal message, chunk + assert_equal Encoding::UTF_8, chunk.encoding + + chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY) + assert_equal Encoding::BINARY, chunk.encoding + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(0, 129) + end + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(129) + end + + assert_raise_with_message(ArgumentError, /Offset can't be negative/) do + buffer.get_string(-1) + end + end + + def test_zero_length_get_string + buffer = IO::Buffer.new.slice(0, 0) + assert_equal "", buffer.get_string + + buffer = IO::Buffer.new(0) + assert_equal "", buffer.get_string + end + + # We check that values are correctly round tripped. + RANGES = { + :U8 => [0, 2**8-1], + :S8 => [-2**7, 0, 2**7-1], + + :U16 => [0, 2**16-1], + :S16 => [-2**15, 0, 2**15-1], + :u16 => [0, 2**16-1], + :s16 => [-2**15, 0, 2**15-1], + + :U32 => [0, 2**32-1], + :S32 => [-2**31, 0, 2**31-1], + :u32 => [0, 2**32-1], + :s32 => [-2**31, 0, 2**31-1], + + :U64 => [0, 2**64-1], + :S64 => [-2**63, 0, 2**63-1], + :u64 => [0, 2**64-1], + :s64 => [-2**63, 0, 2**63-1], + + :F32 => [-1.0, 0.0, 0.5, 1.0, 128.0], + :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0], + } + + def test_get_set_value + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + values.each do |value| + buffer.set_value(data_type, 0, value) + assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}." + end + end + end + + def test_get_set_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}." + end + end + + def test_zero_length_get_set_values + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.get_values([], 0) + assert_equal 0, buffer.set_values([], 0, []) + end + + def test_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}." + end + end + + def test_each + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + data_type_size = IO::Buffer.size_of(data_type) + values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]} + + buffer.set_values(format, 0, values) + assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}." + end + end + + def test_zero_length_each + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.each(:U8).to_a + end + + def test_each_byte + string = "The quick brown fox jumped over the lazy dog." + buffer = IO::Buffer.for(string) + + assert_equal string.bytes, buffer.each_byte.to_a + end + + def test_zero_length_each_byte + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.each_byte.to_a + end + + def test_clear + buffer = IO::Buffer.new(16) + buffer.set_string("Hello World!") + end + def test_invalidation input, output = IO.pipe @@ -181,7 +401,7 @@ class TestIOBuffer < Test::Unit::TestCase # (4) scheduler returns # (5) rb_write_internal invalidate the buffer object - assert_raise RuntimeError do + assert_raise IO::Buffer::LockedError do buffer.free end @@ -192,4 +412,164 @@ class TestIOBuffer < Test::Unit::TestCase input.close end + + def hello_world_tempfile + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + yield io + ensure + io&.close! + end + + def test_read + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_length + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, 5) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_offset + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, nil, 6) + assert_equal "Hello", buffer.get_string(6, 5) + end + end + + def test_write + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello") + buffer.write(io) + + io.seek(0) + assert_equal "Hello", io.read(5) + ensure + io.close! + end + + def test_pread + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5) + + assert_equal "World", buffer.get_string(0, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pread_offset + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5, 6) + + assert_equal "World", buffer.get_string(6, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pwrite + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("World") + buffer.pwrite(io, 6, 5) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_pwrite_offset + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello World") + buffer.pwrite(io, 6, 5, 6) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), (source & mask) + assert_equal IO::Buffer.for("1334133413"), (source | mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), (source ^ mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), ~source + end + + def test_inplace_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), source.dup.and!(mask) + assert_equal IO::Buffer.for("1334133413"), source.dup.or!(mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not! + end + + def test_shared + message = "Hello World" + buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED) + + pid = fork do + buffer.set_string(message) + end + + Process.wait(pid) + string = buffer.get_string(0, message.bytesize) + assert_equal message, string + rescue NotImplementedError + omit "Fork/shared memory is not supported." + end + + def test_private + Tempfile.create(%w"buffer .txt") do |file| + file.write("Hello World") + + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + begin + assert buffer.private? + refute buffer.readonly? + + buffer.set_string("J") + + # It was not changed because the mapping was private: + file.seek(0) + assert_equal "Hello World", file.read + ensure + buffer&.free + end + end + end end |