Skip to content

Commit 255007f

Browse files
committed
[GR-18163] Fix handling options by some IO methods
PullRequest: truffleruby/3628
2 parents 360ec34 + e1a8ccf commit 255007f

File tree

7 files changed

+105
-30
lines changed

7 files changed

+105
-30
lines changed

lib/truffle/pathname.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ def binread(*args) IO.binread(@path, *args) end
822822
def write(...) IO.write(@path, ...) end
823823

824824
# See <tt>IO.binwrite</tt>. Returns the number of bytes written to the file.
825-
def binwrite(*args) IO.binwrite(@path, *args) end
825+
def binwrite(...) IO.binwrite(@path, ...) end
826826

827827
# See <tt>IO.readlines</tt>. Returns all the lines from the file.
828828
def readlines(...) IO.readlines(@path, ...) end

spec/ruby/core/io/read_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@
7979
string.encoding.should == Encoding::UTF_8
8080
end
8181

82+
it "doesn't require mode to be specified in :open_args" do
83+
string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII}])
84+
string.encoding.should == Encoding::US_ASCII
85+
end
86+
87+
it "doesn't require mode to be specified in :open_args even if flags option passed" do
88+
string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII, flags: File::CREAT}])
89+
string.encoding.should == Encoding::US_ASCII
90+
end
91+
8292
it "treats second nil argument as no length limit" do
8393
IO.read(@fname, nil).should == @contents
8494
IO.read(@fname, nil, 5).should == IO.read(@fname, @contents.length, 5)

spec/ruby/core/io/readlines_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@
127127
end
128128
end
129129
end
130+
131+
describe "when passed arbitrary keyword argument" do
132+
it "tolerates it" do
133+
@io.readlines(chomp: true, foo: :bar).should == IOSpecs.lines_without_newline_characters
134+
end
135+
end
130136
end
131137

132138
describe "IO#readlines" do

spec/ruby/core/io/shared/binwrite.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@
2121
IO.send(@method, @filename, "abcde").should == 5
2222
end
2323

24+
ruby_version_is "3.0" do
25+
it "accepts options as a keyword argument" do
26+
IO.send(@method, @filename, "hi", 0, flags: File::CREAT).should == 2
27+
28+
-> {
29+
IO.send(@method, @filename, "hi", 0, {flags: File::CREAT})
30+
}.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 2..3)")
31+
end
32+
end
33+
2434
it "creates a file if missing" do
2535
fn = @filename + "xxx"
2636
begin
@@ -67,6 +77,11 @@
6777
File.read(@filename).should == "\0\0foo"
6878
end
6979

80+
it "accepts a :flags option without :mode one" do
81+
IO.send(@method, @filename, "hello, world!", flags: File::CREAT)
82+
File.read(@filename).should == "hello, world!"
83+
end
84+
7085
it "raises an error if readonly mode is specified" do
7186
-> { IO.send(@method, @filename, "abcde", mode: "r") }.should raise_error(IOError)
7287
end

spec/ruby/core/io/write_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@
8181
File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
8282
end
8383

84+
it "uses the encoding from the given option for non-ascii encoding even if in binary mode" do
85+
File.open(@filename, "w", encoding: Encoding::UTF_32LE, binmode: true) do |file|
86+
file.should.binmode?
87+
file.write("hi").should == 8
88+
end
89+
File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
90+
91+
File.open(@filename, "wb", encoding: Encoding::UTF_32LE) do |file|
92+
file.should.binmode?
93+
file.write("hi").should == 8
94+
end
95+
File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
96+
end
97+
8498
it "uses the encoding from the given option for non-ascii encoding when multiple arguments passes" do
8599
File.open(@filename, "w", encoding: Encoding::UTF_32LE) do |file|
86100
file.write("h", "i").should == 8
@@ -131,10 +145,38 @@
131145
File.read(@filename).should == "\0\0hi"
132146
end
133147

148+
it "requires mode to be specified in :open_args" do
149+
-> {
150+
IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true}])
151+
}.should raise_error(IOError, "not opened for writing")
152+
153+
IO.write(@filename, 'hi', open_args: ["w", {encoding: Encoding::UTF_32LE, binmode: true}]).should == 8
154+
IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, mode: "w"}]).should == 8
155+
end
156+
157+
it "requires mode to be specified in :open_args even if flags option passed" do
158+
-> {
159+
IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT}])
160+
}.should raise_error(IOError, "not opened for writing")
161+
162+
IO.write(@filename, 'hi', open_args: ["w", {encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT}]).should == 8
163+
IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT, mode: "w"}]).should == 8
164+
end
165+
134166
it "uses the given encoding and returns the number of bytes written" do
135167
IO.write(@filename, 'hi', mode: "w", encoding: Encoding::UTF_32LE).should == 8
136168
end
137169

170+
it "raises ArgumentError if encoding is specified in mode parameter and is given as :encoding option" do
171+
-> {
172+
IO.write(@filename, 'hi', mode: "w:UTF-16LE:UTF-16BE", encoding: Encoding::UTF_32LE)
173+
}.should raise_error(ArgumentError, "encoding specified twice")
174+
175+
-> {
176+
IO.write(@filename, 'hi', mode: "w:UTF-16BE", encoding: Encoding::UTF_32LE)
177+
}.should raise_error(ArgumentError, "encoding specified twice")
178+
end
179+
138180
it "writes the file with the permissions in the :perm parameter" do
139181
rm_r @filename
140182
IO.write(@filename, 'write :perm spec', mode: "w", perm: 0o755).should == 16

spec/tags/core/io/read_tags.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
fails:IO.read from a pipe opens a pipe to a fork if the rest is -
2-
fails(hangs):IO#read raises IOError when stream is closed by another thread
3-
fails:IO#read raises ArgumentError when length is less than 0
4-
fails:IO.read uses an :open_args option
5-
fails:IO.read disregards other options if :open_args is given
2+
fails(hangs):IO#read raises IOError when stream is closed by another thread

src/main/ruby/truffleruby/core/io.rb

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -329,20 +329,14 @@ def self.binread(file, length=nil, offset=0)
329329
end
330330
end
331331

332-
def self.binwrite(file, string, *args)
333-
offset, opts = args
334-
opts ||= {}
335-
if Primitive.object_kind_of?(offset, Hash)
336-
offset, opts = nil, offset
337-
end
332+
def self.binwrite(file, string, offset=nil, **options)
333+
default_mode = File::CREAT | File::RDWR | File::BINARY
334+
default_mode |= File::TRUNC unless offset
335+
336+
mode, _binary, external, _internal, _autoclose, perm = IO.normalize_options(nil, nil, options, default_mode)
338337

339-
mode, _binary, external, _internal, _autoclose, perm = IO.normalize_options(nil, nil, opts)
340-
unless mode
341-
mode = File::CREAT | File::RDWR | File::BINARY
342-
mode |= File::TRUNC unless offset
343-
end
344338
File.open(file, mode, encoding: (external || 'ASCII-8BIT'), perm: perm) do |f|
345-
f.seek(offset || 0)
339+
f.seek(offset) if offset
346340
f.write(string)
347341
end
348342
end
@@ -501,11 +495,10 @@ def self.read_encode(io, str)
501495

502496
def self.write(file, string, offset=nil, **options)
503497
if Primitive.nil?(options[:open_args])
504-
mode, _binary, external, _internal, _autoclose, perm = IO.normalize_options(nil, nil, options)
505-
unless mode
506-
mode = File::CREAT | File::WRONLY
507-
mode |= File::TRUNC unless offset
508-
end
498+
default_mode = File::CREAT | File::WRONLY
499+
default_mode |= File::TRUNC unless offset
500+
501+
mode, _binary, external, _internal, _autoclose, perm = IO.normalize_options(nil, nil, options, default_mode)
509502

510503
open_args = [mode]
511504
open_kw = { encoding: (external || 'ASCII-8BIT'), perm: perm }
@@ -527,7 +520,6 @@ def self.for_fd(fd, mode=nil, **options)
527520
def self.read(name, length=nil, offset=0, **options)
528521
offset = 0 if Primitive.nil? offset
529522
name = Truffle::Type.coerce_to_path name
530-
mode = options.delete(:mode) || 'r'
531523

532524
offset = Primitive.rb_to_int(offset || 0)
533525
raise Errno::EINVAL, 'offset must not be negative' if offset < 0
@@ -537,12 +529,20 @@ def self.read(name, length=nil, offset=0, **options)
537529
raise ArgumentError, 'length must not be negative' if length < 0
538530
end
539531

532+
if Primitive.nil?(options[:open_args])
533+
file_new_args = [name]
534+
file_new_options = options
535+
else
536+
file_new_args = [name] + options[:open_args]
537+
file_new_options = Primitive.object_kind_of?(file_new_args.last, Hash) ? file_new_args.pop : {}
538+
end
539+
540540
# Detect pipe mode
541541
if name[0] == ?|
542542
io = IO.popen(name[1..-1], 'r')
543543
return nil unless io # child process
544544
else
545-
io = File.new(name, mode, **options)
545+
io = File.new(*file_new_args, **file_new_options)
546546
end
547547

548548
str = nil
@@ -565,7 +565,7 @@ def self.try_convert(obj)
565565
Truffle::Type.rb_check_convert_type obj, IO, :to_io
566566
end
567567

568-
def self.normalize_options(mode, perm, options)
568+
def self.normalize_options(mode, perm, options, default_mode=nil)
569569
autoclose = true
570570

571571
if mode
@@ -584,6 +584,7 @@ def self.normalize_options(mode, perm, options)
584584
end
585585

586586
mode ||= optmode
587+
mode ||= default_mode
587588

588589
if flags = options[:flags]
589590
flags = Truffle::Type.rb_convert_type(flags, Integer, :to_int)
@@ -611,6 +612,8 @@ def self.normalize_options(mode, perm, options)
611612
autoclose = Primitive.as_boolean(options[:autoclose]) if options.key?(:autoclose)
612613
end
613614

615+
mode ||= default_mode
616+
614617
if Primitive.object_kind_of?(mode, String)
615618
mode, external, internal = mode.split(':', 3)
616619
raise ArgumentError, 'invalid access mode' unless mode
@@ -1704,6 +1707,8 @@ def read(length=nil, buffer=nil)
17041707
return nil
17051708
end
17061709

1710+
raise ArgumentError, 'length must not be negative' if length < 0
1711+
17071712
str = +''
17081713
needed = length
17091714
while needed > 0 and not @ibuffer.exhausted?
@@ -1817,12 +1822,12 @@ def readline(sep_or_limit=$/, limit=nil, chomp: false)
18171822
#
18181823
# f = File.new("testfile")
18191824
# f.readlines[0] #=> "This is line one\n"
1820-
def readlines(sep_or_limit=$/, limit=nil, chomp: false)
1821-
ret = []
1822-
1825+
def readlines(sep_or_limit=$/, limit=nil, **options)
1826+
chomp = options.fetch(:chomp, false)
18231827
each_reader = create_each_reader(sep_or_limit, limit, chomp, true)
1824-
each_reader&.each { |line| ret << line }
18251828

1829+
ret = []
1830+
each_reader&.each { |line| ret << line }
18261831
ret
18271832
end
18281833

@@ -2319,7 +2324,7 @@ def write(*objects)
23192324

23202325
ensure_open_and_writable
23212326

2322-
if !binmode? && external_encoding && external_encoding != string.encoding && external_encoding != Encoding::BINARY
2327+
if external_encoding && external_encoding != string.encoding && external_encoding != Encoding::BINARY
23232328
unless string.ascii_only? && external_encoding.ascii_compatible?
23242329
string = string.encode(external_encoding)
23252330
end

0 commit comments

Comments
 (0)