From 60c35d9ead96af2293ce34be842224d007b4fd50 Mon Sep 17 00:00:00 2001 From: Alexander Bulancov <6594487+trinistr@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:28:19 +0300 Subject: [PATCH] WIP Add spec for IO::Buffer.map --- core/io/buffer/fixtures/big_file.txt | 184 +++++++++++++++++++++++++++ core/io/buffer/map_spec.rb | 160 +++++++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 core/io/buffer/fixtures/big_file.txt create mode 100644 core/io/buffer/map_spec.rb diff --git a/core/io/buffer/fixtures/big_file.txt b/core/io/buffer/fixtures/big_file.txt new file mode 100644 index 0000000000..7fd98c2866 --- /dev/null +++ b/core/io/buffer/fixtures/big_file.txt @@ -0,0 +1,184 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer.map" do + after :each do + @buffer&.free + @buffer = nil + @file&.close + @file = nil + end + + def open_fixture + File.open("#{__dir__}/../fixtures/read_text.txt", "r+") + end + + it "creates a new buffer mapped from a file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.size.should == 9 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abcâdef\n" + end + + ruby_version_is ""..."3.3" do + it "creates a buffer with default state and expected flags" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should.shared? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + end + + ruby_version_is "3.3" do + it "creates a buffer with default state and expected flags" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should.shared? + @buffer.should_not.private? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + end + + context "with an empty file" do + it "raises Errno::EINVAL" do + -> { IO::Buffer.map(File.open("#{__dir__}/../fixtures/empty.txt", "r+")) }.should raise_error(Errno::EINVAL) + end + end + + context "with size argument" do + it "limits the buffer to the specified size in bytes, starting from the start of the file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4) + + @buffer.size.should == 4 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abc\xC3" + end + + it "maps the whole file if size is nil" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil) + + @buffer.size.should == 9 + end + + it "raises Errno::EINVAL if size is 0" do + @file = open_fixture + -> { IO::Buffer.map(@file, 0) }.should raise_error(Errno::EINVAL) + end + + it "raises TypeError if size is not an Integer or nil" do + @file = open_fixture + -> { IO::Buffer.map(@file, "10") }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.map(@file, 10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, -1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + end + + context "with size and offset arguments" do + context "if offset is a multiple of page size" do + it "maps the specified length starting from the offset" do + @file = File.open("#{__dir__}/fixtures/big_file.txt", "r+") + @buffer = IO::Buffer.map(@file, 14, IO::Buffer::PAGE_SIZE) + + @buffer.size.should == 14 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "-> { IO::Buffe" + end + + it "maps the rest of the file if size is nil" do + @file = File.open("#{__dir__}/fixtures/big_file.txt", "r+") + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE * 2) + + @buffer.size.should == 5895 # BUG: this is wrong + # @buffer.get_string.force_encoding(Encoding::UTF_8).should == "-> { IO::Buffe" + end + end + + it "maps the file from the start if offset is 0" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4, 0) + + @buffer.size.should == 4 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abc\xC3" + end + + it "raises TypeError if offset is not convertible to Integer" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, "3") }.should raise_error(TypeError, "no implicit conversion of String into Integer") + end + + it "raises Errno::EINVAL if offset is not an Integer" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, 3.0) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EINVAL if offset is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, -1) }.should raise_error(Errno::EINVAL) + end + end + + context "with size and flags arguments" do + it "forces mapped buffer with IO::Buffer::MAPPED flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE - 1, IO::Buffer::MAPPED) + @buffer.should.mapped? + @buffer.should_not.internal? + @buffer.should_not.empty? + end + + it "forces internal buffer with IO::Buffer::INTERNAL flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::INTERNAL) + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "raises IO::Buffer::AllocationError if neither IO::Buffer::MAPPED nor IO::Buffer::INTERNAL is given" do + -> { IO::Buffer.new(10, IO::Buffer::READONLY) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + -> { IO::Buffer.new(10, 0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + + ruby_version_is "3.3" do + it "raises ArgumentError if flags is negative" do + -> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!") + end + end + + ruby_version_is ""..."3.3" do + it "raises IO::Buffer::AllocationError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + end + + ruby_version_is "3.3" do + it "raises TypeError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer") + end + end + end +end diff --git a/core/io/buffer/map_spec.rb b/core/io/buffer/map_spec.rb new file mode 100644 index 0000000000..79e0cdd868 --- /dev/null +++ b/core/io/buffer/map_spec.rb @@ -0,0 +1,160 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer.map" do + after :each do + @buffer&.free + @buffer = nil + @file&.close + @file = nil + end + + def open_fixture + File.open("#{__dir__}/../fixtures/read_text.txt", "r+") + end + + it "creates a new buffer mapped from a file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.size.should == 9 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abcâdef\n" + end + + it "allows to close the file after creating buffer, retaining mapping" do + File.open("#{__dir__}/../fixtures/read_text.txt", "r+") do |file| + @buffer = IO::Buffer.map(file) + end + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abcâdef\n" + end + + ruby_version_is ""..."3.3" do + it "creates a buffer with default state and expected flags" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should.shared? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + end + + ruby_version_is "3.3" do + it "creates a buffer with default state and expected flags" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should.shared? + @buffer.should_not.private? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + end + + context "with an empty file" do + it "raises Errno::EINVAL" do + @file = File.open("#{__dir__}/../fixtures/empty.txt", "r+") + -> { IO::Buffer.map(@file) }.should raise_error(Errno::EINVAL) + end + end + + context "with size argument" do + it "limits the buffer to the specified size in bytes, starting from the start of the file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4) + + @buffer.size.should == 4 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abc\xC3" + end + + it "maps the whole file if size is nil" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil) + + @buffer.size.should == 9 + end + + it "raises Errno::EINVAL if size is 0" do + @file = open_fixture + -> { IO::Buffer.map(@file, 0) }.should raise_error(Errno::EINVAL) + end + + it "raises TypeError if size is not an Integer or nil" do + @file = open_fixture + -> { IO::Buffer.map(@file, "10") }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.map(@file, 10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, -1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + end + + context "with size and offset arguments" do + context "if offset is a multiple of page size" do + it "maps the specified length starting from the offset" do + @file = File.open("README.md", "r+") + @buffer = IO::Buffer.map(@file, 14, 65536) + + @buffer.size.should == 14 + # @buffer.get_string(0, 14).force_encoding(Encoding::UTF_8).should == "# Ruby Spec" + end + + # A second mapping just doesn't work? + it "maps the rest of the file if size is nil" do + @file = File.open("#{__dir__}/fixtures/big_file.txt", "r+") + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE) + + @buffer.size.should == 5895 # BUG: why is this the total size? + @buffer.get_string(0, 1).force_encoding(Encoding::UTF_8).should == "-" + end + end + + it "raises Errno::EINVAL if offset is not a multiple of page size" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, IO::Buffer::PAGE_SIZE / 2) }.should raise_error(Errno::EINVAL) + end + + it "maps the file from the start if offset is 0" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4, 0) + + @buffer.size.should == 4 + @buffer.get_string.force_encoding(Encoding::UTF_8).should == "abc\xC3" + end + + it "raises TypeError if offset is not convertible to Integer" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, "4096") }.should raise_error(TypeError, "no implicit conversion of String into Integer") + -> { IO::Buffer.map(@file, 4, nil) }.should raise_error(TypeError, "no implicit conversion from nil to integer") + end + + it "raises Errno::EINVAL if offset is not an allowed value" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, 3) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EINVAL if offset is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, -1) }.should raise_error(Errno::EINVAL) + end + end +end