diff options
Diffstat (limited to 'spec/ruby/core/io/read_spec.rb')
| -rw-r--r-- | spec/ruby/core/io/read_spec.rb | 487 |
1 files changed, 303 insertions, 184 deletions
diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 223e3cde06..dd787c9b60 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "IO.read" do before :each do @@ -23,32 +23,63 @@ describe "IO.read" do IO.read(p) end + # https://bugs.ruby-lang.org/issues/19354 + it "accepts options as keyword arguments" do + IO.read(@fname, 3, 0, mode: "r+").should == @contents[0, 3] + + -> { + IO.read(@fname, 3, 0, {mode: "r+"}) + }.should.raise(ArgumentError, /wrong number of arguments/) + end + it "accepts an empty options Hash" do - IO.read(@fname, {}).should == @contents + IO.read(@fname, **{}).should == @contents + end + + it "accepts a length, and empty options Hash" do + IO.read(@fname, 3, **{}).should == @contents[0, 3] end it "accepts a length, offset, and empty options Hash" do - IO.read(@fname, 3, 0, {}).should == @contents[0, 3] + IO.read(@fname, 3, 0, **{}).should == @contents[0, 3] end it "raises an IOError if the options Hash specifies write mode" do - lambda { IO.read(@fname, 3, 0, {mode: "w"}) }.should raise_error(IOError) + -> { IO.read(@fname, 3, 0, mode: "w") }.should.raise(IOError) end it "raises an IOError if the options Hash specifies append only mode" do - lambda { IO.read(@fname, {mode: "a"}) }.should raise_error(IOError) + -> { IO.read(@fname, mode: "a") }.should.raise(IOError) end it "reads the file if the options Hash includes read mode" do - IO.read(@fname, {mode: "r"}).should == @contents + IO.read(@fname, mode: "r").should == @contents end it "reads the file if the options Hash includes read/write mode" do - IO.read(@fname, {mode: "r+"}).should == @contents + IO.read(@fname, mode: "r+").should == @contents end it "reads the file if the options Hash includes read/write append mode" do - IO.read(@fname, {mode: "a+"}).should == @contents + IO.read(@fname, mode: "a+").should == @contents + end + + platform_is_not :windows do + end + + it "disregards other options if :open_args is given" do + string = IO.read(@fname,mode: "w", encoding: Encoding::UTF_32LE, open_args: ["r", encoding: Encoding::UTF_8]) + string.encoding.should == Encoding::UTF_8 + end + + it "doesn't require mode to be specified in :open_args" do + string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII}]) + string.encoding.should == Encoding::US_ASCII + end + + it "doesn't require mode to be specified in :open_args even if flags option passed" do + string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII, flags: File::CREAT}]) + string.encoding.should == Encoding::US_ASCII end it "treats second nil argument as no length limit" do @@ -73,86 +104,125 @@ describe "IO.read" do IO.read(@fname, 1, 10).should == nil end + it "returns an empty string when reading zero bytes" do + IO.read(@fname, 0).should == '' + end + + it "returns a String in BINARY when passed a size" do + IO.read(@fname, 1).encoding.should == Encoding::BINARY + IO.read(@fname, 0).encoding.should == Encoding::BINARY + end + it "raises an Errno::ENOENT when the requested file does not exist" do rm_r @fname - lambda { IO.read @fname }.should raise_error(Errno::ENOENT) + -> { IO.read @fname }.should.raise(Errno::ENOENT) end it "raises a TypeError when not passed a String type" do - lambda { IO.read nil }.should raise_error(TypeError) + -> { IO.read nil }.should.raise(TypeError) end it "raises an ArgumentError when not passed a valid length" do - lambda { IO.read @fname, -1 }.should raise_error(ArgumentError) + -> { IO.read @fname, -1 }.should.raise(ArgumentError) end - it "raises an Errno::EINVAL when not passed a valid offset" do - lambda { IO.read @fname, 0, -1 }.should raise_error(Errno::EINVAL) - lambda { IO.read @fname, -1, -1 }.should raise_error(Errno::EINVAL) + it "raises an ArgumentError when not passed a valid offset" do + -> { IO.read @fname, 0, -1 }.should.raise(ArgumentError) + -> { IO.read @fname, -1, -1 }.should.raise(ArgumentError) end - with_feature :encoding do - it "uses the external encoding specified via the :external_encoding option" do - str = IO.read(@fname, external_encoding: Encoding::ISO_8859_1) - str.encoding.should == Encoding::ISO_8859_1 - end + it "uses the external encoding specified via the :external_encoding option" do + str = IO.read(@fname, external_encoding: Encoding::ISO_8859_1) + str.encoding.should == Encoding::ISO_8859_1 + end - it "uses the external encoding specified via the :encoding option" do - str = IO.read(@fname, encoding: Encoding::ISO_8859_1) - str.encoding.should == Encoding::ISO_8859_1 + it "uses the external encoding specified via the :encoding option" do + str = IO.read(@fname, encoding: Encoding::ISO_8859_1) + str.encoding.should == Encoding::ISO_8859_1 + end + + platform_is :windows do + it "reads the file in text mode" do + # 0x1A is CTRL+Z and is EOF in Windows text mode. + File.binwrite(@fname, "\x1Abbb") + IO.read(@fname).should.empty? end 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" +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 + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd).should == "hello\n" + end end - IO.read(cmd).should == "hello\n" - end - with_feature :fork do - it "opens a pipe to a fork if the rest is -" do - str = IO.read("|-") - 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" + 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" + end end - IO.read(cmd, 1).should == "h" - end - platform_is_not :windows do - it "raises Errno::ESPIPE if passed an offset" do - lambda { - IO.read("|sh -c 'echo hello'", 1, 1) - }.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(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 - lambda { - IO.read("|cmd.exe /C echo hello", 1, 1) - }.should raise_error(Errno::EINVAL) + 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(Errno::EINVAL) + end + end + end + + # 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 describe "IO.read on an empty file" do before :each do @@ -195,26 +265,58 @@ describe "IO#read" do @io.read(4).should == '7890' end + it "treats first nil argument as no length limit" do + @io.read(nil).should == @contents + end + + it "raises an ArgumentError when not passed a valid length" do + -> { @io.read(-1) }.should.raise(ArgumentError) + end + 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' + + @io.read(nil, buf).should == "" + + buf.should == '' + + buf = +'non-empty string' + + @io.read(0, buf).should == "" + + buf.should == '' + end + + it "raise FrozenError if the output buffer is frozen" do + @io.read + -> { @io.read(0, 'frozen-string'.freeze) }.should.raise(FrozenError) + -> { @io.read(1, 'frozen-string'.freeze) }.should.raise(FrozenError) + -> { @io.read(nil, 'frozen-string'.freeze) }.should.raise(FrozenError) + end + + it "raise FrozenError if the output buffer is frozen (2)" do + @io.read + -> { @io.read(1, ''.freeze) }.should.raise(FrozenError) end it "consumes zero bytes when reading zero bytes" 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 @io.read - @io.eof?.should == true + @io.should.eof? end it "reads the contents of a file" do @@ -222,50 +324,72 @@ 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 - @io.read(nil, buf).object_id.should == buf.object_id + it "returns the given buffer when there is nothing to read" do + 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) - @io.read(15, obj).object_id.should_not == obj.object_id + @io.read(15, obj).should_not.equal? obj buf.should == @contents end @@ -299,9 +423,12 @@ describe "IO#read" do end it "raises IOError on closed stream" do - lambda { IOSpecs.closed_io.read }.should raise_error(IOError) + -> { IOSpecs.closed_io.read }.should.raise(IOError) end + it "raises ArgumentError when length is less than 0" do + -> { @io.read(-1) }.should.raise(ArgumentError) + end platform_is_not :windows do it "raises IOError when stream is closed by another thread" do @@ -317,7 +444,7 @@ describe "IO#read" do Thread.pass until t.stop? r.close t.join - t.value.should be_kind_of(IOError) + t.value.should.is_a?(IOError) w.close end end @@ -378,16 +505,9 @@ describe "IO#read in binary mode" do result = File.open(@name, "rb") { |f| f.read }.chomp - result.encoding.should == Encoding::ASCII_8BIT - xE2 = [226].pack('C*') - result.should == ("abc" + xE2 + "def").force_encoding(Encoding::ASCII_8BIT) - end - - it "does not transcode file contents when an internal encoding is specified" do - result = File.open(@name, "r:binary:utf-8") { |f| f.read }.chomp - result.encoding.should == Encoding::ASCII_8BIT + result.encoding.should == Encoding::BINARY xE2 = [226].pack('C*') - result.should == ("abc" + xE2 + "def").force_encoding(Encoding::ASCII_8BIT) + result.should == ("abc" + xE2 + "def").force_encoding(Encoding::BINARY) end end @@ -418,169 +538,168 @@ describe "IO.read with BOM" do it "reads a file without a bom" do name = fixture __FILE__, "no_bom_UTF-8.txt" result = File.read(name, mode: "rb:BOM|utf-8") - result.force_encoding("ascii-8bit").should == "UTF-8\n" + result.force_encoding("binary").should == "UTF-8\n" end it "reads a file with a utf-8 bom" do name = fixture __FILE__, "bom_UTF-8.txt" result = File.read(name, mode: "rb:BOM|utf-16le") - result.force_encoding("ascii-8bit").should == "UTF-8\n" + result.force_encoding("binary").should == "UTF-8\n" end it "reads a file with a utf-16le bom" do name = fixture __FILE__, "bom_UTF-16LE.txt" result = File.read(name, mode: "rb:BOM|utf-8") - result.force_encoding("ascii-8bit").should == "U\x00T\x00F\x00-\x001\x006\x00L\x00E\x00\n\x00" + result.force_encoding("binary").should == "U\x00T\x00F\x00-\x001\x006\x00L\x00E\x00\n\x00" end it "reads a file with a utf-16be bom" do name = fixture __FILE__, "bom_UTF-16BE.txt" result = File.read(name, mode: "rb:BOM|utf-8") - result.force_encoding("ascii-8bit").should == "\x00U\x00T\x00F\x00-\x001\x006\x00B\x00E\x00\n" + result.force_encoding("binary").should == "\x00U\x00T\x00F\x00-\x001\x006\x00B\x00E\x00\n" end it "reads a file with a utf-32le bom" do name = fixture __FILE__, "bom_UTF-32LE.txt" result = File.read(name, mode: "rb:BOM|utf-8") - result.force_encoding("ascii-8bit").should == "U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00L\x00\x00\x00E\x00\x00\x00\n\x00\x00\x00" + result.force_encoding("binary").should == "U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00L\x00\x00\x00E\x00\x00\x00\n\x00\x00\x00" end it "reads a file with a utf-32be bom" do name = fixture __FILE__, "bom_UTF-32BE.txt" result = File.read(name, mode: "rb:BOM|utf-8") - result.force_encoding("ascii-8bit").should == "\x00\x00\x00U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00B\x00\x00\x00E\x00\x00\x00\n" + result.force_encoding("binary").should == "\x00\x00\x00U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00B\x00\x00\x00E\x00\x00\x00\n" end end -with_feature :encoding do - describe :io_read_internal_encoding, shared: true do - it "returns a transcoded String" do - @io.read.should == "ありがとう\n" +describe :io_read_internal_encoding, shared: true do + it "returns a transcoded String" do + @io.read.should == "ありがとう\n" + end + + it "sets the String encoding to the internal encoding" do + @io.read.encoding.should.equal?(Encoding::UTF_8) + end + + describe "when passed nil for limit" do + it "sets the buffer to a transcoded String" do + result = @io.read(nil, buf = +"") + buf.should.equal?(result) + buf.should == "ありがとう\n" end - it "sets the String encoding to the internal encoding" do - @io.read.encoding.should equal(Encoding::UTF_8) + it "sets the buffer's encoding to the internal encoding" do + buf = "".dup.force_encoding Encoding::ISO_8859_1 + @io.read(nil, buf) + buf.encoding.should.equal?(Encoding::UTF_8) end + end +end - describe "when passed nil for limit" do - it "sets the buffer to a transcoded String" do - result = @io.read(nil, buf = "") - buf.should equal(result) - buf.should == "ありがとう\n" - end +describe :io_read_size_internal_encoding, shared: true do + it "reads bytes when passed a size" do + @io.read(2).should == [164, 162].pack('C*').force_encoding(Encoding::BINARY) + end - it "sets the buffer's encoding to the internal encoding" do - buf = "".force_encoding Encoding::ISO_8859_1 - @io.read(nil, buf) - buf.encoding.should equal(Encoding::UTF_8) - end - end + it "returns a String in BINARY when passed a size" do + @io.read(4).encoding.should.equal?(Encoding::BINARY) + @io.read(0).encoding.should.equal?(Encoding::BINARY) end - describe :io_read_size_internal_encoding, shared: true do - it "reads bytes when passed a size" do - @io.read(2).should == [164, 162].pack('C*').force_encoding(Encoding::ASCII_8BIT) - end + it "does not change the buffer's encoding when passed a limit" do + 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 "returns a String in ASCII-8BIT when passed a size" do - @io.read(4).encoding.should equal(Encoding::ASCII_8BIT) - end + it "truncates the buffer but does not change the buffer's encoding when no data remains" do + buf = "abc".dup.force_encoding Encoding::ISO_8859_1 + @io.read + + @io.read(1, buf).should == nil + buf.size.should == 0 + buf.encoding.should.equal?(Encoding::ISO_8859_1) + end +end - it "does not change the buffer's encoding when passed a limit" do - buf = "".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) +describe "IO#read" do + describe "when IO#external_encoding and IO#internal_encoding are nil" do + before :each do + @name = tmp("io_read.txt") + touch(@name) { |f| f.write "\x00\x01\x02" } + @io = new_io @name, "r+" end - it "trucates the buffer but does not change the buffer's encoding when no data remains" do - buf = "abc".force_encoding Encoding::ISO_8859_1 - @io.read + after :each do + @io.close if @io + rm_r @name + end - @io.read(1, buf).should be_nil - buf.size.should == 0 - buf.encoding.should equal(Encoding::ISO_8859_1) + it "sets the String encoding to Encoding.default_external" do + @io.read.encoding.should.equal?(Encoding.default_external) end end - describe "IO#read" do - describe "when IO#external_encoding and IO#internal_encoding are nil" do - before :each do - @name = tmp("io_read.txt") - touch(@name) { |f| f.write "\x00\x01\x02" } - @io = new_io @name, "r+" - end + describe "with internal encoding" do + after :each do + @io.close if @io + end - after :each do - @io.close if @io - rm_r @name + describe "not specified" do + before :each do + @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp" end - it "sets the String encoding to Encoding.default_external" do - @io.read.encoding.should equal(Encoding.default_external) + it "does not transcode the String" do + @io.read.should == ("ありがとう\n").encode(Encoding::EUC_JP) end - end - describe "with internal encoding" do - after :each do - @io.close if @io + it "sets the String encoding to the external encoding" do + @io.read.encoding.should.equal?(Encoding::EUC_JP) end - describe "not specified" do - before :each do - @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp" - end - - it "does not transcode the String" do - @io.read.should == ("ありがとう\n").encode(Encoding::EUC_JP) - end - - it "sets the String encoding to the external encoding" do - @io.read.encoding.should equal(Encoding::EUC_JP) - end + it_behaves_like :io_read_size_internal_encoding, nil + end - it_behaves_like :io_read_size_internal_encoding, nil + describe "specified by open mode" do + before :each do + @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8" end - describe "specified by open mode" do - before :each do - @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8" - end + it_behaves_like :io_read_internal_encoding, nil + it_behaves_like :io_read_size_internal_encoding, nil + end - it_behaves_like :io_read_internal_encoding, nil - it_behaves_like :io_read_size_internal_encoding, nil + describe "specified by mode: option" do + before :each do + @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8" end - describe "specified by mode: option" do - before :each do - @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8" - end + it_behaves_like :io_read_internal_encoding, nil + it_behaves_like :io_read_size_internal_encoding, nil + end - it_behaves_like :io_read_internal_encoding, nil - it_behaves_like :io_read_size_internal_encoding, nil + describe "specified by internal_encoding: option" do + before :each do + options = { mode: "r", + internal_encoding: "utf-8", + external_encoding: "euc-jp" } + @io = IOSpecs.io_fixture "read_euc_jp.txt", options end - describe "specified by internal_encoding: option" do - before :each do - options = { mode: "r", - internal_encoding: "utf-8", - external_encoding: "euc-jp" } - @io = IOSpecs.io_fixture "read_euc_jp.txt", options - end + it_behaves_like :io_read_internal_encoding, nil + it_behaves_like :io_read_size_internal_encoding, nil + end - it_behaves_like :io_read_internal_encoding, nil - it_behaves_like :io_read_size_internal_encoding, nil + describe "specified by encoding: option" do + before :each do + options = { mode: "r", encoding: "euc-jp:utf-8" } + @io = IOSpecs.io_fixture "read_euc_jp.txt", options end - describe "specified by encoding: option" do - before :each do - options = { mode: "r", encoding: "euc-jp:utf-8" } - @io = IOSpecs.io_fixture "read_euc_jp.txt", options - end - - it_behaves_like :io_read_internal_encoding, nil - it_behaves_like :io_read_size_internal_encoding, nil - end + it_behaves_like :io_read_internal_encoding, nil + it_behaves_like :io_read_size_internal_encoding, nil end end end |
