Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4a7d459
Add spec for IO#binmode disabling newline conversion when reading
javanthropus Jul 22, 2026
976d046
Add specs for IO#getbyte after ungetc
javanthropus Jul 22, 2026
6f9fba9
Add specs for IO#internal_encoding with BINARY external encoding
javanthropus Jul 22, 2026
a8dd789
Add spec for IO#pwrite raising Errno::EINVAL on invalid offset
javanthropus Jul 22, 2026
d91bdf7
Update and add specs for IO#read_nonblock after ungetc and with 0 length
javanthropus Jul 22, 2026
40e859f
Add specs for IO#read after ungetc
javanthropus Jul 22, 2026
3a69b22
Add specs for IO#readbyte after ungetc
javanthropus Jul 22, 2026
f0a3b5f
Add spec for IO#readpartial raising IOError after ungetc with charact…
javanthropus Jul 22, 2026
46f9838
Add specs for IO#reopen argument checking and open modes
javanthropus Jul 22, 2026
538111f
Add spec for IO#seek clearing character buffer after ungetc
javanthropus Jul 22, 2026
950b6cc
Refactor IO#set_encoding specs and add missing edge cases
javanthropus Jul 22, 2026
09eb335
Add and update specs for IO#sysread with buffered IO
javanthropus Jul 22, 2026
18069f9
Add and update specs for IO#sysseek with buffered IO
javanthropus Jul 22, 2026
e1b1972
Add and refactor specs for IO#syswrite on pipes
javanthropus Jul 22, 2026
c9449cb
Add specs for IO#write with ASCII-8BIT encoding and newline conversion
javanthropus Jul 22, 2026
5694592
Omit IO#pwrite test for EINVAL result on Windows
javanthropus Jul 23, 2026
7008f3b
Remove guards and specs for pre-3.3 behaviors
javanthropus Jul 24, 2026
0749bb7
Add additional specs for binmode to verify behavior of other reader m…
javanthropus Jul 25, 2026
40f33d2
Move spec for #set_encoding and #internal_encoding behavior to set_en…
javanthropus Jul 25, 2026
b9fa9e5
Use a test value that should trigger an error for pwrite on all platf…
javanthropus Jul 25, 2026
5309d09
Remove erroneous, semi-duplicate code from set_encoding specs
javanthropus Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions core/io/binmode_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,142 @@
@io.binmode
@io.internal_encoding.should == nil
end

it "disables newline conversion for #read" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.read.should == data
end
Comment thread
andrykonchin marked this conversation as resolved.

it "disables newline conversion for #gets" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.gets.should == "line1\r\n"
@io.gets.should == "line2\r\n"
end

it "disables newline conversion for #readline" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.readline.should == "line1\r\n"
@io.readline.should == "line2\r\n"
end

it "disables newline conversion for #readlines" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.readlines.should == ["line1\r\n", "line2\r\n"]
end

it "disables newline conversion for #each" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.each.to_a.should == ["line1\r\n", "line2\r\n"]
end

it "disables newline conversion for #each_line" do
data = "line1\r\nline2\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.each_line.to_a.should == ["line1\r\n", "line2\r\n"]
end

it "disables newline conversion for #getc" do
data = "line1\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
5.times { @io.getc }
@io.getc.should == "\r"
@io.getc.should == "\n"
end

it "disables newline conversion for #readchar" do
data = "line1\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
5.times { @io.readchar }
@io.readchar.should == "\r"
@io.readchar.should == "\n"
end

it "disables newline conversion for #each_char" do
data = "line1\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.each_char.to_a.should == ["l", "i", "n", "e", "1", "\r", "\n"]
end

it "disables newline conversion for #each_codepoint" do
data = "line1\r\n"

@io = new_io(@name, "wb")
@io.write(data)
@io.close

@io = new_io(@name, "rt")
@io.set_encoding("utf-8:ISO-8859-1", newline: :universal)
@io.binmode
@io.each_codepoint.to_a.should == [108, 105, 110, 101, 49, 13, 10]
end
end

describe "IO#binmode?" do
Expand Down
16 changes: 16 additions & 0 deletions core/io/getbyte_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@
it "raises an IOError on closed stream" do
-> { IOSpecs.closed_io.getbyte }.should.raise(IOError)
end

it "reads after ungetc without character conversion" do
@io.set_encoding("utf-8")
c = @io.getc
@io.ungetc(c)
@io.getbyte.should == 86
end

it "raises an exception after ungetc with character conversion" do
@io.set_encoding("utf-8:utf-16be")
c = @io.getc
@io.ungetc(c)
-> do
@io.getbyte
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: this appears to be a CRuby-specific implementation detail (or an intentional design constraint) that other Ruby implementations, such as JRuby, TruffleRuby, or Natalie, might handle differently (e.g., by
permitting byte-oriented reading under these conditions).

CRuby already allows mixing byte-oriented and character-oriented read operations on streams with multibyte encodings (such as UTF-16BE). Semantically, calling ungetc does not make this behavior any worse or introduce additional issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this project documents the behavior of CRuby, and this is indeed a CRuby behavior. I've not tried it on other implementations. Is that a requirement now?

end

describe "IO#getbyte" do
Expand Down
5 changes: 5 additions & 0 deletions core/io/internal_encoding_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
@io = new_io @name, "#{@object}:binary"
@io.internal_encoding.should == nil
end

it "returns nil when the external encoding is BINARY and internal encoding is set" do
@io = new_io @name, "#{@object}:binary:ibm437"
@io.internal_encoding.should == nil
end
end
end

Expand Down
6 changes: 6 additions & 0 deletions core/io/pwrite_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
}.should.raise(NoMethodError, /undefined method [`']to_s'/)
end

it "raises a Errno::EINVAL if the offset is invalid" do
-> {
@file.pwrite("foo", -3)
}.should.raise(Errno::EINVAL)
end

it "raises a TypeError if the offset cannot be converted to an Integer" do
-> {
@file.pwrite("foo", Object.new)
Expand Down
14 changes: 12 additions & 2 deletions core/io/read_nonblock_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@
@read.read_nonblock(3).should == "bar"
end

it "raises an exception after ungetc with data in the buffer and character conversion enabled" do
it "raises an exception after ungetc with character conversion enabled" do
@write.write("foobar")
@read.set_encoding(
'utf-8', universal_newline: true
)
c = @read.getc
@read.ungetc(c)
-> { @read.read_nonblock(3).should == "foo" }.should.raise(IOError)
-> do
@read.read_nonblock(3)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it "returns less data if that is all that is available" do
Expand Down Expand Up @@ -137,6 +139,14 @@
-> { @read.read_nonblock(5) }.should.raise(EOFError)
end

ruby_bug "#18421", ""..."3.0.4" do
it "clears and returns the given buffer if the length argument is 0" do
buffer = String.new("existing content")
@read.read_nonblock(0, buffer).should == buffer
buffer.should == ""
end
end

it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@write.write("abc")
Expand Down
38 changes: 38 additions & 0 deletions core/io/read_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,12 @@
@io.read.encoding.should.equal?(Encoding::EUC_JP)
end

it "reads after ungetc" do
c = @io.getc
@io.ungetc(c)
@io.read(2).should == [164, 162].pack('C*').force_encoding(Encoding::BINARY)
end

it_behaves_like :io_read_size_internal_encoding, nil
end

Expand All @@ -680,6 +686,14 @@
@io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8"
end

it "raises an exception after ungetc" do
c = @io.getc
@io.ungetc(c)
-> do
@io.read(2)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it_behaves_like :io_read_internal_encoding, nil
it_behaves_like :io_read_size_internal_encoding, nil
end
Expand All @@ -689,6 +703,14 @@
@io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8"
end

it "raises an exception after ungetc" do
c = @io.getc
@io.ungetc(c)
-> do
@io.read(2)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it_behaves_like :io_read_internal_encoding, nil
it_behaves_like :io_read_size_internal_encoding, nil
end
Expand All @@ -701,6 +723,14 @@
@io = IOSpecs.io_fixture "read_euc_jp.txt", options
end

it "raises an exception after ungetc" do
c = @io.getc
@io.ungetc(c)
-> do
@io.read(2)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it_behaves_like :io_read_internal_encoding, nil
it_behaves_like :io_read_size_internal_encoding, nil
end
Expand All @@ -711,6 +741,14 @@
@io = IOSpecs.io_fixture "read_euc_jp.txt", options
end

it "raises an exception after ungetc" do
c = @io.getc
@io.ungetc(c)
-> do
@io.read(2)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it_behaves_like :io_read_internal_encoding, nil
it_behaves_like :io_read_size_internal_encoding, nil
end
Expand Down
16 changes: 16 additions & 0 deletions core/io/readbyte_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@
@io.readbyte
end.should.raise EOFError
end

it "reads after ungetc without character conversion" do
@io.set_encoding("utf-8")
c = @io.getc
@io.ungetc(c)
@io.readbyte.should == ?r.getbyte(0)
end

it "raises an exception after ungetc with character conversion" do
@io.set_encoding("utf-8:utf-16be")
c = @io.getc
@io.ungetc(c)
-> do
@io.readbyte
end.should.raise(IOError, "byte oriented read for character buffered IO")
end
end
10 changes: 10 additions & 0 deletions core/io/readpartial_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@
@rd.readpartial(2).should == "b"
end

it "raises an exception after ungetc with character conversion enabled" do
@wr.write("foobar")
@rd.set_encoding('utf-8', 'ascii')
c = @rd.getc
@rd.ungetc(c)
-> do
@rd.readpartial(3)
end.should.raise(IOError, "byte oriented read for character buffered IO")
end

it "discards the existing buffer content upon successful read" do
buffer = +"existing content"
@wr.write("hello world")
Expand Down
Loading