diff options
Diffstat (limited to 'spec/ruby/core/io')
40 files changed, 1870 insertions, 217 deletions
diff --git a/spec/ruby/core/io/autoclose_spec.rb b/spec/ruby/core/io/autoclose_spec.rb new file mode 100644 index 0000000000..715ada7c93 --- /dev/null +++ b/spec/ruby/core/io/autoclose_spec.rb @@ -0,0 +1,77 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "IO#autoclose?" do + before :each do + @io = IOSpecs.io_fixture "lines.txt" + end + + after :each do + @io.autoclose = true unless @io.closed? + @io.close unless @io.closed? + end + + it "is set to true by default" do + @io.should.autoclose? + end + + it "cannot be queried on a closed IO object" do + @io.close + -> { @io.autoclose? }.should raise_error(IOError, /closed stream/) + end +end + +describe "IO#autoclose=" do + before :each do + @io = IOSpecs.io_fixture "lines.txt" + end + + after :each do + @io.autoclose = true unless @io.closed? + @io.close unless @io.closed? + end + + it "can be set to true" do + @io.autoclose = false + @io.autoclose = true + @io.should.autoclose? + end + + it "can be set to false" do + @io.autoclose = true + @io.autoclose = false + @io.should_not.autoclose? + end + + it "can be set to any truthy value" do + @io.autoclose = false + @io.autoclose = 42 + @io.should.autoclose? + + @io.autoclose = false + @io.autoclose = Object.new + @io.should.autoclose? + end + + it "can be set to any falsy value" do + @io.autoclose = true + @io.autoclose = nil + @io.should_not.autoclose? + end + + it "can be set multiple times" do + @io.autoclose = true + @io.should.autoclose? + + @io.autoclose = false + @io.should_not.autoclose? + + @io.autoclose = true + @io.should.autoclose? + end + + it "cannot be set on a closed IO object" do + @io.close + -> { @io.autoclose = false }.should raise_error(IOError, /closed stream/) + end +end diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 418e89213b..9e36b84da9 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -45,7 +45,7 @@ describe "IO.binread" do -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do cmd = "|echo ok" diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb new file mode 100644 index 0000000000..e1fd4ab6a2 --- /dev/null +++ b/spec/ruby/core/io/buffer/empty_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#empty?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :empty? + + it "is true for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.empty?.should be_true + end + + ruby_version_is "3.3" do + it "is true for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.empty?.should be_true + end + end + end + + it "is true for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).empty?.should be_true + end +end diff --git a/spec/ruby/core/io/buffer/external_spec.rb b/spec/ruby/core/io/buffer/external_spec.rb new file mode 100644 index 0000000000..4377a38357 --- /dev/null +++ b/spec/ruby/core/io/buffer/external_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#external?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.external?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.external?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.external?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.external?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.external?.should be_true + end + + it "is true for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.external?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is true" do + IO::Buffer.string(4) do |buffer| + buffer.external?.should be_true + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.external?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.external?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.external?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.external?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.external?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.external?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/free_spec.rb b/spec/ruby/core/io/buffer/free_spec.rb new file mode 100644 index 0000000000..f3a4918978 --- /dev/null +++ b/spec/ruby/core/io/buffer/free_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#free" do + context "with a buffer created with .new" do + it "frees internal memory and nullifies the buffer" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + end + + it "frees mapped memory and nullifies the buffer" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + buffer.free + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "frees mapped memory and nullifies the buffer" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + buffer.free + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + buffer = IO::Buffer.for(string) + # Read-only buffer, can't modify the string. + buffer.free + buffer.null?.should be_true + end + end + + context "with a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + IO::Buffer.for(string) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = + IO::Buffer.string(4) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + it "can be called repeatedly without an error" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + buffer.free + buffer.null?.should be_true + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + buffer = IO::Buffer.new(4) + buffer.locked do + -> { buffer.free }.should raise_error(IO::Buffer::LockedError, "Buffer is locked!") + end + buffer.free + buffer.null?.should be_true + end + + context "with a slice of a buffer" do + it "nullifies the slice, not touching the buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + slice.free + slice.null?.should be_true + buffer.null?.should be_false + + buffer.free + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + buffer.free + slice.null?.should be_false + slice.valid?.should be_false + end + end +end diff --git a/spec/ruby/core/io/buffer/initialize_spec.rb b/spec/ruby/core/io/buffer/initialize_spec.rb new file mode 100644 index 0000000000..c86d1e7f1d --- /dev/null +++ b/spec/ruby/core/io/buffer/initialize_spec.rb @@ -0,0 +1,103 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#initialize" do + after :each do + @buffer&.free + @buffer = nil + end + + it "creates a new zero-filled buffer with default size" do + @buffer = IO::Buffer.new + @buffer.size.should == IO::Buffer::DEFAULT_SIZE + @buffer.each(:U8).should.all? { |_offset, value| value.eql?(0) } + end + + it "creates a buffer with default state" do + @buffer = IO::Buffer.new + @buffer.should_not.shared? + @buffer.should_not.readonly? + + @buffer.should_not.empty? + @buffer.should_not.null? + + # This is run-time state, set by #locked. + @buffer.should_not.locked? + end + + context "with size argument" do + it "creates a new internal buffer if size is less than IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE - 1 + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "creates a new mapped buffer if size is greater than or equal to IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should_not.empty? + end + + it "creates a null buffer if size is 0" do + @buffer = IO::Buffer.new(0) + @buffer.size.should.zero? + @buffer.should_not.internal? + @buffer.should_not.mapped? + @buffer.should.null? + @buffer.should.empty? + end + + it "raises TypeError if size is not an Integer" do + -> { IO::Buffer.new(nil) }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.new(10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + -> { IO::Buffer.new(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + 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/spec/ruby/core/io/buffer/internal_spec.rb b/spec/ruby/core/io/buffer/internal_spec.rb new file mode 100644 index 0000000000..409699cc3c --- /dev/null +++ b/spec/ruby/core/io/buffer/internal_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#internal?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is true for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.internal?.should be_true + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.internal?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.internal?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.internal?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.internal?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.internal?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.internal?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.internal?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.internal?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/locked_spec.rb b/spec/ruby/core/io/buffer/locked_spec.rb new file mode 100644 index 0000000000..4ffa569fd2 --- /dev/null +++ b/spec/ruby/core/io/buffer/locked_spec.rb @@ -0,0 +1,75 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#locked" do + after :each do + @buffer&.free + @buffer = nil + end + + context "when buffer is locked" do + it "allows reading and writing operations on the buffer" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + @buffer.locked do + @buffer.get_string.should == "test" + @buffer.set_string("meat") + end + @buffer.get_string.should == "meat" + end + + it "disallows operations changing buffer itself, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + # Just an example, each method is responsible for checking the lock state. + -> { @buffer.resize(8) }.should raise_error(IO::Buffer::LockedError) + end + end + end + + it "disallows reentrant locking, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.locked {} }.should raise_error(IO::Buffer::LockedError, "Buffer already locked!") + end + end + + it "does not propagate to buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.locked do + @buffer.locked?.should be_true + slice.locked?.should be_false + slice.locked { slice.locked?.should be_true } + end + end + + it "does not propagate backwards from buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.locked do + slice.locked?.should be_true + @buffer.locked?.should be_false + @buffer.locked { @buffer.locked?.should be_true } + end + end +end + +describe "IO::Buffer#locked?" do + after :each do + @buffer&.free + @buffer = nil + end + + it "is false by default" do + @buffer = IO::Buffer.new(4) + @buffer.locked?.should be_false + end + + it "is true only inside of #locked block" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + @buffer.locked?.should be_true + end + @buffer.locked?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/mapped_spec.rb b/spec/ruby/core/io/buffer/mapped_spec.rb new file mode 100644 index 0000000000..b3610207ff --- /dev/null +++ b/spec/ruby/core/io/buffer/mapped_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#mapped?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.mapped?.should be_false + end + + it "is true for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.mapped?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.mapped?.should be_true + end + end + + ruby_version_is "3.3" do + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.mapped?.should be_true + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.mapped?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.mapped?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.mapped?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.mapped?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/null_spec.rb b/spec/ruby/core/io/buffer/null_spec.rb new file mode 100644 index 0000000000..3fb1144d0e --- /dev/null +++ b/spec/ruby/core/io/buffer/null_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#null?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :null? + + it "is false for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.null?.should be_false + end + + ruby_version_is "3.3" do + it "is false for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.null?.should be_false + end + end + end + + it "is false for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).null?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/private_spec.rb b/spec/ruby/core/io/buffer/private_spec.rb new file mode 100644 index 0000000000..7aa308997b --- /dev/null +++ b/spec/ruby/core/io/buffer/private_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../../spec_helper' + +ruby_version_is "3.3" do + describe "IO::Buffer#private?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.private?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.private?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.private?.should be_false + end + end + + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.private?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.private?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.private?.should be_false + end + end + end + + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.private?.should be_false + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.private?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.private?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.private?.should be_false + end + end + + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.private?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.private?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.private?.should be_false + end + end + end + + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.private?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/readonly_spec.rb b/spec/ruby/core/io/buffer/readonly_spec.rb new file mode 100644 index 0000000000..0014a876ed --- /dev/null +++ b/spec/ruby/core/io/buffer/readonly_spec.rb @@ -0,0 +1,143 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#readonly?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.readonly?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.readonly?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a writable mapping" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.readonly?.should be_false + end + end + + it "is true for a readonly mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.readonly?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.readonly?.should be_true + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.readonly?.should be_false + end + end + + it "is true for a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.readonly?.should be_false + end + end + end + end + + # This seems to be the only flag propagated from the source buffer to the slice. + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.readonly?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.readonly?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a read-write file-backed buffer" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a readonly file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.slice.readonly?.should be_false + end + end + end + end + + context "created with .for" do + it "is true when slicing a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.slice.readonly?.should be_true + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.slice.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.readonly?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/resize_spec.rb b/spec/ruby/core/io/buffer/resize_spec.rb new file mode 100644 index 0000000000..0da3a23356 --- /dev/null +++ b/spec/ruby/core/io/buffer/resize_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#resize" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "resizes internal buffer, preserving type" do + @buffer = IO::Buffer.new(4) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + + platform_is :linux do + it "resizes mapped buffer, preserving type" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + end + + platform_is_not :linux do + it "resizes mapped buffer, changing type to internal" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + end + + context "with a file-backed buffer created with .map" do + it "disallows resizing shared buffer, raising IO::Buffer::AccessError" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + ruby_version_is "3.3" do + it "resizes private buffer, discarding excess contents" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.get_string.should == "require_re" + @buffer.resize(12) + @buffer.size.should == 12 + @buffer.get_string.should == "require_re\0\0" + end + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + @buffer = IO::Buffer.for(+"test") + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + context "with a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.for(+'test') do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.string(4) do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + context "with a null buffer" do + it "allows resizing a 0-sized buffer, creating a regular buffer according to new size" do + @buffer = IO::Buffer.new(0) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + + it "allows resizing after a free, creating a regular buffer according to new size" do + @buffer = IO::Buffer.for("test") + @buffer.free + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + + it "allows resizing to 0, freeing memory" do + @buffer = IO::Buffer.new(4) + @buffer.resize(0) + @buffer.null?.should be_true + end + + it "can be called repeatedly" do + @buffer = IO::Buffer.new(4) + @buffer.resize(10) + @buffer.resize(27) + @buffer.resize(1) + @buffer.size.should == 1 + end + + it "always clears extra memory" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + # This should not cause a re-allocation, just a technical resizing, + # even with very aggressive memory allocation. + @buffer.resize(2) + @buffer.resize(4) + @buffer.get_string.should == "te\0\0" + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::LockedError, "Cannot resize locked buffer!") + end + end + + it "raises ArgumentError if size is negative" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + + it "raises TypeError if size is not an Integer" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(nil) }.should raise_error(TypeError, "not an Integer") + -> { @buffer.resize(10.0) }.should raise_error(TypeError, "not an Integer") + end + + context "with a slice of a buffer" do + # Current behavior of slice resizing seems unintended (it's undocumented, too). + # It either creates a completely new buffer, or breaks the slice on size 0. + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/io/buffer/shared/null_and_empty.rb b/spec/ruby/core/io/buffer/shared/null_and_empty.rb new file mode 100644 index 0000000000..c8fe9e5e46 --- /dev/null +++ b/spec/ruby/core/io/buffer/shared/null_and_empty.rb @@ -0,0 +1,59 @@ +describe :io_buffer_null_and_empty, shared: true do + it "is false for a buffer with size > 0" do + @buffer = IO::Buffer.new(1) + @buffer.send(@method).should be_false + end + + it "is false for a slice with length > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(1, 2).send(@method).should be_false + end + + it "is false for a file-mapped buffer" do + File.open(__FILE__, "rb") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.send(@method).should be_false + end + end + + it "is false for a non-empty String-backed buffer created with .for" do + @buffer = IO::Buffer.for("test") + @buffer.send(@method).should be_false + end + + ruby_version_is "3.3" do + it "is false for a non-empty String-backed buffer created with .string" do + IO::Buffer.string(4) do |buffer| + buffer.send(@method).should be_false + end + end + end + + it "is true for a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.send(@method).should be_true + end + + it "is true for a slice of a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.slice(0, 0).send(@method).should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(1) + @buffer.free + @buffer.send(@method).should be_true + end + + it "is true for a buffer resized to 0" do + @buffer = IO::Buffer.new(1) + @buffer.resize(0) + @buffer.send(@method).should be_true + end + + it "is true for a buffer whose memory was transferred" do + buffer = IO::Buffer.new(1) + @buffer = buffer.transfer + buffer.send(@method).should be_true + end +end diff --git a/spec/ruby/core/io/buffer/shared_spec.rb b/spec/ruby/core/io/buffer/shared_spec.rb new file mode 100644 index 0000000000..f2a638cf39 --- /dev/null +++ b/spec/ruby/core/io/buffer/shared_spec.rb @@ -0,0 +1,117 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#shared?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.shared?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.shared?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.shared?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.shared?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.shared?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.shared?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.shared?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.shared?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.shared?.should be_false + end + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.shared?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/transfer_spec.rb b/spec/ruby/core/io/buffer/transfer_spec.rb new file mode 100644 index 0000000000..cb8c843ff2 --- /dev/null +++ b/spec/ruby/core/io/buffer/transfer_spec.rb @@ -0,0 +1,118 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#transfer" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "transfers internal memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + + it "transfers mapped memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "transfers mapped memory to a new buffer, nullifying the original" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "transfers memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.for("test") + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a block" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.for(+"test") do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.string(4) do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + it "allows multiple transfers" do + buffer_1 = IO::Buffer.new(4) + buffer_2 = buffer_1.transfer + @buffer = buffer_2.transfer + buffer_1.null?.should be_true + buffer_2.null?.should be_true + @buffer.null?.should be_false + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.transfer }.should raise_error(IO::Buffer::LockedError, "Cannot transfer ownership of locked buffer!") + end + end + + context "with a slice of a buffer" do + it "transfers source to a new slice, not touching the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.set_string("test") + + new_slice = slice.transfer + slice.null?.should be_true + new_slice.null?.should be_false + @buffer.null?.should be_false + + new_slice.set_string("ea") + @buffer.get_string.should == "east" + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + + slice.null?.should be_false + slice.valid?.should be_false + -> { slice.get_string }.should raise_error(IO::Buffer::InvalidatedError, "Buffer has been invalidated!") + end + end +end diff --git a/spec/ruby/core/io/buffer/valid_spec.rb b/spec/ruby/core/io/buffer/valid_spec.rb new file mode 100644 index 0000000000..680a35ae9a --- /dev/null +++ b/spec/ruby/core/io/buffer/valid_spec.rb @@ -0,0 +1,110 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#valid?" do + after :each do + @buffer&.free + @buffer = nil + end + + # Non-slices are always valid + context "with a non-slice buffer" do + it "is true for a regular buffer" do + @buffer = IO::Buffer.new(4) + @buffer.valid?.should be_true + end + + it "is true for a 0-size buffer" do + @buffer = IO::Buffer.new(0) + @buffer.valid?.should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(4) + @buffer.free + @buffer.valid?.should be_true + end + + it "is true for a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + it "is true for a freed string-backed buffer" do + @buffer = IO::Buffer.for("hello") + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + # "A buffer becomes invalid if it is a slice of another buffer (or string) + # which has been freed or re-allocated at a different address." + context "with a slice" do + it "is true for a slice of a live buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + end + + context "when buffer is resized" do + it "is false when slice becomes outside the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(2, 2) + @buffer.resize(3) + slice.valid?.should be_false + end + + platform_is_not :linux do + # This test does not cause a copy-resize on Linux. + # `#resize` MAY cause the buffer to move, but there is no guarantee. + it "is false when buffer is copied on resize" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + slice = @buffer.slice(0, 2) + @buffer.resize(8) + slice.valid?.should be_false + end + end + end + + it "is false for a slice of a transferred buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + slice.valid?.should be_false + end + + it "is false for a slice of a freed buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + slice.valid?.should be_false + end + + it "is false for a slice of a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_false + end + end + + it "is true for a slice of a freed string-backed buffer while string is alive" do + @buffer = IO::Buffer.for("alive") + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_true + end + + # There probably should be a test with a garbage-collected string, + # but it's not clear how to force that. + + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/io/dup_spec.rb b/spec/ruby/core/io/dup_spec.rb index 68d538377f..564e007438 100644 --- a/spec/ruby/core/io/dup_spec.rb +++ b/spec/ruby/core/io/dup_spec.rb @@ -25,27 +25,27 @@ describe "IO#dup" do @i.fileno.should_not == @f.fileno end -quarantine! do # This does not appear to be consistent across platforms - it "shares the original stream between the two IOs" do - start = @f.pos - @i.pos.should == start + quarantine! do # This does not appear to be consistent across platforms + it "shares the original stream between the two IOs" do + start = @f.pos + @i.pos.should == start - s = "Hello, wo.. wait, where am I?\n" - s2 = "<evil voice> Muhahahaa!" + s = "Hello, wo.. wait, where am I?\n" + s2 = "<evil voice> Muhahahaa!" - @f.write s - @i.pos.should == @f.pos + @f.write s + @i.pos.should == @f.pos - @i.rewind - @i.gets.should == s + @i.rewind + @i.gets.should == s - @i.rewind - @i.write s2 + @i.rewind + @i.write s2 - @f.rewind - @f.gets.should == "#{s2}\n" + @f.rewind + @f.gets.should == "#{s2}\n" + end end -end it "allows closing the new IO without affecting the original" do @i.close diff --git a/spec/ruby/core/io/external_encoding_spec.rb b/spec/ruby/core/io/external_encoding_spec.rb index 2fcf1c7218..7765c6c0f5 100644 --- a/spec/ruby/core/io/external_encoding_spec.rb +++ b/spec/ruby/core/io/external_encoding_spec.rb @@ -94,12 +94,10 @@ describe "IO#external_encoding" do rm_r @name end - ruby_version_is '3.1' do - it "can be retrieved from a closed stream" do - io = IOSpecs.io_fixture("lines.txt", "r") - io.close - io.external_encoding.should equal(Encoding.default_external) - end + it "can be retrieved from a closed stream" do + io = IOSpecs.io_fixture("lines.txt", "r") + io.close + io.external_encoding.should equal(Encoding.default_external) end describe "with 'r' mode" do diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index c361d27879..6abe8901ba 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -14,33 +14,35 @@ describe "IO.foreach" do IO.foreach(@name) { $..should == @count += 1 } end - describe "when the filename starts with |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end + ruby_version_is ""..."4.0" do + describe "when the filename starts with |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach(cmd) { |l| ScratchPad << l } + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach(cmd) { |l| ScratchPad << l } + end + ScratchPad.recorded.should == ["hello\n", "line2\n"] end - ScratchPad.recorded.should == ["hello\n", "line2\n"] - end - platform_is_not :windows do - it "gets data from a fork when passed -" do - parent_pid = $$ + platform_is_not :windows do + it "gets data from a fork when passed -" do + parent_pid = $$ - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|-") { |l| ScratchPad << l } - end + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach("|-") { |l| ScratchPad << l } + end - if $$ == parent_pid - ScratchPad.recorded.should == ["hello\n", "from a fork\n"] - else # child - puts "hello" - puts "from a fork" - exit! + if $$ == parent_pid + ScratchPad.recorded.should == ["hello\n", "from a fork\n"] + else # child + puts "hello" + puts "from a fork" + exit! + end end end end diff --git a/spec/ruby/core/io/internal_encoding_spec.rb b/spec/ruby/core/io/internal_encoding_spec.rb index 60afaf2ebd..7a583d4bcb 100644 --- a/spec/ruby/core/io/internal_encoding_spec.rb +++ b/spec/ruby/core/io/internal_encoding_spec.rb @@ -113,12 +113,10 @@ describe "IO#internal_encoding" do Encoding.default_internal = @internal end - ruby_version_is '3.1' do - it "can be retrieved from a closed stream" do - io = IOSpecs.io_fixture("lines.txt", "r") - io.close - io.internal_encoding.should equal(Encoding.default_internal) - end + it "can be retrieved from a closed stream" do + io = IOSpecs.io_fixture("lines.txt", "r") + io.close + io.internal_encoding.should equal(Encoding.default_internal) end describe "with 'r' mode" do diff --git a/spec/ruby/core/io/ioctl_spec.rb b/spec/ruby/core/io/ioctl_spec.rb index 8dcd9eb2c6..3f7b5ad5d7 100644 --- a/spec/ruby/core/io/ioctl_spec.rb +++ b/spec/ruby/core/io/ioctl_spec.rb @@ -12,7 +12,7 @@ describe "IO#ioctl" do guard -> { RUBY_PLATFORM.include?("86") } do # x86 / x86_64 it "resizes an empty String to match the output size" do File.open(__FILE__, 'r') do |f| - buffer = '' + buffer = +'' # FIONREAD in /usr/include/asm-generic/ioctls.h f.ioctl 0x541B, buffer buffer.unpack('I').first.should be_kind_of(Integer) diff --git a/spec/ruby/core/io/path_spec.rb b/spec/ruby/core/io/path_spec.rb index 8145c32f39..798adb2163 100644 --- a/spec/ruby/core/io/path_spec.rb +++ b/spec/ruby/core/io/path_spec.rb @@ -1,14 +1,12 @@ require_relative '../../spec_helper' describe "IO#path" do - ruby_version_is "3.2" do - it "returns the path of the file associated with the IO object" do - path = tmp("io_path.txt") - File.open(path, "w") do |file| - IO.new(file.fileno, path: file.path, autoclose: false).path.should == file.path - end - ensure - File.unlink(path) + it "returns the path of the file associated with the IO object" do + path = tmp("io_path.txt") + File.open(path, "w") do |file| + IO.new(file.fileno, path: file.path, autoclose: false).path.should == file.path end + ensure + File.unlink(path) end end diff --git a/spec/ruby/core/io/popen_spec.rb b/spec/ruby/core/io/popen_spec.rb index e9d32c5c7d..6043862614 100644 --- a/spec/ruby/core/io/popen_spec.rb +++ b/spec/ruby/core/io/popen_spec.rb @@ -95,6 +95,22 @@ describe "IO.popen" do @io = IO.popen(ruby_cmd('exit 0'), mode) end + it "accepts a path using the chdir: keyword argument" do + path = File.dirname(@fname) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: path) + @io.read.chomp.should == path + end + + it "accepts a path using the chdir: keyword argument and a coercible path" do + path = File.dirname(@fname) + object = mock("path") + object.should_receive(:to_path).and_return(path) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: object) + @io.read.chomp.should == path + end + describe "with a block" do it "yields an open IO to the block" do IO.popen(ruby_cmd('exit'), "r") do |io| diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb index aa496ee803..dc7bcedf3e 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -21,23 +21,30 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do end it "accepts a length, an offset, and an output buffer" do - buffer = "foo" - @file.pread(3, 4, buffer) + buffer = +"foo" + @file.pread(3, 4, buffer).should.equal?(buffer) buffer.should == "567" end it "shrinks the buffer in case of less bytes read" do - buffer = "foo" + buffer = +"foo" @file.pread(1, 0, buffer) buffer.should == "1" end it "grows the buffer in case of more bytes read" do - buffer = "foo" + buffer = +"foo" @file.pread(5, 0, buffer) buffer.should == "12345" end + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @file.pread(10, 0, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + it "does not advance the file pointer" do @file.pread(4, 0).should == "1234" @file.read.should == "1234567890" @@ -52,12 +59,18 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do @file.pread(0, 4).should == "" end + it "returns a buffer for maxlen = 0 when buffer specified" do + buffer = +"foo" + @file.pread(0, 4, buffer).should.equal?(buffer) + buffer.should == "foo" + end + it "ignores the offset for maxlen = 0, even if it is out of file bounds" do @file.pread(0, 400).should == "" end it "does not reset the buffer when reading with maxlen = 0" do - buffer = "foo" + buffer = +"foo" @file.pread(0, 4, buffer) buffer.should == "foo" @@ -79,7 +92,7 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do it "converts a buffer to String using to_str" do buffer = mock('buffer') - buffer.should_receive(:to_str).at_least(1).and_return("foo") + buffer.should_receive(:to_str).at_least(1).and_return(+"foo") @file.pread(4, 0, buffer) buffer.should_not.is_a?(String) buffer.to_str.should == "1234" diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb index 9a708fffef..a186ddaa5d 100644 --- a/spec/ruby/core/io/puts_spec.rb +++ b/spec/ruby/core/io/puts_spec.rb @@ -6,7 +6,7 @@ describe "IO#puts" do @before_separator = $/ @name = tmp("io_puts.txt") @io = new_io @name - ScratchPad.record "" + ScratchPad.record(+"") def @io.write(str) ScratchPad << str end @@ -33,7 +33,7 @@ describe "IO#puts" do ScratchPad.recorded.should == "\n" end - it "writes empty string with a newline when when given nil as multiple args" do + it "writes empty string with a newline when given nil as multiple args" do @io.puts(nil, nil).should == nil ScratchPad.recorded.should == "\n\n" end diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb index 00d40db28d..2bc508b37d 100644 --- a/spec/ruby/core/io/pwrite_spec.rb +++ b/spec/ruby/core/io/pwrite_spec.rb @@ -57,7 +57,7 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do it "raises a NoMethodError if object does not respond to #to_s" do -> { @file.pwrite(BasicObject.new, 0) - }.should raise_error(NoMethodError, /undefined method `to_s'/) + }.should raise_error(NoMethodError, /undefined method [`']to_s'/) end it "raises a TypeError if the offset cannot be converted to an Integer" do diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb index a62b75274c..51e7cd6bd2 100644 --- a/spec/ruby/core/io/read_nonblock_spec.rb +++ b/spec/ruby/core/io/read_nonblock_spec.rb @@ -96,21 +96,21 @@ describe "IO#read_nonblock" do end it "reads into the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") @read.read_nonblock(1, buffer) buffer.should == "1" end it "returns the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") output = @read.read_nonblock(1, buffer) output.should equal(buffer) end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @write.write("hello world") @write.close @read.read_nonblock(11, buffer) @@ -118,7 +118,7 @@ describe "IO#read_nonblock" do end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @write.close -> { @read.read_nonblock(1, buffer) }.should raise_error(EOFError) buffer.should be_empty diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index db11468ea4..988ec2ce30 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -168,76 +168,78 @@ describe "IO.read" do end end -describe "IO.read from a pipe" do - it "runs the rest as a subprocess and returns the standard output" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end - - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd).should == "hello\n" - end - end +ruby_version_is ""..."4.0" do + describe "IO.read from a pipe" do + it "runs the rest as a subprocess and returns the standard output" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end - platform_is_not :windows do - it "opens a pipe to a fork if the rest is -" do - str = nil suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - str = IO.read("|-") + IO.read(cmd).should == "hello\n" end + end - if str # parent - str.should == "hello from child\n" - else #child - puts "hello from child" - exit! + platform_is_not :windows do + it "opens a pipe to a fork if the rest is -" do + str = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + str = IO.read("|-") + end + + if str # parent + str.should == "hello from child\n" + else #child + puts "hello from child" + exit! + end end end - end - it "reads only the specified number of bytes requested" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end + it "reads only the specified number of bytes requested" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd, 1).should == "h" + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd, 1).should == "h" + end end - end - platform_is_not :windows do - it "raises Errno::ESPIPE if passed an offset" do - -> { - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|sh -c 'echo hello'", 1, 1) - end - }.should raise_error(Errno::ESPIPE) + platform_is_not :windows do + it "raises Errno::ESPIPE if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|sh -c 'echo hello'", 1, 1) + end + }.should raise_error(Errno::ESPIPE) + end end - end -quarantine! do # The process tried to write to a nonexistent pipe. - platform_is :windows do - # TODO: It should raise Errno::ESPIPE on Windows as well - # once https://bugs.ruby-lang.org/issues/12230 is fixed. - it "raises Errno::EINVAL if passed an offset" do - -> { - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|cmd.exe /C echo hello", 1, 1) + quarantine! do # The process tried to write to a nonexistent pipe. + platform_is :windows do + # TODO: It should raise Errno::ESPIPE on Windows as well + # once https://bugs.ruby-lang.org/issues/12230 is fixed. + it "raises Errno::EINVAL if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|cmd.exe /C echo hello", 1, 1) + end + }.should raise_error(Errno::EINVAL) end - }.should raise_error(Errno::EINVAL) + end end - end -end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.read(cmd) - }.should complain(/IO process creation with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation" do + cmd = "|echo ok" + -> { + IO.read(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end end @@ -294,19 +296,19 @@ describe "IO#read" do it "clears the output buffer if there is nothing to read" do @io.pos = 10 - buf = 'non-empty string' + buf = +'non-empty string' @io.read(10, buf).should == nil buf.should == '' - buf = 'non-empty string' + buf = +'non-empty string' @io.read(nil, buf).should == "" buf.should == '' - buf = 'non-empty string' + buf = +'non-empty string' @io.read(0, buf).should == "" @@ -331,7 +333,7 @@ describe "IO#read" do @io.read(0).should == '' @io.pos.should == 0 - @io.getc.chr.should == '1' + @io.getc.should == '1' end it "is at end-of-file when everything has been read" do @@ -344,53 +346,68 @@ describe "IO#read" do end it "places the specified number of bytes in the buffer" do - buf = "" + buf = +"" @io.read 5, buf buf.should == "12345" end it "expands the buffer when too small" do - buf = "ABCDE" + buf = +"ABCDE" @io.read nil, buf buf.should == @contents end it "overwrites the buffer" do - buf = "ABCDEFGHIJ" + buf = +"ABCDEFGHIJ" @io.read nil, buf buf.should == @contents end it "truncates the buffer when too big" do - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read nil, buf buf.should == @contents @io.rewind - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read 5, buf buf.should == @contents[0..4] end + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.read(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + # https://bugs.ruby-lang.org/issues/20416 + it "does not preserve the encoding of the given buffer when max length is not provided" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.read(nil, buffer) + + buffer.encoding.should_not == Encoding::ISO_8859_1 + end + it "returns the given buffer" do - buf = "" + buf = +"" @io.read(nil, buf).should equal buf end it "returns the given buffer when there is nothing to read" do - buf = "" + buf = +"" @io.read @io.read(nil, buf).should equal buf end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" obj = mock("buff") obj.should_receive(:to_str).any_number_of_times.and_return(buf) @@ -588,13 +605,13 @@ describe :io_read_internal_encoding, shared: true do describe "when passed nil for limit" do it "sets the buffer to a transcoded String" do - result = @io.read(nil, buf = "") + result = @io.read(nil, buf = +"") buf.should equal(result) buf.should == "ありがとう\n" end it "sets the buffer's encoding to the internal encoding" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(nil, buf) buf.encoding.should equal(Encoding::UTF_8) end @@ -612,14 +629,14 @@ describe :io_read_size_internal_encoding, shared: true do end it "does not change the buffer's encoding when passed a limit" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(4, buf) buf.should == [164, 162, 164, 234].pack('C*').force_encoding(Encoding::ISO_8859_1) buf.encoding.should equal(Encoding::ISO_8859_1) end it "truncates the buffer but does not change the buffer's encoding when no data remains" do - buf = "abc".force_encoding Encoding::ISO_8859_1 + buf = "abc".dup.force_encoding Encoding::ISO_8859_1 @io.read @io.read(1, buf).should be_nil diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index 3a6ff3d0f3..b4770775d1 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -174,45 +174,47 @@ describe "IO.readlines" do $_.should == "test" end - describe "when passed a string that starts with a |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end - - lines = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines(cmd) - end - lines.should == ["hello\n", "line2\n"] - end + ruby_version_is ""..."4.0" do + describe "when passed a string that starts with a |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - platform_is_not :windows do - it "gets data from a fork when passed -" do lines = nil suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines("|-") + lines = IO.readlines(cmd) end + lines.should == ["hello\n", "line2\n"] + end - if lines # parent - lines.should == ["hello\n", "from a fork\n"] - else - puts "hello" - puts "from a fork" - exit! + platform_is_not :windows do + it "gets data from a fork when passed -" do + lines = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + lines = IO.readlines("|-") + end + + if lines # parent + lines.should == ["hello\n", "from a fork\n"] + else + puts "hello" + puts "from a fork" + exit! + end end end end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.readlines(cmd) - }.should complain(/IO process creation with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.readlines(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 2901b429c2..176c33cf9e 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' @@ -59,10 +59,10 @@ describe "IO#readpartial" do end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @wr.write("hello world") @wr.close - @rd.readpartial(11, buffer) + @rd.readpartial(11, buffer).should.equal?(buffer) buffer.should == "hello world" end @@ -74,7 +74,7 @@ describe "IO#readpartial" do end it "discards the existing buffer content upon error" do - buffer = 'hello' + buffer = +'hello' @wr.close -> { @rd.readpartial(1, buffer) }.should raise_error(EOFError) buffer.should be_empty @@ -93,12 +93,15 @@ describe "IO#readpartial" do @rd.readpartial(0).should == "" end - ruby_bug "#18421", ""..."3.0.4" do - it "clears and returns the given buffer if the length argument is 0" do - buffer = "existing content" - @rd.readpartial(0, buffer).should == buffer - buffer.should == "" - end + it "raises IOError if the stream is closed and the length argument is 0" do + @rd.close + -> { @rd.readpartial(0) }.should raise_error(IOError, "closed stream") + end + + it "clears and returns the given buffer if the length argument is 0" do + buffer = +"existing content" + @rd.readpartial(0, buffer).should == buffer + buffer.should == "" end it "preserves the encoding of the given buffer" do @@ -106,6 +109,7 @@ describe "IO#readpartial" do @wr.write("abc") @wr.close @rd.readpartial(10, buffer) + buffer.encoding.should == Encoding::ISO_8859_1 end end diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb index 1e4e50a81b..3893e7620f 100644 --- a/spec/ruby/core/io/select_spec.rb +++ b/spec/ruby/core/io/select_spec.rb @@ -114,6 +114,39 @@ describe "IO.select" do it "raises an ArgumentError when passed a negative timeout" do -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError) end + + describe "returns the available descriptors when the file descriptor" do + it "is in both read and error arrays" do + @wr.write("foobar") + result = IO.select([@rd], nil, [@rd]) + result.should == [[@rd], [], []] + end + + it "is in both write and error arrays" do + result = IO.select(nil, [@wr], [@wr]) + result.should == [[], [@wr], []] + end + + it "is in both read and write arrays" do + filename = tmp("IO_select_read_write_file") + w = File.open(filename, 'w+') + begin + IO.select([w], [w], []).should == [[w], [w], []] + ensure + w.close + rm_r filename + end + + IO.select([@wr], [@wr], []).should == [[], [@wr], []] + + @wr.write("foobar") + # CRuby on macOS returns [[@rd], [@rd], []], weird but we accept it here, probably only for pipe read-end + [ + [[@rd], [], []], + [[@rd], [@rd], []] + ].should.include? IO.select([@rd], [@rd], []) + end + end end describe "IO.select when passed nil for timeout" do diff --git a/spec/ruby/core/io/set_encoding_by_bom_spec.rb b/spec/ruby/core/io/set_encoding_by_bom_spec.rb index 92433d6640..30d5ce5a5a 100644 --- a/spec/ruby/core/io/set_encoding_by_bom_spec.rb +++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb @@ -62,11 +62,11 @@ describe "IO#set_encoding_by_bom" do @io.rewind @io.set_encoding(Encoding::ASCII_8BIT) - File.binwrite(@name, "\xFE\xFFabc") + File.binwrite(@name, "\xFE\xFFabcd") @io.set_encoding_by_bom.should == Encoding::UTF_16BE @io.external_encoding.should == Encoding::UTF_16BE - @io.read.b.should == "abc".b + @io.read.b.should == "abcd".b end it "returns the result encoding if found BOM UTF_32LE sequence" do @@ -94,11 +94,11 @@ describe "IO#set_encoding_by_bom" do @io.rewind @io.set_encoding(Encoding::ASCII_8BIT) - File.binwrite(@name, "\x00\x00\xFE\xFFabc") + File.binwrite(@name, "\x00\x00\xFE\xFFabcd") @io.set_encoding_by_bom.should == Encoding::UTF_32BE @io.external_encoding.should == Encoding::UTF_32BE - @io.read.b.should == "abc".b + @io.read.b.should == "abcd".b end it "returns nil if io is empty" do diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb index aca622834f..0747f31b8a 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -202,20 +202,10 @@ describe :io_each, shared: true do end describe "when passed chomp and nil as a separator" do - ruby_version_is "3.2" do - it "yields self's content" do - @io.pos = 100 - @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] - end - end - - ruby_version_is ""..."3.2" do - it "yields self's content without trailing new line character" do - @io.pos = 100 - @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six."] - end + it "yields self's content" do + @io.pos = 100 + @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] end end diff --git a/spec/ruby/core/io/shared/gets_ascii.rb b/spec/ruby/core/io/shared/gets_ascii.rb index 2a8fe3c9a5..2bd5470d99 100644 --- a/spec/ruby/core/io/shared/gets_ascii.rb +++ b/spec/ruby/core/io/shared/gets_ascii.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :io_gets_ascii, shared: true do describe "with ASCII separator" do before :each do diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb index cba5f33ebf..e84133493c 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -208,6 +208,26 @@ describe :io_new, shared: true do @io.internal_encoding.to_s.should == 'IBM866' end + it "does not use binary encoding when mode encoding is specified along with binmode: true option" do + @io = IO.send(@method, @fd, 'w:iso-8859-1', binmode: true) + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when mode encoding is specified" do + @io = IO.send(@method, @fd, 'w:ascii-8bit', textmode: true) + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + + it "does not use binmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', binmode: true, external_encoding: 'iso-8859-1') + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', textmode: true, external_encoding: 'ascii-8bit') + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + it "raises ArgumentError for nil options" do -> { IO.send(@method, @fd, 'w', nil) @@ -325,6 +345,9 @@ describe :io_new_errors, shared: true do @io = IO.send(@method, @fd, 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) -> { + @io = IO.send(@method, @fd, 'w:ISO-8859-1', internal_encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) + -> { @io = IO.send(@method, @fd, 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb index d2b604bba3..6c1fa11a59 100644 --- a/spec/ruby/core/io/shared/readlines.rb +++ b/spec/ruby/core/io/shared/readlines.rb @@ -99,7 +99,7 @@ describe :io_readlines_options_19, shared: true do end it "accepts non-ASCII data as separator" do - result = IO.send(@method, @name, "\303\250".force_encoding("utf-8"), &@object) + result = IO.send(@method, @name, "\303\250".dup.force_encoding("utf-8"), &@object) (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator end end diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index e7f63cefec..d56a27b3af 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -21,25 +21,25 @@ describe "IO#sysread on a file" do end it "reads the specified number of bytes from the file to the buffer" do - buf = "" # empty buffer + buf = +"" # empty buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" # small buffer + buf = +"ABCDE" # small buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" * 5 # large buffer + buf = +"ABCDE" * 5 # large buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" (obj = mock("buff")).should_receive(:to_str).any_number_of_times.and_return(buf) @file.sysread(15, obj).should == buf buf.should == "012345678901234" @@ -90,23 +90,30 @@ describe "IO#sysread on a file" do end it "immediately returns the given buffer if the length argument is 0" do - buffer = "existing content" + buffer = +"existing content" @file.sysread(0, buffer).should == buffer buffer.should == "existing content" end it "discards the existing buffer content upon successful read" do - buffer = "existing content" - @file.sysread(11, buffer) + buffer = +"existing content" + @file.sysread(11, buffer).should.equal?(buffer) buffer.should == "01234567890" end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @file.seek(0, :END) -> { @file.sysread(1, buffer) }.should raise_error(EOFError) buffer.should be_empty end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + string = @file.sysread(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end end describe "IO#sysread" do @@ -124,9 +131,7 @@ describe "IO#sysread" do @read.sysread(3).should == "ab" end - guard_not -> { platform_is :windows and ruby_version_is ""..."3.2" } do # https://bugs.ruby-lang.org/issues/18880 - it "raises ArgumentError when length is less than 0" do - -> { @read.sysread(-1) }.should raise_error(ArgumentError) - end + it "raises ArgumentError when length is less than 0" do + -> { @read.sysread(-1) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb index c403c864fd..5bfc690f9b 100644 --- a/spec/ruby/core/io/write_nonblock_spec.rb +++ b/spec/ruby/core/io/write_nonblock_spec.rb @@ -43,7 +43,7 @@ platform_is_not :windows do it "checks if the file is writable if writing zero bytes" do -> { - @readonly_file.write_nonblock("") + @readonly_file.write_nonblock("") }.should raise_error(IOError) end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 4a26f8dbaf..e58100f846 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -220,7 +220,7 @@ describe "IO.write" do end end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do -> { |
