diff options
Diffstat (limited to 'spec/ruby/core')
33 files changed, 782 insertions, 44 deletions
diff --git a/spec/ruby/core/basicobject/instance_eval_spec.rb b/spec/ruby/core/basicobject/instance_eval_spec.rb index b6a146095d..350b08a30e 100644 --- a/spec/ruby/core/basicobject/instance_eval_spec.rb +++ b/spec/ruby/core/basicobject/instance_eval_spec.rb @@ -20,12 +20,18 @@ describe "BasicObject#instance_eval" do a.instance_eval('self').equal?(a).should be_true end - it "expects a block with no arguments" do - -> { "hola".instance_eval }.should raise_error(ArgumentError) + it "raises an ArgumentError when no arguments and no block are given" do + -> { "hola".instance_eval }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") end - it "takes no arguments with a block" do - -> { "hola".instance_eval(4, 5) {|a,b| a + b } }.should raise_error(ArgumentError) + it "raises an ArgumentError when a block and normal arguments are given" do + -> { "hola".instance_eval(4, 5) {|a,b| a + b } }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") + end + + it "raises an ArgumentError when more than 3 arguments are given" do + -> { + "hola".instance_eval("1 + 1", "some file", 0, "bogus") + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") end it "yields the object to the block" do @@ -185,4 +191,58 @@ end x.should == :value end + + it "converts string argument with #to_str method" do + source_code = Object.new + def source_code.to_str() "1" end + + a = BasicObject.new + a.instance_eval(source_code).should == 1 + end + + it "raises ArgumentError if returned value is not String" do + source_code = Object.new + def source_code.to_str() :symbol end + + a = BasicObject.new + -> { a.instance_eval(source_code) }.should raise_error(TypeError, /can't convert Object to String/) + end + + it "converts filename argument with #to_str method" do + filename = Object.new + def filename.to_str() "file.rb" end + + err = begin + Object.new.instance_eval("raise", filename) + rescue => e + e + end + err.backtrace.first.split(":")[0].should == "file.rb" + end + + it "raises ArgumentError if returned value is not String" do + filename = Object.new + def filename.to_str() :symbol end + + -> { Object.new.instance_eval("raise", filename) }.should raise_error(TypeError, /can't convert Object to String/) + end + + it "converts lineno argument with #to_int method" do + lineno = Object.new + def lineno.to_int() 15 end + + err = begin + Object.new.instance_eval("raise", "file.rb", lineno) + rescue => e + e + end + err.backtrace.first.split(":")[1].should == "15" + end + + it "raises ArgumentError if returned value is not Integer" do + lineno = Object.new + def lineno.to_int() :symbol end + + -> { Object.new.instance_eval("raise", "file.rb", lineno) }.should raise_error(TypeError, /can't convert Object to Integer/) + end end diff --git a/spec/ruby/core/enumerable/sum_spec.rb b/spec/ruby/core/enumerable/sum_spec.rb index 4a978794e5..fc173e4173 100644 --- a/spec/ruby/core/enumerable/sum_spec.rb +++ b/spec/ruby/core/enumerable/sum_spec.rb @@ -22,8 +22,21 @@ describe 'Enumerable#sum' do @enum.sum.should == 5/3r end - it 'takes a block to transform the elements' do - @enum.sum { |element| element * 2 }.should == 10/3r + context 'with a block' do + it 'transforms the elements' do + @enum.sum { |element| element * 2 }.should == 10/3r + end + + it 'does not destructure array elements' do + class << @enum + def each + yield [1,2] + yield [3] + end + end + + @enum.sum(&:last).should == 5 + end end # https://bugs.ruby-lang.org/issues/12217 diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb index b9f82f8133..07be99f400 100644 --- a/spec/ruby/core/io/gets_spec.rb +++ b/spec/ruby/core/io/gets_spec.rb @@ -213,6 +213,8 @@ describe "IO#gets" do it "returns empty string when 0 passed as a limit" do @io.gets(0).should == "" + @io.gets(nil, 0).should == "" + @io.gets("", 0).should == "" end end diff --git a/spec/ruby/core/io/print_spec.rb b/spec/ruby/core/io/print_spec.rb index 04e971ef6d..085852024c 100644 --- a/spec/ruby/core/io/print_spec.rb +++ b/spec/ruby/core/io/print_spec.rb @@ -3,16 +3,27 @@ require_relative 'fixtures/classes' describe "IO#print" do before :each do - @old_separator = $\ - suppress_warning {$\ = '->'} + @old_record_separator = $\ + @old_field_separator = $, + suppress_warning { + $\ = '->' + $, = '^^' + } @name = tmp("io_print") end after :each do - suppress_warning {$\ = @old_separator} + suppress_warning { + $\ = @old_record_separator + $, = @old_field_separator + } rm_r @name end + it "returns nil" do + touch(@name) { |f| f.print.should be_nil } + end + it "writes $_.to_s followed by $\\ (if any) to the stream if no arguments given" do o = mock('o') o.should_receive(:to_s).and_return("mockmockmock") @@ -38,13 +49,15 @@ describe "IO#print" do IO.read(@name).should == "hello#{$\}" end - it "writes each obj.to_s to the stream and appends $\\ (if any) given multiple objects" do + it "writes each obj.to_s to the stream separated by $, (if any) and appends $\\ (if any) given multiple objects" do o, o2 = Object.new, Object.new def o.to_s(); 'o'; end def o2.to_s(); 'o2'; end - touch(@name) { |f| f.print(o, o2) } - IO.read(@name).should == "#{o.to_s}#{o2.to_s}#{$\}" + suppress_warning { + touch(@name) { |f| f.print(o, o2) } + } + IO.read(@name).should == "#{o.to_s}#{$,}#{o2.to_s}#{$\}" end it "raises IOError on closed stream" do diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb index e50531d336..a62b75274c 100644 --- a/spec/ruby/core/io/read_nonblock_spec.rb +++ b/spec/ruby/core/io/read_nonblock_spec.rb @@ -55,6 +55,27 @@ describe "IO#read_nonblock" do @read.read_nonblock(4).should == "hell" end + it "reads after ungetc with data in the buffer" do + @write.write("foobar") + @read.set_encoding( + 'utf-8', universal_newline: false + ) + c = @read.getc + @read.ungetc(c) + @read.read_nonblock(3).should == "foo" + @read.read_nonblock(3).should == "bar" + end + + it "raises an exception after ungetc with data in the buffer and 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_error(IOError) + end + it "returns less data if that is all that is available" do @write << "hello" @read.read_nonblock(10).should == "hello" @@ -70,6 +91,10 @@ describe "IO#read_nonblock" do @read.read_nonblock(1).should == "1" end + it "raises ArgumentError when length is less than 0" do + -> { @read.read_nonblock(-1) }.should raise_error(ArgumentError) + end + it "reads into the passed buffer" do buffer = "" @write.write("1") @@ -84,6 +109,21 @@ describe "IO#read_nonblock" do output.should equal(buffer) end + it "discards the existing buffer content upon successful read" do + buffer = "existing content" + @write.write("hello world") + @write.close + @read.read_nonblock(11, buffer) + buffer.should == "hello world" + end + + it "discards the existing buffer content upon error" do + buffer = "existing content" + @write.close + -> { @read.read_nonblock(1, buffer) }.should raise_error(EOFError) + buffer.should be_empty + end + it "raises IOError on closed stream" do -> { IOSpecs.closed_io.read_nonblock(5) }.should raise_error(IOError) end @@ -96,4 +136,13 @@ describe "IO#read_nonblock" do -> { @read.read_nonblock(5) }.should raise_error(EOFError) end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @write.write("abc") + @write.close + @read.read_nonblock(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 28cab13340..d34f7bd0eb 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -270,6 +270,13 @@ describe "IO#read" do @io.read(nil, buf).should equal buf end + 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" obj = mock("buff") @@ -312,6 +319,9 @@ describe "IO#read" do -> { IOSpecs.closed_io.read }.should raise_error(IOError) end + it "raises ArgumentError when length is less than 0" do + -> { @io.read(-1) }.should raise_error(ArgumentError) + end platform_is_not :windows do it "raises IOError when stream is closed by another thread" do diff --git a/spec/ruby/core/io/readchar_spec.rb b/spec/ruby/core/io/readchar_spec.rb index b5f762a846..a66773851a 100644 --- a/spec/ruby/core/io/readchar_spec.rb +++ b/spec/ruby/core/io/readchar_spec.rb @@ -1,6 +1,16 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +describe :io_readchar_internal_encoding, shared: true do + it "returns a transcoded String" do + @io.readchar.should == "あ" + end + + it "sets the String encoding to the internal encoding" do + @io.readchar.encoding.should equal(Encoding::UTF_8) + end +end + describe "IO#readchar" do before :each do @io = IOSpecs.io_fixture "lines.txt" @@ -29,6 +39,62 @@ describe "IO#readchar" do end end +describe "IO#readchar with internal encoding" do + after :each do + @io.close if @io + 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.readchar.should == ("あ").encode(Encoding::EUC_JP) + end + + it "sets the String encoding to the external encoding" do + @io.readchar.encoding.should equal(Encoding::EUC_JP) + end + 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_readchar_internal_encoding, nil + 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_readchar_internal_encoding, nil + 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_readchar_internal_encoding, nil + 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_readchar_internal_encoding, nil + end +end + describe "IO#readchar" do before :each do @io = IOSpecs.io_fixture "empty.txt" diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 2b33a0d5b1..324ae0b6e6 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -59,7 +59,7 @@ describe "IO#readpartial" do end it "discards the existing buffer content upon successful read" do - buffer = "existing" + buffer = "existing content" @wr.write("hello world") @wr.close @rd.readpartial(11, buffer) @@ -93,4 +93,17 @@ describe "IO#readpartial" do @rd.readpartial(0).should == "" 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 + buffer = ''.encode(Encoding::ISO_8859_1) + @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/rewind_spec.rb b/spec/ruby/core/io/rewind_spec.rb index 649041afaf..5579cbd988 100644 --- a/spec/ruby/core/io/rewind_spec.rb +++ b/spec/ruby/core/io/rewind_spec.rb @@ -18,6 +18,17 @@ describe "IO#rewind" do @io.readline.should == "Voici la ligne une.\n" end + it "positions the instance to the beginning of output for write-only IO" do + name = tmp("io_rewind_spec") + io = File.open(name, "w") + io.write("Voici la ligne une.\n") + io.rewind + io.pos.should == 0 + ensure + io.close + rm_r name + end + it "positions the instance to the beginning of input and clears EOF" do value = @io.read @io.rewind @@ -32,6 +43,10 @@ describe "IO#rewind" do @io.lineno.should == 0 end + it "returns 0" do + @io.rewind.should == 0 + end + it "raises IOError on closed stream" do -> { IOSpecs.closed_io.rewind }.should raise_error(IOError) end 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 b52d3a943a..92433d6640 100644 --- a/spec/ruby/core/io/set_encoding_by_bom_spec.rb +++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb @@ -12,45 +12,232 @@ describe "IO#set_encoding_by_bom" do rm_r @name end + it "returns nil if not readable" do + not_readable_io = new_io(@name, 'wb') + + not_readable_io.set_encoding_by_bom.should be_nil + not_readable_io.external_encoding.should == Encoding::ASCII_8BIT + ensure + not_readable_io.close + end + it "returns the result encoding if found BOM UTF-8 sequence" do + File.binwrite(@name, "\u{FEFF}") + + @io.set_encoding_by_bom.should == Encoding::UTF_8 + @io.external_encoding.should == Encoding::UTF_8 + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + File.binwrite(@name, "\u{FEFF}abc") @io.set_encoding_by_bom.should == Encoding::UTF_8 @io.external_encoding.should == Encoding::UTF_8 + @io.read.b.should == "abc".b end it "returns the result encoding if found BOM UTF_16LE sequence" do + File.binwrite(@name, "\xFF\xFE") + + @io.set_encoding_by_bom.should == Encoding::UTF_16LE + @io.external_encoding.should == Encoding::UTF_16LE + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + File.binwrite(@name, "\xFF\xFEabc") @io.set_encoding_by_bom.should == Encoding::UTF_16LE @io.external_encoding.should == Encoding::UTF_16LE + @io.read.b.should == "abc".b end it "returns the result encoding if found BOM UTF_16BE sequence" do + File.binwrite(@name, "\xFE\xFF") + + @io.set_encoding_by_bom.should == Encoding::UTF_16BE + @io.external_encoding.should == Encoding::UTF_16BE + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + File.binwrite(@name, "\xFE\xFFabc") @io.set_encoding_by_bom.should == Encoding::UTF_16BE @io.external_encoding.should == Encoding::UTF_16BE + @io.read.b.should == "abc".b end it "returns the result encoding if found BOM UTF_32LE sequence" do + File.binwrite(@name, "\xFF\xFE\x00\x00") + + @io.set_encoding_by_bom.should == Encoding::UTF_32LE + @io.external_encoding.should == Encoding::UTF_32LE + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + File.binwrite(@name, "\xFF\xFE\x00\x00abc") @io.set_encoding_by_bom.should == Encoding::UTF_32LE @io.external_encoding.should == Encoding::UTF_32LE + @io.read.b.should == "abc".b end it "returns the result encoding if found BOM UTF_32BE sequence" do + File.binwrite(@name, "\x00\x00\xFE\xFF") + + @io.set_encoding_by_bom.should == Encoding::UTF_32BE + @io.external_encoding.should == Encoding::UTF_32BE + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + File.binwrite(@name, "\x00\x00\xFE\xFFabc") @io.set_encoding_by_bom.should == Encoding::UTF_32BE @io.external_encoding.should == Encoding::UTF_32BE + @io.read.b.should == "abc".b + end + + it "returns nil if io is empty" do + @io.set_encoding_by_bom.should be_nil + @io.external_encoding.should == Encoding::ASCII_8BIT + end + + it "returns nil if UTF-8 BOM sequence is incomplete" do + File.write(@name, "\xEF") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xEF".b + @io.rewind + + File.write(@name, "\xEFa") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xEFa".b + @io.rewind + + File.write(@name, "\xEF\xBB") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xEF\xBB".b + @io.rewind + + File.write(@name, "\xEF\xBBa") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xEF\xBBa".b + end + + it "returns nil if UTF-16BE BOM sequence is incomplete" do + File.write(@name, "\xFE") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xFE".b + @io.rewind + + File.write(@name, "\xFEa") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xFEa".b + end + + it "returns nil if UTF-16LE/UTF-32LE BOM sequence is incomplete" do + File.write(@name, "\xFF") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xFF".b + @io.rewind + + File.write(@name, "\xFFa") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\xFFa".b + end + + it "returns UTF-16LE if UTF-32LE BOM sequence is incomplete" do + File.write(@name, "\xFF\xFE") + + @io.set_encoding_by_bom.should == Encoding::UTF_16LE + @io.external_encoding.should == Encoding::UTF_16LE + @io.read.b.should == "".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + + File.write(@name, "\xFF\xFE\x00") + + @io.set_encoding_by_bom.should == Encoding::UTF_16LE + @io.external_encoding.should == Encoding::UTF_16LE + @io.read.b.should == "\x00".b + @io.rewind + @io.set_encoding(Encoding::ASCII_8BIT) + + File.write(@name, "\xFF\xFE\x00a") + + @io.set_encoding_by_bom.should == Encoding::UTF_16LE + @io.external_encoding.should == Encoding::UTF_16LE + @io.read.b.should == "\x00a".b + end + + it "returns nil if UTF-32BE BOM sequence is incomplete" do + File.write(@name, "\x00") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00".b + @io.rewind + + File.write(@name, "\x00a") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00a".b + @io.rewind + + File.write(@name, "\x00\x00") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00\x00".b + @io.rewind + + File.write(@name, "\x00\x00a") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00\x00a".b + @io.rewind + + File.write(@name, "\x00\x00\xFE") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00\x00\xFE".b + @io.rewind + + File.write(@name, "\x00\x00\xFEa") + + @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read.b.should == "\x00\x00\xFEa".b end it "returns nil if found BOM sequence not provided" do File.write(@name, "abc") @io.set_encoding_by_bom.should == nil + @io.external_encoding.should == Encoding::ASCII_8BIT + @io.read(3).should == "abc".b end it 'returns exception if io not in binary mode' do diff --git a/spec/ruby/core/io/set_encoding_spec.rb b/spec/ruby/core/io/set_encoding_spec.rb index 5aec6a96c3..bc448acfce 100644 --- a/spec/ruby/core/io/set_encoding_spec.rb +++ b/spec/ruby/core/io/set_encoding_spec.rb @@ -188,4 +188,21 @@ describe "IO#set_encoding" do @io.external_encoding.should == Encoding::UTF_8 @io.internal_encoding.should == Encoding::UTF_16BE end + + it "saves encoding options passed as a hash in the last argument" do + File.write(@name, "\xff") + io = File.open(@name) + io.set_encoding(Encoding::EUC_JP, Encoding::SHIFT_JIS, invalid: :replace, replace: ".") + io.read.should == "." + ensure + io.close + end + + it "raises ArgumentError when no arguments are given" do + -> { @io.set_encoding() }.should raise_error(ArgumentError) + end + + it "raises ArgumentError when too many arguments are given" do + -> { @io.set_encoding(1, 2, 3) }.should raise_error(ArgumentError) + end end diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb index 607e7de03e..1b8abce5ff 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -113,6 +113,13 @@ describe :io_each, shared: true do @io.send(@method, "") { |s| ScratchPad << s } ScratchPad.recorded.should == IOSpecs.paragraphs end + + it "discards leading newlines" do + @io.readline + @io.readline + @io.send(@method, "") { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] + end end describe "with both separator and limit" do @@ -152,6 +159,13 @@ describe :io_each, shared: true do @io.send(@method, "", 1024) { |s| ScratchPad << s } ScratchPad.recorded.should == IOSpecs.paragraphs end + + it "discards leading newlines" do + @io.readline + @io.readline + @io.send(@method, "", 1024) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] + end end end end @@ -220,6 +234,14 @@ describe :io_each, shared: true do ] end end + + describe "when passed too many arguments" do + it "raises ArgumentError" do + -> { + @io.send(@method, "", 1, "excess argument", chomp: true) {} + }.should raise_error(ArgumentError) + end + end end describe :io_each_default_separator, shared: true do diff --git a/spec/ruby/core/io/shared/write.rb b/spec/ruby/core/io/shared/write.rb index 270b84ac39..9503f94212 100644 --- a/spec/ruby/core/io/shared/write.rb +++ b/spec/ruby/core/io/shared/write.rb @@ -69,16 +69,6 @@ describe :io_write, shared: true do -> { IOSpecs.closed_io.send(@method, "hello") }.should raise_error(IOError) end - it "does not modify the passed argument" do - File.open(@filename, "w") do |f| - f.set_encoding(Encoding::IBM437) - # A character whose codepoint differs between UTF-8 and IBM437 - f.write "ƒ".freeze - end - - File.binread(@filename).bytes.should == [159] - end - describe "on a pipe" do before :each do @r, @w = IO.pipe diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index 8201ad47ca..e7f63cefec 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -6,7 +6,7 @@ describe "IO#sysread on a file" do @file_name = tmp("IO_sysread_file") + $$.to_s File.open(@file_name, "w") do |f| # write some stuff - f.write("012345678901234567890123456789") + f.write("012345678901234567890123456789\nabcdef") end @file = File.open(@file_name, "r+") end @@ -84,6 +84,29 @@ describe "IO#sysread on a file" do it "raises IOError on closed stream" do -> { IOSpecs.closed_io.sysread(5) }.should raise_error(IOError) end + + it "immediately returns an empty string if the length argument is 0" do + @file.sysread(0).should == "" + end + + it "immediately returns the given buffer if the length argument is 0" do + 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.should == "01234567890" + end + + it "discards the existing buffer content upon error" do + buffer = "existing content" + @file.seek(0, :END) + -> { @file.sysread(1, buffer) }.should raise_error(EOFError) + buffer.should be_empty + end end describe "IO#sysread" do @@ -100,4 +123,10 @@ describe "IO#sysread" do @write.syswrite "ab" @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 + end end diff --git a/spec/ruby/core/io/syswrite_spec.rb b/spec/ruby/core/io/syswrite_spec.rb index a28cc62174..eeb8d302a7 100644 --- a/spec/ruby/core/io/syswrite_spec.rb +++ b/spec/ruby/core/io/syswrite_spec.rb @@ -29,6 +29,16 @@ describe "IO#syswrite on a file" do end end + it "does not modify the passed argument" do + File.open(@filename, "w") do |f| + f.set_encoding(Encoding::IBM437) + # A character whose codepoint differs between UTF-8 and IBM437 + f.syswrite("ƒ".freeze) + end + + File.binread(@filename).bytes.should == [198, 146] + end + it "warns if called immediately after a buffered IO#write" do @file.write("abcde") -> { @file.syswrite("fghij") }.should complain(/syswrite/) diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb index a8b9ce0a36..5532556d8a 100644 --- a/spec/ruby/core/io/write_nonblock_spec.rb +++ b/spec/ruby/core/io/write_nonblock_spec.rb @@ -31,6 +31,16 @@ platform_is_not :windows do end end + it "does not modify the passed argument" do + File.open(@filename, "w") do |f| + f.set_encoding(Encoding::IBM437) + # A character whose codepoint differs between UTF-8 and IBM437 + f.write_nonblock("ƒ".freeze) + end + + File.binread(@filename).bytes.should == [198, 146] + end + it "checks if the file is writable if writing zero bytes" do -> { @readonly_file.write_nonblock("") diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index f29fdf3a01..bcc0582d9e 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -44,6 +44,16 @@ describe "IO#write on a file" do @file.write("hellø").should == 6 end + it "does not modify the passed argument" do + File.open(@filename, "w") do |f| + f.set_encoding(Encoding::IBM437) + # A character whose codepoint differs between UTF-8 and IBM437 + f.write("ƒ".freeze) + end + + File.binread(@filename).bytes.should == [159] + end + it "uses the encoding from the given option for non-ascii encoding" do File.open(@filename, "w", encoding: Encoding::UTF_32LE) do |file| file.write("hi").should == 8 diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb index 1bdd1740ca..eae191aa54 100644 --- a/spec/ruby/core/kernel/p_spec.rb +++ b/spec/ruby/core/kernel/p_spec.rb @@ -76,10 +76,8 @@ describe "Kernel#p" do -> { p(*[]) }.should output("") end -=begin Not sure how to spec this, but wanted to note the behavior here - it "does not flush if receiver is not a TTY or a File" do - end -=end + # Not sure how to spec this, but wanted to note the behavior here + it "does not flush if receiver is not a TTY or a File" end describe "Kernel.p" do diff --git a/spec/ruby/core/method/fixtures/classes.rb b/spec/ruby/core/method/fixtures/classes.rb index be96f65e25..50daa773e1 100644 --- a/spec/ruby/core/method/fixtures/classes.rb +++ b/spec/ruby/core/method/fixtures/classes.rb @@ -213,4 +213,28 @@ module MethodSpecs n * m end end + + module InheritedMethods + module A + private + def derp(message) + 'A' + end + end + + module B + private + def derp + 'B' + super('superclass') + end + end + + class C + include A + include B + + public :derp + alias_method :meow, :derp + end + end end diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb index e5d8b87a06..dc8764f6c7 100644 --- a/spec/ruby/core/method/super_method_spec.rb +++ b/spec/ruby/core/method/super_method_spec.rb @@ -42,4 +42,32 @@ describe "Method#super_method" do method.super_method.should == nil end + + # https://github.com/jruby/jruby/issues/7240 + context "after changing an inherited methods visibility" do + it "calls the proper super method" do + MethodSpecs::InheritedMethods::C.new.derp.should == 'BA' + end + + ruby_version_is ""..."3.2" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.method(:derp) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end + + ruby_version_is "3.2" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.method(:derp) + method.super_method.owner.should == MethodSpecs::InheritedMethods::B + end + end + end + + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end end diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb index 75730395e8..0c15629c08 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -17,11 +17,16 @@ describe "Module#const_defined?" do ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4).should be_true end - it "returns true if the constant is defined in a mixed-in module of the receiver" do + it "returns true if the constant is defined in a mixed-in module of the receiver's parent" do # CS_CONST10 is defined in a module included by ChildA ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST10).should be_true end + it "returns true if the constant is defined in a mixed-in module (with prepends) of the receiver" do + # CS_CONST11 is defined in the module included by ContainerPrepend + ConstantSpecs::ContainerPrepend.const_defined?(:CS_CONST11).should be_true + end + it "returns true if the constant is defined in Object and the receiver is a module" do # CS_CONST1 is defined in Object ConstantSpecs::ModuleA.const_defined?(:CS_CONST1).should be_true diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb index c65b30de24..ce94436bfd 100644 --- a/spec/ruby/core/module/define_method_spec.rb +++ b/spec/ruby/core/module/define_method_spec.rb @@ -219,18 +219,55 @@ describe "Module#define_method" do o.block_test2.should == o end + it "raises TypeError if name cannot converted to String" do + -> { + Class.new { define_method(1001, -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + + -> { + Class.new { define_method([], -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "converts non-String name to String with #to_str" do + obj = Object.new + def obj.to_str() "foo" end + + new_class = Class.new { define_method(obj, -> { :called }) } + new_class.new.foo.should == :called + end + + it "raises TypeError when #to_str called on non-String name returns non-String value" do + obj = Object.new + def obj.to_str() [] end + + -> { + Class.new { define_method(obj, -> {}) } + }.should raise_error(TypeError, /can't convert Object to String/) + end + it "raises a TypeError when the given method is no Method/Proc" do -> { Class.new { define_method(:test, "self") } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type String (expected Proc/Method/UnboundMethod)") -> { Class.new { define_method(:test, 1234) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type Integer (expected Proc/Method/UnboundMethod)") -> { Class.new { define_method(:test, nil) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type NilClass (expected Proc/Method/UnboundMethod)") + end + + it "uses provided Method/Proc even if block is specified" do + new_class = Class.new do + define_method(:test, -> { :method_is_called }) do + :block_is_called + end + end + + new_class.new.test.should == :method_is_called end it "raises an ArgumentError when no block is given" do diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb index a64f672b2f..2b47ff112e 100644 --- a/spec/ruby/core/module/fixtures/classes.rb +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -42,6 +42,14 @@ module ModuleSpecs class LookupChild < Lookup end + module ModuleWithPrepend + prepend LookupMod + end + + class WithPrependedModule + include ModuleWithPrepend + end + class Parent # For private_class_method spec def self.private_method; end diff --git a/spec/ruby/core/module/included_modules_spec.rb b/spec/ruby/core/module/included_modules_spec.rb index 40e20953f4..ce94ed1285 100644 --- a/spec/ruby/core/module/included_modules_spec.rb +++ b/spec/ruby/core/module/included_modules_spec.rb @@ -4,9 +4,11 @@ require_relative 'fixtures/classes' describe "Module#included_modules" do it "returns a list of modules included in self" do ModuleSpecs.included_modules.should == [] + ModuleSpecs::Child.included_modules.should include(ModuleSpecs::Super, ModuleSpecs::Basic, Kernel) ModuleSpecs::Parent.included_modules.should include(Kernel) ModuleSpecs::Basic.included_modules.should == [] ModuleSpecs::Super.included_modules.should include(ModuleSpecs::Basic) + ModuleSpecs::WithPrependedModule.included_modules.should include(ModuleSpecs::ModuleWithPrepend) end end diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb index b4d6a0d8c8..30e27bb180 100644 --- a/spec/ruby/core/module/instance_method_spec.rb +++ b/spec/ruby/core/module/instance_method_spec.rb @@ -51,14 +51,36 @@ describe "Module#instance_method" do @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/ end - it "raises a TypeError if not passed a symbol" do - -> { Object.instance_method([]) }.should raise_error(TypeError) - -> { Object.instance_method(0) }.should raise_error(TypeError) + it "raises a TypeError if the given name is not a String/Symbol" do + -> { Object.instance_method([]) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(0) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(nil) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(mock('x')) }.should raise_error(TypeError, /is not a symbol nor a string/) end - it "raises a TypeError if the given name is not a string/symbol" do - -> { Object.instance_method(nil) }.should raise_error(TypeError) - -> { Object.instance_method(mock('x')) }.should raise_error(TypeError) + it "accepts String name argument" do + method = ModuleSpecs::InstanceMeth.instance_method(:foo) + method.should be_kind_of(UnboundMethod) + end + + it "accepts Symbol name argument" do + method = ModuleSpecs::InstanceMeth.instance_method("foo") + method.should be_kind_of(UnboundMethod) + end + + it "converts non-String name by calling #to_str method" do + obj = Object.new + def obj.to_str() "foo" end + + method = ModuleSpecs::InstanceMeth.instance_method(obj) + method.should be_kind_of(UnboundMethod) + end + + it "raises TypeError when passed non-String name and #to_str returns non-String value" do + obj = Object.new + def obj.to_str() [] end + + -> { ModuleSpecs::InstanceMeth.instance_method(obj) }.should raise_error(TypeError, /can't convert Object to String/) end it "raises a NameError if the method has been undefined" do diff --git a/spec/ruby/core/module/shared/class_eval.rb b/spec/ruby/core/module/shared/class_eval.rb index 224078ae54..9ef7b5be44 100644 --- a/spec/ruby/core/module/shared/class_eval.rb +++ b/spec/ruby/core/module/shared/class_eval.rb @@ -55,40 +55,49 @@ describe :module_class_eval, shared: true do it "converts a non-string filename to a string using to_str" do (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) ModuleSpecs.send(@method, "1+1", file) + + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.send(@method, "1+1", file, 15) end it "raises a TypeError when the given filename can't be converted to string using to_str" do (file = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError, /can't convert MockObject to String/) end it "converts non string eval-string to string using to_str" do (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") ModuleSpecs.send(@method, o).should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb").should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb", 15).should == 2 end it "raises a TypeError when the given eval-string can't be converted to string using to_str" do o = mock('x') - -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, "no implicit conversion of MockObject into String") (o = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, /can't convert MockObject to String/) end it "raises an ArgumentError when no arguments and no block are given" do - -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError) + -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") end it "raises an ArgumentError when more than 3 arguments are given" do -> { ModuleSpecs.send(@method, "1 + 1", "some file", 0, "bogus") - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") end it "raises an ArgumentError when a block and normal arguments are given" do -> { ModuleSpecs.send(@method, "1 + 1") { 1 + 1 } - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)") end # This case was found because Rubinius was caching the compiled diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index b57f660816..0417486692 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -29,9 +29,35 @@ describe "String#split with String" do "1,2,,3,4,,".split(',').should == ["1", "2", "", "3", "4"] "1,2,,3,4,,".split(',', 0).should == ["1", "2", "", "3", "4"] " a b c\nd ".split(" ").should == ["", "a", "b", "c\nd"] + " a あ c\nd ".split(" ").should == ["", "a", "あ", "c\nd"] "hai".split("hai").should == [] ",".split(",").should == [] ",".split(",", 0).should == [] + "あ".split("あ").should == [] + "あ".split("あ", 0).should == [] + end + + it "does not suppress trailing empty fields when a positive limit is given" do + " 1 2 ".split(" ", 2).should == ["1", "2 "] + " 1 2 ".split(" ", 3).should == ["1", "2", ""] + " 1 2 ".split(" ", 4).should == ["1", "2", ""] + " 1 あ ".split(" ", 2).should == ["1", "あ "] + " 1 あ ".split(" ", 3).should == ["1", "あ", ""] + " 1 あ ".split(" ", 4).should == ["1", "あ", ""] + + "1,2,".split(',', 2).should == ["1", "2,"] + "1,2,".split(',', 3).should == ["1", "2", ""] + "1,2,".split(',', 4).should == ["1", "2", ""] + "1,あ,".split(',', 2).should == ["1", "あ,"] + "1,あ,".split(',', 3).should == ["1", "あ", ""] + "1,あ,".split(',', 4).should == ["1", "あ", ""] + + "1 2 ".split(/ /, 2).should == ["1", "2 "] + "1 2 ".split(/ /, 3).should == ["1", "2", ""] + "1 2 ".split(/ /, 4).should == ["1", "2", ""] + "1 あ ".split(/ /, 2).should == ["1", "あ "] + "1 あ ".split(/ /, 3).should == ["1", "あ", ""] + "1 あ ".split(/ /, 4).should == ["1", "あ", ""] end it "returns an array with one entry if limit is 1: the original string" do diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb index 4136f09348..e35e1fc0b4 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -17,6 +17,15 @@ describe 'Thread::Backtrace::Location#absolute_path' do end end + it 'returns the correct absolute path when using a relative main script path and changing CWD' do + script = fixture(__FILE__, 'subdir/absolute_path_main_chdir.rb') + sibling = fixture(__FILE__, 'subdir/sibling.rb') + subdir = File.dirname script + Dir.chdir(fixture(__FILE__)) do + ruby_exe('subdir/absolute_path_main_chdir.rb').should == "subdir/absolute_path_main_chdir.rb\n#{subdir}\n#{subdir}\n#{script}\n#{sibling}\n" + end + end + context "when used in eval with a given filename" do code = "caller_locations(0)[0].absolute_path" diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb new file mode 100644 index 0000000000..33c8fb36ef --- /dev/null +++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb @@ -0,0 +1,11 @@ +puts __FILE__ +puts __dir__ +Dir.chdir __dir__ + +# Check __dir__ is still correct after chdir +puts __dir__ + +puts caller_locations(0)[0].absolute_path + +# require_relative also needs to know the absolute path of the current file so we test it here too +require_relative 'sibling' diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb new file mode 100644 index 0000000000..2a854ddccd --- /dev/null +++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb @@ -0,0 +1 @@ +puts __FILE__ diff --git a/spec/ruby/core/unboundmethod/fixtures/classes.rb b/spec/ruby/core/unboundmethod/fixtures/classes.rb index 46b1c51669..1f466e39d8 100644 --- a/spec/ruby/core/unboundmethod/fixtures/classes.rb +++ b/spec/ruby/core/unboundmethod/fixtures/classes.rb @@ -84,4 +84,14 @@ module UnboundMethodSpecs class C < B def overridden; end end + + module HashSpecs + class SuperClass + def foo + end + end + + class SubClass < SuperClass + end + end end diff --git a/spec/ruby/core/unboundmethod/hash_spec.rb b/spec/ruby/core/unboundmethod/hash_spec.rb index 12dce0020f..6888675bc1 100644 --- a/spec/ruby/core/unboundmethod/hash_spec.rb +++ b/spec/ruby/core/unboundmethod/hash_spec.rb @@ -12,4 +12,11 @@ describe "UnboundMethod#hash" do to_s, inspect = Array.instance_method(:to_s), Array.instance_method(:inspect) to_s.hash.should == inspect.hash end + + it "equals a hash of the same method in the superclass" do + foo_in_superclass = UnboundMethodSpecs::HashSpecs::SuperClass.instance_method(:foo) + foo = UnboundMethodSpecs::HashSpecs::SubClass.instance_method(:foo) + + foo.hash.should == foo_in_superclass.hash + end end diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb index c9fa1ec533..5cf96e6fa9 100644 --- a/spec/ruby/core/unboundmethod/super_method_spec.rb +++ b/spec/ruby/core/unboundmethod/super_method_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative '../method/fixtures/classes' describe "UnboundMethod#super_method" do it "returns the method that would be called by super in the method" do @@ -25,4 +26,28 @@ describe "UnboundMethod#super_method" do method.super_method.should == nil end + + # https://github.com/jruby/jruby/issues/7240 + context "after changing an inherited methods visibility" do + ruby_version_is ""..."3.2" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:derp) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end + + ruby_version_is "3.2" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:derp) + method.super_method.owner.should == MethodSpecs::InheritedMethods::B + end + end + end + + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end end |