diff options
Diffstat (limited to 'spec/ruby')
69 files changed, 742 insertions, 141 deletions
diff --git a/spec/ruby/.travis.yml b/spec/ruby/.travis.yml index dab9d9297c..35fea82656 100644 --- a/spec/ruby/.travis.yml +++ b/spec/ruby/.travis.yml @@ -6,15 +6,15 @@ script: - ../mspec/bin/mspec $MSPEC_OPTS matrix: include: - - rvm: 2.5.1 + - rvm: 2.5.3 env: MSPEC_OPTS="-R2 -ff" - - rvm: 2.3.7 - - rvm: 2.4.4 - - rvm: 2.5.1 + - rvm: 2.3.8 + - rvm: 2.4.5 + - rvm: 2.5.3 env: CHECK_LEAKS=true - rvm: ruby-head - env: RUBOCOP=true - rvm: 2.4.4 + rvm: 2.4.5 script: - gem install rubocop -v 0.54.0 - rubocop diff --git a/spec/ruby/core/array/eql_spec.rb b/spec/ruby/core/array/eql_spec.rb index d4707eb518..8565b94c60 100644 --- a/spec/ruby/core/array/eql_spec.rb +++ b/spec/ruby/core/array/eql_spec.rb @@ -6,14 +6,14 @@ describe "Array#eql?" do it_behaves_like :array_eql, :eql? it "returns false if any corresponding elements are not #eql?" do - [1, 2, 3, 4].send(@method, [1, 2, 3, 4.0]).should be_false + [1, 2, 3, 4].should_not eql([1, 2, 3, 4.0]) end it "returns false if other is not a kind of Array" do obj = mock("array eql?") obj.should_not_receive(:to_ary) - obj.should_not_receive(@method) + obj.should_not_receive(:eql?) - [1, 2, 3].send(@method, obj).should be_false + [1, 2, 3].should_not eql(obj) end end diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb index 2f8671b29a..7ca9067328 100644 --- a/spec/ruby/core/array/fixtures/classes.rb +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -2,9 +2,10 @@ class Object # This helper is defined here rather than in MSpec because # it is only used in #pack specs. def pack_format(count=nil, repeat=nil) - format = "#{instance_variable_get(:@method)}#{count}" + format = instance_variable_get(:@method) + format += count.to_s unless format == 'P' || format == 'p' format *= repeat if repeat - format + format.dup # because it may then become tainted end end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb index 223e99cefe..1cee3858ff 100644 --- a/spec/ruby/core/array/pack/a_spec.rb +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -3,12 +3,14 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' +require_relative 'shared/taint' describe "Array#pack with format 'A'" do it_behaves_like :array_pack_basic, 'A' it_behaves_like :array_pack_basic_non_float, 'A' it_behaves_like :array_pack_no_platform, 'A' it_behaves_like :array_pack_string, 'A' + it_behaves_like :array_pack_taint, 'A' it "adds all the bytes to the output when passed the '*' modifier" do ["abc"].pack("A*").should == "abc" @@ -36,6 +38,7 @@ describe "Array#pack with format 'a'" do it_behaves_like :array_pack_basic_non_float, 'a' it_behaves_like :array_pack_no_platform, 'a' it_behaves_like :array_pack_string, 'a' + it_behaves_like :array_pack_taint, 'a' it "adds all the bytes to the output when passed the '*' modifier" do ["abc"].pack("a*").should == "abc" diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb index 2756a6bfb1..8a75825e3e 100644 --- a/spec/ruby/core/array/pack/b_spec.rb +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -3,12 +3,14 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' +require_relative 'shared/taint' describe "Array#pack with format 'B'" do it_behaves_like :array_pack_basic, 'B' it_behaves_like :array_pack_basic_non_float, 'B' it_behaves_like :array_pack_arguments, 'B' it_behaves_like :array_pack_hex, 'B' + it_behaves_like :array_pack_taint, 'B' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") @@ -59,6 +61,7 @@ describe "Array#pack with format 'b'" do it_behaves_like :array_pack_basic_non_float, 'b' it_behaves_like :array_pack_arguments, 'b' it_behaves_like :array_pack_hex, 'b' + it_behaves_like :array_pack_taint, 'b' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb index fee2d1efec..51bf551ba3 100644 --- a/spec/ruby/core/array/pack/h_spec.rb +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -3,12 +3,14 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' +require_relative 'shared/taint' describe "Array#pack with format 'H'" do it_behaves_like :array_pack_basic, 'H' it_behaves_like :array_pack_basic_non_float, 'H' it_behaves_like :array_pack_arguments, 'H' it_behaves_like :array_pack_hex, 'H' + it_behaves_like :array_pack_taint, 'H' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") @@ -105,6 +107,7 @@ describe "Array#pack with format 'h'" do it_behaves_like :array_pack_basic_non_float, 'h' it_behaves_like :array_pack_arguments, 'h' it_behaves_like :array_pack_hex, 'h' + it_behaves_like :array_pack_taint, 'h' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb index a29f1937e8..24acf2cef2 100644 --- a/spec/ruby/core/array/pack/m_spec.rb +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -2,11 +2,13 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "Array#pack with format 'M'" do it_behaves_like :array_pack_basic, 'M' it_behaves_like :array_pack_basic_non_float, 'M' it_behaves_like :array_pack_arguments, 'M' + it_behaves_like :array_pack_taint, 'M' it "encodes an empty string as an empty string" do [""].pack("M").should == "" @@ -192,6 +194,7 @@ describe "Array#pack with format 'm'" do it_behaves_like :array_pack_basic, 'm' it_behaves_like :array_pack_basic_non_float, 'm' it_behaves_like :array_pack_arguments, 'm' + it_behaves_like :array_pack_taint, 'm' it "encodes an empty string as an empty string" do [""].pack("m").should == "" diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb index d562108967..857d403313 100644 --- a/spec/ruby/core/array/pack/p_spec.rb +++ b/spec/ruby/core/array/pack/p_spec.rb @@ -1,11 +1,58 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "Array#pack with format 'P'" do it_behaves_like :array_pack_basic_non_float, 'P' + it_behaves_like :array_pack_taint, 'P' + + it "produces as many bytes as there are in a pointer" do + ["hello"].pack("P").size.should == [0].pack("J").size + end + + it "round-trips a string through pack and unpack" do + ["hello"].pack("P").unpack("P5").should == ["hello"] + end + + it "taints the input string" do + input_string = "hello" + [input_string].pack("P") + input_string.tainted?.should be_true + end + + it "does not taint the output string in normal cases" do + ["hello"].pack("P").tainted?.should be_false + end + + it "with nil gives a null pointer" do + [nil].pack("P").unpack("J").should == [0] + end end describe "Array#pack with format 'p'" do it_behaves_like :array_pack_basic_non_float, 'p' + it_behaves_like :array_pack_taint, 'p' + + it "produces as many bytes as there are in a pointer" do + ["hello"].pack("p").size.should == [0].pack("J").size + end + + it "round-trips a string through pack and unpack" do + ["hello"].pack("p").unpack("p").should == ["hello"] + end + + it "taints the input string" do + input_string = "hello" + [input_string].pack("p") + input_string.tainted?.should be_true + end + + it "does not taint the output string in normal cases" do + ["hello"].pack("p").tainted?.should be_false + end + + it "with nil gives a null pointer" do + [nil].pack("p").unpack("J").should == [0] + end end diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb index cedb0886e2..256c1c08e8 100644 --- a/spec/ruby/core/array/pack/shared/string.rb +++ b/spec/ruby/core/array/pack/shared/string.rb @@ -36,38 +36,6 @@ describe :array_pack_string, shared: true do lambda { [obj].pack(pack_format) }.should raise_error(TypeError) end - it "returns a tainted string when a pack argument is tainted" do - ["abcd".taint, 0x20].pack(pack_format("3C")).tainted?.should be_true - end - - it "does not return a tainted string when the array is tainted" do - ["abcd", 0x20].taint.pack(pack_format("3C")).tainted?.should be_false - end - - it "returns a tainted string when the format is tainted" do - ["abcd", 0x20].pack(pack_format("3C").taint).tainted?.should be_true - end - - it "returns a tainted string when an empty format is tainted" do - ["abcd", 0x20].pack("".taint).tainted?.should be_true - end - - it "returns a untrusted string when the format is untrusted" do - ["abcd", 0x20].pack(pack_format("3C").untrust).untrusted?.should be_true - end - - it "returns a untrusted string when the empty format is untrusted" do - ["abcd", 0x20].pack("".untrust).untrusted?.should be_true - end - - it "returns a untrusted string when a pack argument is untrusted" do - ["abcd".untrust, 0x20].pack(pack_format("3C")).untrusted?.should be_true - end - - it "returns a trusted string when the array is untrusted" do - ["abcd", 0x20].untrust.pack(pack_format("3C")).untrusted?.should be_false - end - it "returns a string in encoding of common to the concatenated results" do f = pack_format("*") [ [["\u{3042 3044 3046 3048}", 0x2000B].pack(f+"U"), Encoding::ASCII_8BIT], diff --git a/spec/ruby/core/array/pack/shared/taint.rb b/spec/ruby/core/array/pack/shared/taint.rb new file mode 100644 index 0000000000..88f349cb24 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/taint.rb @@ -0,0 +1,33 @@ +describe :array_pack_taint, shared: true do + it "returns a tainted string when a pack argument is tainted" do + ["abcd".taint, 0x20].pack(pack_format("3C")).tainted?.should be_true + end + + it "does not return a tainted string when the array is tainted" do + ["abcd", 0x20].taint.pack(pack_format("3C")).tainted?.should be_false + end + + it "returns a tainted string when the format is tainted" do + ["abcd", 0x20].pack(pack_format("3C").taint).tainted?.should be_true + end + + it "returns a tainted string when an empty format is tainted" do + ["abcd", 0x20].pack("".taint).tainted?.should be_true + end + + it "returns a untrusted string when the format is untrusted" do + ["abcd", 0x20].pack(pack_format("3C").untrust).untrusted?.should be_true + end + + it "returns a untrusted string when the empty format is untrusted" do + ["abcd", 0x20].pack("".untrust).untrusted?.should be_true + end + + it "returns a untrusted string when a pack argument is untrusted" do + ["abcd".untrust, 0x20].pack(pack_format("3C")).untrusted?.should be_true + end + + it "returns a trusted string when the array is untrusted" do + ["abcd", 0x20].untrust.pack(pack_format("3C")).untrusted?.should be_false + end +end diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb index 9c248165b3..d708518c16 100644 --- a/spec/ruby/core/array/pack/u_spec.rb +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -3,6 +3,7 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' +require_relative 'shared/taint' describe "Array#pack with format 'U'" do it_behaves_like :array_pack_basic, 'U' @@ -15,6 +16,7 @@ describe "Array#pack with format 'u'" do it_behaves_like :array_pack_basic, 'u' it_behaves_like :array_pack_basic_non_float, 'u' it_behaves_like :array_pack_arguments, 'u' + it_behaves_like :array_pack_taint, 'u' it "encodes an empty string as an empty string" do [""].pack("u").should == "" diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb index 8c89aa9d2a..d0600f0c26 100644 --- a/spec/ruby/core/array/pack/z_spec.rb +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -3,12 +3,14 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' +require_relative 'shared/taint' describe "Array#pack with format 'Z'" do it_behaves_like :array_pack_basic, 'Z' it_behaves_like :array_pack_basic_non_float, 'Z' it_behaves_like :array_pack_no_platform, 'Z' it_behaves_like :array_pack_string, 'Z' + it_behaves_like :array_pack_taint, 'Z' it "adds all the bytes and appends a NULL byte when passed the '*' modifier" do ["abc"].pack("Z*").should == "abc\x00" diff --git a/spec/ruby/core/enumerable/all_spec.rb b/spec/ruby/core/enumerable/all_spec.rb index bd87a361d4..b02ac8b21b 100644 --- a/spec/ruby/core/enumerable/all_spec.rb +++ b/spec/ruby/core/enumerable/all_spec.rb @@ -68,7 +68,6 @@ describe "Enumerable#all?" do end it "gathers whole arrays as elements when each yields multiple" do - # This spec doesn't spec what it says it does multi = EnumerableSpecs::YieldsMultiWithFalse.new multi.all?.should be_true end diff --git a/spec/ruby/core/enumerable/none_spec.rb b/spec/ruby/core/enumerable/none_spec.rb index 99fbb24a95..bff252df5e 100644 --- a/spec/ruby/core/enumerable/none_spec.rb +++ b/spec/ruby/core/enumerable/none_spec.rb @@ -51,7 +51,6 @@ describe "Enumerable#none?" do end it "gathers whole arrays as elements when each yields multiple" do - # This spec doesn't spec what it says it does multi = EnumerableSpecs::YieldsMultiWithFalse.new multi.none?.should be_false end diff --git a/spec/ruby/core/enumerable/one_spec.rb b/spec/ruby/core/enumerable/one_spec.rb index d3ff42890f..2bd67adc26 100644 --- a/spec/ruby/core/enumerable/one_spec.rb +++ b/spec/ruby/core/enumerable/one_spec.rb @@ -78,7 +78,6 @@ describe "Enumerable#one?" do end it "gathers initial args as elements when each yields multiple" do - # This spec doesn't spec what it says it does multi = EnumerableSpecs::YieldsMulti.new yielded = [] multi.one? { |e| yielded << e; false }.should == false diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb index d414b1919f..f7717c9404 100644 --- a/spec/ruby/core/hash/slice_spec.rb +++ b/spec/ruby/core/hash/slice_spec.rb @@ -6,10 +6,11 @@ ruby_version_is "2.5" do @hash = { a: 1, b: 2, c: 3 } end - it "returns new hash" do + it "returns a new empty hash without arguments" do ret = @hash.slice ret.should_not equal(@hash) ret.should be_an_instance_of(Hash) + ret.should == {} end it "returns the requested subset" do @@ -27,10 +28,28 @@ ruby_version_is "2.5" do it "returns a Hash instance, even on subclasses" do klass = Class.new(Hash) h = klass.new + h[:bar] = 12 h[:foo] = 42 r = h.slice(:foo) r.should == {foo: 42} r.class.should == Hash end + + it "uses the regular Hash#[] method, even on subclasses that override it" do + ScratchPad.record [] + klass = Class.new(Hash) do + def [](value) + ScratchPad << :used_subclassed_operator + super + end + end + + h = klass.new + h[:bar] = 12 + h[:foo] = 42 + h.slice(:foo) + + ScratchPad.recorded.should == [] + end end end diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb new file mode 100644 index 0000000000..b5b516fa53 --- /dev/null +++ b/spec/ruby/core/io/pread_spec.rb @@ -0,0 +1,52 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' + +ruby_version_is "2.5" do + platform_is_not :windows do + describe "IO#pread" do + before :each do + @fname = tmp("io_pread.txt") + @contents = "1234567890" + touch(@fname) { |f| f.write @contents } + @file = File.open(@fname, "r+") + end + + after :each do + @file.close + rm_r @fname + end + + it "accepts a length, and an offset" do + @file.pread(4, 0).should == "1234" + @file.pread(3, 4).should == "567" + end + + it "accepts a length, an offset, and an output buffer" do + buffer = "foo" + @file.pread(3, 4, buffer) + buffer.should == "567" + end + + it "does not advance the file pointer" do + @file.pread(4, 0).should == "1234" + @file.read.should == "1234567890" + end + + it "raises EOFError if end-of-file is reached" do + lambda { @file.pread(1, 10) }.should raise_error(EOFError) + end + + it "raises IOError when file is not open in read mode" do + File.open(@fname, "w") do |file| + lambda { file.pread(1, 1) }.should raise_error(IOError) + end + end + + it "raises IOError when file is closed" do + file = File.open(@fname, "r+") + file.close + lambda { file.pread(1, 1) }.should raise_error(IOError) + end + end + end +end diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb new file mode 100644 index 0000000000..fd3d1b98e9 --- /dev/null +++ b/spec/ruby/core/io/pwrite_spec.rb @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' + +ruby_version_is "2.5" do + platform_is_not :windows do + describe "IO#pwrite" do + before :each do + @fname = tmp("io_pwrite.txt") + @file = File.open(@fname, "w+") + end + + after :each do + @file.close + rm_r @fname + end + + it "returns the number of bytes written" do + @file.pwrite("foo", 0).should == 3 + end + + it "accepts a string and an offset" do + @file.pwrite("foo", 2) + @file.pread(3, 2).should == "foo" + end + + it "does not advance the pointer in the file" do + @file.pwrite("bar", 3) + @file.write("foo") + @file.pread(6, 0).should == "foobar" + end + + it "raises IOError when file is not open in write mode" do + File.open(@fname, "r") do |file| + lambda { file.pwrite("foo", 1) }.should raise_error(IOError) + end + end + + it "raises IOError when file is closed" do + file = File.open(@fname, "w+") + file.close + lambda { file.pwrite("foo", 1) }.should raise_error(IOError) + end + end + end +end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index e6db3351db..50e3df864d 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -127,6 +127,17 @@ end describe "IO#write" do it_behaves_like :io_write, :write + + ruby_version_is "2.5" do + it "accepts multiple arguments" do + IO.pipe do |r, w| + w.write("foo", "bar") + w.close + + r.read.should == "foobar" + end + end + end end platform_is :windows do diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb index f738f43a10..44e4f44ada 100644 --- a/spec/ruby/core/kernel/Complex_spec.rb +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -126,8 +126,8 @@ describe "Kernel.Complex()" do describe "when passed a non-Numeric second argument" do it "raises TypeError" do - lambda { Complex.send(@method, :sym, :sym) }.should raise_error(TypeError) - lambda { Complex.send(@method, 0, :sym) }.should raise_error(TypeError) + lambda { Complex(:sym, :sym) }.should raise_error(TypeError) + lambda { Complex(0, :sym) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index cc475ed758..1f45bbb083 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -68,6 +68,7 @@ module KernelSpecs module SomeOtherModule; end module AncestorModule; end module MyModule; end + module MyPrependedModule; end module MyExtensionModule; end class AncestorClass < String @@ -80,6 +81,8 @@ module KernelSpecs class KindaClass < AncestorClass include MyModule + prepend MyPrependedModule + def initialize self.extend MyExtensionModule end diff --git a/spec/ruby/core/kernel/shared/kind_of.rb b/spec/ruby/core/kernel/shared/kind_of.rb index 690018d655..0614c02214 100644 --- a/spec/ruby/core/kernel/shared/kind_of.rb +++ b/spec/ruby/core/kernel/shared/kind_of.rb @@ -31,7 +31,11 @@ describe :kernel_kind_of, shared: true do @o.send(@method, KernelSpecs::MyExtensionModule).should == true end - it "returns false if given a Module not included in object's class nor ancestors" do + it "returns true if given a Module that object has been prepended with" do + @o.send(@method, KernelSpecs::MyPrependedModule).should == true + end + + it "returns false if given a Module not included nor prepended in object's class nor ancestors" do @o.send(@method, KernelSpecs::SomeOtherModule).should == false end @@ -41,4 +45,11 @@ describe :kernel_kind_of, shared: true do lambda { @o.send(@method, :KindaClass) }.should raise_error(TypeError) lambda { @o.send(@method, Object.new) }.should raise_error(TypeError) end + + it "does not take into account `class` method overriding" do + def @o.class; Integer; end + + @o.send(@method, Integer).should == false + @o.send(@method, KernelSpecs::KindaClass).should == true + end end diff --git a/spec/ruby/core/proc/element_reference_spec.rb b/spec/ruby/core/proc/element_reference_spec.rb index 1c9b89521a..f60ae1b086 100644 --- a/spec/ruby/core/proc/element_reference_spec.rb +++ b/spec/ruby/core/proc/element_reference_spec.rb @@ -1,6 +1,8 @@ require_relative '../../spec_helper' require_relative 'shared/call' require_relative 'shared/call_arguments' +require_relative 'fixtures/proc_aref' +require_relative 'fixtures/proc_aref_frozen' describe "Proc#[]" do it_behaves_like :proc_call, :[] @@ -14,3 +16,14 @@ end describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do it_behaves_like :proc_call_on_proc_or_lambda, :call end + +ruby_bug "#15118", ""..."2.6" do + describe "Proc#[] with frozen_string_literals" do + it "doesn't duplicate frozen strings" do + ProcArefSpecs.aref.frozen?.should be_false + ProcArefSpecs.aref_freeze.frozen?.should be_true + ProcArefFrozenSpecs.aref.frozen?.should be_true + ProcArefFrozenSpecs.aref_freeze.frozen?.should be_true + end + end +end diff --git a/spec/ruby/core/proc/fixtures/proc_aref.rb b/spec/ruby/core/proc/fixtures/proc_aref.rb new file mode 100644 index 0000000000..a305667797 --- /dev/null +++ b/spec/ruby/core/proc/fixtures/proc_aref.rb @@ -0,0 +1,9 @@ +module ProcArefSpecs + def self.aref + proc {|a| a }["sometext"] + end + + def self.aref_freeze + proc {|a| a }["sometext".freeze] + end +end diff --git a/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb new file mode 100644 index 0000000000..50a330ba4f --- /dev/null +++ b/spec/ruby/core/proc/fixtures/proc_aref_frozen.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module ProcArefFrozenSpecs + def self.aref + proc {|a| a }["sometext"] + end + + def self.aref_freeze + proc {|a| a }["sometext".freeze] + end +end diff --git a/spec/ruby/core/proc/shared/to_s.rb b/spec/ruby/core/proc/shared/to_s.rb index c3f82a73f3..530eaff3a0 100644 --- a/spec/ruby/core/proc/shared/to_s.rb +++ b/spec/ruby/core/proc/shared/to_s.rb @@ -3,18 +3,30 @@ describe :proc_to_s, shared: true do it "returns a description optionally including file and line number" do Proc.new { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?)(@([^ ]*)\/to_s\.rb:4)?>$/ end + + it "has an ASCII-8BIT encoding" do + Proc.new { "hello" }.send(@method).encoding.should == Encoding::ASCII_8BIT + end end describe "for a proc created with lambda" do it "returns a description including '(lambda)' and optionally including file and line number" do lambda { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?)(@([^ ]*)\/to_s\.rb:10)? \(lambda\)>$/ end + + it "has an ASCII-8BIT encoding" do + lambda { "hello" }.send(@method).encoding.should == Encoding::ASCII_8BIT + end end describe "for a proc created with proc" do it "returns a description optionally including file and line number" do proc { "hello" }.send(@method).should =~ /^#<Proc:([^ ]*?)(@([^ ]*)\/to_s\.rb:16)?>$/ end + + it "has an ASCII-8BIT encoding" do + proc { "hello" }.send(@method).encoding.should == Encoding::ASCII_8BIT + end end describe "for a proc created with UnboundMethod#to_proc" do @@ -23,5 +35,11 @@ describe :proc_to_s, shared: true do method("hello").to_proc.send(@method).should =~ /^#<Proc:([^ ]*?)(@([^ ]*)\/to_s\.rb:22)? \(lambda\)>$/ end + + it "has an ASCII-8BIT encoding" do + def hello; end + + method("hello").to_proc.send(@method).encoding.should == Encoding::ASCII_8BIT + end end end diff --git a/spec/ruby/core/random/bytes_spec.rb b/spec/ruby/core/random/bytes_spec.rb index 7fe62c334b..2caf18fbd0 100644 --- a/spec/ruby/core/random/bytes_spec.rb +++ b/spec/ruby/core/random/bytes_spec.rb @@ -1,18 +1,9 @@ # -*- encoding: binary -*- require_relative '../../spec_helper' +require_relative 'shared/bytes' describe "Random#bytes" do - it "returns a String" do - Random.new.bytes(1).should be_an_instance_of(String) - end - - it "returns a String of the length given as argument" do - Random.new.bytes(15).length.should == 15 - end - - it "returns an ASCII-8BIT String" do - Random.new.bytes(15).encoding.should == Encoding::ASCII_8BIT - end + it_behaves_like :random_bytes, :bytes, Random.new it "returns the same output for a given seed" do Random.new(33).bytes(2).should == Random.new(33).bytes(2) @@ -32,8 +23,10 @@ describe "Random#bytes" do rnd.bytes(1000) # skip some rnd.bytes(2).should == "\x17\x12" end +end - it "returns a random binary String" do - Random.new.bytes(12).should_not == Random.new.bytes(12) +ruby_version_is "2.6" do + describe "Random.bytes" do + it_behaves_like :random_bytes, :bytes, Random end end diff --git a/spec/ruby/core/random/shared/bytes.rb b/spec/ruby/core/random/shared/bytes.rb new file mode 100644 index 0000000000..3485d0f221 --- /dev/null +++ b/spec/ruby/core/random/shared/bytes.rb @@ -0,0 +1,17 @@ +describe :random_bytes, shared: true do + it "returns a String" do + @object.bytes(1).should be_an_instance_of(String) + end + + it "returns a String of the length given as argument" do + @object.bytes(15).length.should == 15 + end + + it "returns an ASCII-8BIT String" do + @object.bytes(15).encoding.should == Encoding::ASCII_8BIT + end + + it "returns a random binary String" do + @object.bytes(12).should_not == @object.bytes(12) + end +end diff --git a/spec/ruby/core/range/eql_spec.rb b/spec/ruby/core/range/eql_spec.rb index c62fa0076e..fa6c71840e 100644 --- a/spec/ruby/core/range/eql_spec.rb +++ b/spec/ruby/core/range/eql_spec.rb @@ -5,6 +5,6 @@ describe "Range#eql?" do it_behaves_like :range_eql, :eql? it "returns false if the endpoints are not eql?" do - (0..1).send(@method, 0..1.0).should == false + (0..1).should_not eql(0..1.0) end end diff --git a/spec/ruby/core/range/equal_value_spec.rb b/spec/ruby/core/range/equal_value_spec.rb index baa5d0d411..889557fc2a 100644 --- a/spec/ruby/core/range/equal_value_spec.rb +++ b/spec/ruby/core/range/equal_value_spec.rb @@ -5,6 +5,6 @@ describe "Range#==" do it_behaves_like :range_eql, :== it "returns true if the endpoints are ==" do - (0..1).send(@method, 0..1.0).should == true + (0..1).should == (0..1.0) end end diff --git a/spec/ruby/core/string/chars_spec.rb b/spec/ruby/core/string/chars_spec.rb index 54876f9f9d..e4f26bc0cc 100644 --- a/spec/ruby/core/string/chars_spec.rb +++ b/spec/ruby/core/string/chars_spec.rb @@ -5,7 +5,6 @@ describe "String#chars" do it_behaves_like :string_chars, :chars it "returns an array when no block given" do - ary = "hello".send(@method) - ary.should == ['h', 'e', 'l', 'l', 'o'] + "hello".chars.should == ['h', 'e', 'l', 'l', 'o'] end end diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb index 66f2884168..bccb3d0484 100644 --- a/spec/ruby/core/string/codepoints_spec.rb +++ b/spec/ruby/core/string/codepoints_spec.rb @@ -8,13 +8,13 @@ with_feature :encoding do it_behaves_like :string_codepoints, :codepoints it "returns an Array when no block is given" do - "abc".send(@method).should == [?a.ord, ?b.ord, ?c.ord] + "abc".codepoints.should == [?a.ord, ?b.ord, ?c.ord] end it "raises an ArgumentError when no block is given if self has an invalid encoding" do s = "\xDF".force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false - lambda {s.send(@method)}.should raise_error(ArgumentError) + lambda { s.codepoints }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/core/string/each_grapheme_cluster_spec.rb b/spec/ruby/core/string/each_grapheme_cluster_spec.rb new file mode 100644 index 0000000000..5367f84887 --- /dev/null +++ b/spec/ruby/core/string/each_grapheme_cluster_spec.rb @@ -0,0 +1,11 @@ +require_relative 'shared/chars' +require_relative 'shared/grapheme_clusters' +require_relative 'shared/each_char_without_block' + +ruby_version_is "2.5" do + describe "String#each_grapheme_cluster" do + it_behaves_like :string_chars, :each_grapheme_cluster + it_behaves_like :string_grapheme_clusters, :each_grapheme_cluster + it_behaves_like :string_each_char_without_block, :each_grapheme_cluster + end +end diff --git a/spec/ruby/core/string/fixtures/classes.rb b/spec/ruby/core/string/fixtures/classes.rb index 6af106f9d3..1cc7600abb 100644 --- a/spec/ruby/core/string/fixtures/classes.rb +++ b/spec/ruby/core/string/fixtures/classes.rb @@ -4,7 +4,7 @@ class Object def unpack_format(count=nil, repeat=nil) format = "#{instance_variable_get(:@method)}#{count}" format *= repeat if repeat - format + format.dup # because it may then become tainted end end diff --git a/spec/ruby/core/string/grapheme_clusters_spec.rb b/spec/ruby/core/string/grapheme_clusters_spec.rb new file mode 100644 index 0000000000..0cba0e4216 --- /dev/null +++ b/spec/ruby/core/string/grapheme_clusters_spec.rb @@ -0,0 +1,15 @@ +require_relative 'shared/chars' +require_relative 'shared/grapheme_clusters' + +ruby_version_is "2.5" do + describe "String#grapheme_clusters" do + it_behaves_like :string_chars, :grapheme_clusters + it_behaves_like :string_grapheme_clusters, :grapheme_clusters + + it "returns an array when no block given" do + string = "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}" + string.grapheme_clusters.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"] + + end + end +end diff --git a/spec/ruby/core/string/lines_spec.rb b/spec/ruby/core/string/lines_spec.rb index 6b2d74daf1..1c13936c30 100644 --- a/spec/ruby/core/string/lines_spec.rb +++ b/spec/ruby/core/string/lines_spec.rb @@ -7,7 +7,7 @@ describe "String#lines" do it_behaves_like :string_each_line, :lines it "returns an array when no block given" do - ary = "hello world".send(@method, ' ') + ary = "hello world".lines(' ') ary.should == ["hello ", "world"] end diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb index e81b27e4ce..72738ece24 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -19,6 +19,23 @@ describe "String#%" do ("%d%% %s" % [10, "of chickens!"]).should == "10% of chickens!" end + describe "output's encoding" do + it "is the same as the format string if passed value is encoding-compatible" do + [Encoding::ASCII_8BIT, Encoding::US_ASCII, Encoding::UTF_8, Encoding::SHIFT_JIS].each do |encoding| + ("hello %s!".encode(encoding) % "world").encoding.should == encoding + end + end + + it "negotiates a compatible encoding if necessary" do + ("hello %s" % 195.chr).encoding.should == Encoding::ASCII_8BIT + ("hello %s".encode("shift_jis") % "wörld").encoding.should == Encoding::UTF_8 + end + + it "raises if a compatible encoding can't be found" do + lambda { "hello %s".encode("utf-8") % "world".encode("UTF-16LE") }.should raise_error(Encoding::CompatibilityError) + end + end + ruby_version_is ""..."2.5" do it "formats single % character at the end as literal %" do ("%" % []).should == "%" diff --git a/spec/ruby/core/string/shared/grapheme_clusters.rb b/spec/ruby/core/string/shared/grapheme_clusters.rb new file mode 100644 index 0000000000..8b666868b1 --- /dev/null +++ b/spec/ruby/core/string/shared/grapheme_clusters.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :string_grapheme_clusters, shared: true do + it "passes each grapheme cluster in self to the given block" do + a = [] + # test string: abc[rainbow flag emoji][paw prints] + "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}".send(@method) { |c| a << c } + a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"] + end + + it "returns self" do + s = StringSpecs::MyString.new "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}" + s.send(@method) {}.should equal(s) + end +end diff --git a/spec/ruby/core/string/slice_spec.rb b/spec/ruby/core/string/slice_spec.rb index 12876e757d..a3e9d6b74f 100644 --- a/spec/ruby/core/string/slice_spec.rb +++ b/spec/ruby/core/string/slice_spec.rb @@ -75,9 +75,9 @@ describe "String#slice! with index" do with_feature :encoding do it "returns the character given by the character index" do - "hellö there".send(@method, 1).should == "e" - "hellö there".send(@method, 4).should == "ö" - "hellö there".send(@method, 6).should == "t" + "hellö there".slice!(1).should == "e" + "hellö there".slice!(4).should == "ö" + "hellö there".slice!(6).should == "t" end end @@ -151,15 +151,15 @@ describe "String#slice! with index, length" do with_feature :encoding do it "returns the substring given by the character offsets" do - "hellö there".send(@method, 1,0).should == "" - "hellö there".send(@method, 1,3).should == "ell" - "hellö there".send(@method, 1,6).should == "ellö t" - "hellö there".send(@method, 1,9).should == "ellö ther" + "hellö there".slice!(1,0).should == "" + "hellö there".slice!(1,3).should == "ell" + "hellö there".slice!(1,6).should == "ellö t" + "hellö there".slice!(1,9).should == "ellö ther" end it "treats invalid bytes as single bytes" do xE6xCB = [0xE6,0xCB].pack('CC').force_encoding('utf-8') - "a#{xE6xCB}b".send(@method, 1, 2).should == xE6xCB + "a#{xE6xCB}b".slice!(1, 2).should == xE6xCB end end end @@ -239,13 +239,13 @@ describe "String#slice! Range" do with_feature :encoding do it "returns the substring given by the character offsets of the range" do - "hellö there".send(@method, 1..1).should == "e" - "hellö there".send(@method, 1..3).should == "ell" - "hellö there".send(@method, 1...3).should == "el" - "hellö there".send(@method, -4..-2).should == "her" - "hellö there".send(@method, -4...-2).should == "he" - "hellö there".send(@method, 5..-1).should == " there" - "hellö there".send(@method, 5...-1).should == " ther" + "hellö there".slice!(1..1).should == "e" + "hellö there".slice!(1..3).should == "ell" + "hellö there".slice!(1...3).should == "el" + "hellö there".slice!(-4..-2).should == "her" + "hellö there".slice!(-4...-2).should == "he" + "hellö there".slice!(5..-1).should == " there" + "hellö there".slice!(5...-1).should == " ther" end end @@ -307,8 +307,8 @@ describe "String#slice! with Regexp" do with_feature :encoding do it "returns the matching portion of self with a multi byte character" do - "hëllo there".send(@method, /[ë](.)\1/).should == "ëll" - "".send(@method, //).should == "" + "hëllo there".slice!(/[ë](.)\1/).should == "ëll" + "".slice!(//).should == "" end end @@ -391,13 +391,13 @@ describe "String#slice! with Regexp, index" do with_feature :encoding do it "returns the encoding aware capture for the given index" do - "hår".send(@method, /(.)(.)(.)/, 0).should == "hår" - "hår".send(@method, /(.)(.)(.)/, 1).should == "h" - "hår".send(@method, /(.)(.)(.)/, 2).should == "å" - "hår".send(@method, /(.)(.)(.)/, 3).should == "r" - "hår".send(@method, /(.)(.)(.)/, -1).should == "r" - "hår".send(@method, /(.)(.)(.)/, -2).should == "å" - "hår".send(@method, /(.)(.)(.)/, -3).should == "h" + "hår".slice!(/(.)(.)(.)/, 0).should == "hår" + "hår".slice!(/(.)(.)(.)/, 1).should == "h" + "hår".slice!(/(.)(.)(.)/, 2).should == "å" + "hår".slice!(/(.)(.)(.)/, 3).should == "r" + "hår".slice!(/(.)(.)(.)/, -1).should == "r" + "hår".slice!(/(.)(.)(.)/, -2).should == "å" + "hår".slice!(/(.)(.)(.)/, -3).should == "h" end end diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index 0d2c6211c4..b89a28c149 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -402,4 +402,14 @@ describe "String#split with Regexp" do broken_str.force_encoding('utf-8') lambda{ broken_str.split(/\r\n|\r|\n/) }.should raise_error(ArgumentError) end + + ruby_version_is "2.6" do + it "yields each split substrings if a block is given" do + a = [] + returned_object = "chunky bacon".split(" ") { |str| a << str.capitalize } + + returned_object.should == "chunky bacon" + a.should == ["Chunky", "Bacon"] + end + end end diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index 0adcf0ff80..3de338e2e1 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -3,12 +3,14 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' +require_relative 'shared/taint' describe "String#unpack with format 'A'" do it_behaves_like :string_unpack_basic, 'A' it_behaves_like :string_unpack_no_platform, 'A' it_behaves_like :string_unpack_string, 'A' it_behaves_like :string_unpack_Aa, 'A' + it_behaves_like :string_unpack_taint, 'A' it "removes trailing space and NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b", ""]], @@ -40,6 +42,7 @@ describe "String#unpack with format 'a'" do it_behaves_like :string_unpack_no_platform, 'a' it_behaves_like :string_unpack_string, 'a' it_behaves_like :string_unpack_Aa, 'a' + it_behaves_like :string_unpack_taint, 'a' it "does not remove trailing whitespace or NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b \x00"]], diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index 332c85b5f8..a0bbc3d421 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -2,10 +2,12 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "String#unpack with format 'B'" do it_behaves_like :string_unpack_basic, 'B' it_behaves_like :string_unpack_no_platform, 'B' + it_behaves_like :string_unpack_taint, 'B' it "decodes one bit from each byte for each format character starting with the most significant bit" do [ ["\x00", "B", ["0"]], @@ -96,6 +98,7 @@ end describe "String#unpack with format 'b'" do it_behaves_like :string_unpack_basic, 'b' it_behaves_like :string_unpack_no_platform, 'b' + it_behaves_like :string_unpack_taint, 'b' it "decodes one bit from each byte for each format character starting with the least significant bit" do [ ["\x00", "b", ["0"]], diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 7d0acf5718..07d52149d1 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -2,10 +2,12 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "String#unpack with format 'H'" do it_behaves_like :string_unpack_basic, 'H' it_behaves_like :string_unpack_no_platform, 'H' + it_behaves_like :string_unpack_taint, 'H' it "decodes one nibble from each byte for each format character starting with the most significant bit" do [ ["\x8f", "H", ["8"]], @@ -66,6 +68,7 @@ end describe "String#unpack with format 'h'" do it_behaves_like :string_unpack_basic, 'h' it_behaves_like :string_unpack_no_platform, 'h' + it_behaves_like :string_unpack_taint, 'h' it "decodes one nibble from each byte for each format character starting with the least significant bit" do [ ["\x8f", "h", ["f"]], diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb index 0ae1118583..d714f6fbcb 100644 --- a/spec/ruby/core/string/unpack/m_spec.rb +++ b/spec/ruby/core/string/unpack/m_spec.rb @@ -2,10 +2,12 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "String#unpack with format 'M'" do it_behaves_like :string_unpack_basic, 'M' it_behaves_like :string_unpack_no_platform, 'M' + it_behaves_like :string_unpack_taint, 'M' it "decodes an empty string" do "".unpack("M").should == [""] @@ -100,6 +102,7 @@ end describe "String#unpack with format 'm'" do it_behaves_like :string_unpack_basic, 'm' it_behaves_like :string_unpack_no_platform, 'm' + it_behaves_like :string_unpack_taint, 'm' it "decodes an empty string" do "".unpack("m").should == [""] diff --git a/spec/ruby/core/string/unpack/p_spec.rb b/spec/ruby/core/string/unpack/p_spec.rb index 57b51fef00..136e32adfd 100644 --- a/spec/ruby/core/string/unpack/p_spec.rb +++ b/spec/ruby/core/string/unpack/p_spec.rb @@ -1,21 +1,52 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' +require_relative 'shared/taint' describe "String#unpack with format 'P'" do it_behaves_like :string_unpack_basic, 'P' + it_behaves_like :string_unpack_taint, 'P' - it "returns a random object after consuming a size-of a machine word bytes" do - str = "\0" * 1.size - str.unpack("P").should be_kind_of(Object) + it "round-trips a string through pack and unpack" do + ["hello"].pack("P").unpack("P5").should == ["hello"] + end + + it "cannot unpack a string except from the same object that created it, or a duplicate of it" do + packed = ["hello"].pack("P") + packed.unpack("P5").should == ["hello"] + packed.dup.unpack("P5").should == ["hello"] + lambda { packed.to_sym.to_s.unpack("P5") }.should raise_error(ArgumentError, /no associated pointer/) + end + + it "taints the unpacked string" do + ["hello"].pack("P").unpack("P5").first.tainted?.should be_true + end + + it "reads as many characters as specified" do + ["hello"].pack("P").unpack("P1").should == ["h"] + end + + it "reads only as far as a NUL character" do + ["hello"].pack("P").unpack("P10").should == ["hello"] end end describe "String#unpack with format 'p'" do it_behaves_like :string_unpack_basic, 'p' + it_behaves_like :string_unpack_taint, 'p' + + it "round-trips a string through pack and unpack" do + ["hello"].pack("p").unpack("p").should == ["hello"] + end + + it "cannot unpack a string except from the same object that created it, or a duplicate of it" do + packed = ["hello"].pack("p") + packed.unpack("p").should == ["hello"] + packed.dup.unpack("p").should == ["hello"] + lambda { packed.to_sym.to_s.unpack("p") }.should raise_error(ArgumentError, /no associated pointer/) + end - it "returns a random object after consuming a size-of a machine word bytes" do - str = "\0" * 1.size - str.unpack("p").should be_kind_of(Object) + it "taints the unpacked string" do + ["hello"].pack("p").unpack("p").first.tainted?.should be_true end end diff --git a/spec/ruby/core/string/unpack/shared/taint.rb b/spec/ruby/core/string/unpack/shared/taint.rb new file mode 100644 index 0000000000..391338192a --- /dev/null +++ b/spec/ruby/core/string/unpack/shared/taint.rb @@ -0,0 +1,81 @@ +describe :string_unpack_taint, shared: true do + it "does not taint returned arrays if given an untainted format string" do + "".unpack(unpack_format(2)).tainted?.should be_false + end + + it "does not taint returned arrays if given a tainted format string" do + format_string = unpack_format(2).dup + format_string.taint + "".unpack(format_string).tainted?.should be_false + end + + it "does not taint returned strings if given an untainted format string" do + "".unpack(unpack_format(2)).any?(&:tainted?).should be_false + end + + it "does not taint returned strings if given a tainted format string" do + format_string = unpack_format(2).dup + format_string.taint + "".unpack(format_string).any?(&:tainted?).should be_false + end + + it "does not taint returned arrays if given an untainted packed string" do + "".unpack(unpack_format(2)).tainted?.should be_false + end + + it "does not taint returned arrays if given a tainted packed string" do + packed_string = "" + packed_string.taint + packed_string.unpack(unpack_format(2)).tainted?.should be_false + end + + it "does not taint returned strings if given an untainted packed string" do + "".unpack(unpack_format(2)).any?(&:tainted?).should be_false + end + + it "taints returned strings if given a tainted packed string" do + packed_string = "" + packed_string.taint + packed_string.unpack(unpack_format(2)).all?(&:tainted?).should be_true + end + + it "does not untrust returned arrays if given an untrusted format string" do + "".unpack(unpack_format(2)).untrusted?.should be_false + end + + it "does not untrust returned arrays if given a untrusted format string" do + format_string = unpack_format(2).dup + format_string.untrust + "".unpack(format_string).untrusted?.should be_false + end + + it "does not untrust returned strings if given an untainted format string" do + "".unpack(unpack_format(2)).any?(&:untrusted?).should be_false + end + + it "does not untrust returned strings if given a untrusted format string" do + format_string = unpack_format(2).dup + format_string.untrust + "".unpack(format_string).any?(&:untrusted?).should be_false + end + + it "does not untrust returned arrays if given an trusted packed string" do + "".unpack(unpack_format(2)).untrusted?.should be_false + end + + it "does not untrust returned arrays if given a untrusted packed string" do + packed_string = "" + packed_string.untrust + packed_string.unpack(unpack_format(2)).untrusted?.should be_false + end + + it "does not untrust returned strings if given an trusted packed string" do + "".unpack(unpack_format(2)).any?(&:untrusted?).should be_false + end + + it "untrusts returned strings if given a untrusted packed string" do + packed_string = "" + packed_string.untrust + packed_string.unpack(unpack_format(2)).all?(&:untrusted?).should be_true + end +end diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 6326bd7963..ee1d6c68e7 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -3,11 +3,13 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' +require_relative 'shared/taint' describe "String#unpack with format 'U'" do it_behaves_like :string_unpack_basic, 'U' it_behaves_like :string_unpack_no_platform, 'U' it_behaves_like :string_unpack_unicode, 'U' + it_behaves_like :string_unpack_taint, 'U' it "raises ArgumentError on a malformed byte sequence" do lambda { "\xE3".unpack('U') }.should raise_error(ArgumentError) @@ -21,6 +23,7 @@ end describe "String#unpack with format 'u'" do it_behaves_like :string_unpack_basic, 'u' it_behaves_like :string_unpack_no_platform, 'u' + it_behaves_like :string_unpack_taint, 'u' it "decodes an empty string as an empty string" do "".unpack("u").should == [""] diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb index 67d36ac8bf..2de624a74d 100644 --- a/spec/ruby/core/string/unpack/z_spec.rb +++ b/spec/ruby/core/string/unpack/z_spec.rb @@ -3,11 +3,13 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' +require_relative 'shared/taint' describe "String#unpack with format 'Z'" do it_behaves_like :string_unpack_basic, 'Z' it_behaves_like :string_unpack_no_platform, 'Z' it_behaves_like :string_unpack_string, 'Z' + it_behaves_like :string_unpack_taint, 'Z' it "stops decoding at NULL bytes when passed the '*' modifier" do "a\x00\x00 b \x00c".unpack('Z*Z*Z*Z*').should == ["a", "", " b ", "c"] diff --git a/spec/ruby/core/struct/eql_spec.rb b/spec/ruby/core/struct/eql_spec.rb index 229e81198f..c864b2b943 100644 --- a/spec/ruby/core/struct/eql_spec.rb +++ b/spec/ruby/core/struct/eql_spec.rb @@ -8,6 +8,6 @@ describe "Struct#eql?" do it "returns false if any corresponding elements are not #eql?" do car = StructClasses::Car.new("Honda", "Accord", 1998) similar_car = StructClasses::Car.new("Honda", "Accord", 1998.0) - car.send(@method, similar_car).should be_false + car.should_not eql(similar_car) end end diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb index e493dee8d0..b1a3439747 100644 --- a/spec/ruby/core/thread/backtrace/location/path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb @@ -56,7 +56,7 @@ describe 'Thread::Backtrace::Location#path' do end context 'when the script is outside of the working directory' do - before do + before :each do @parent_dir = tmp('path_outside_pwd') @sub_dir = File.join(@parent_dir, 'sub') @script = File.join(@parent_dir, 'main.rb') @@ -67,9 +67,7 @@ describe 'Thread::Backtrace::Location#path' do cp(source, @script) end - after do - rm_r(@script) - rm_r(@sub_dir) + after :each do rm_r(@parent_dir) end diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index e0218a2572..eced8a75fb 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -49,6 +49,14 @@ describe "Time.new with a utc_offset argument" do Time.new(2000, 1, 1, 0, 0, 0, "-04:10").utc_offset.should == -15000 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + Time.new(2000, 1, 1, 0, 0, 0, "+05:30:37").utc_offset.should == 19837 + end + + it "returns a Time with a UTC offset specified as -HH:MM" do + Time.new(2000, 1, 1, 0, 0, 0, "-04:10:43").utc_offset.should == -15043 + end + describe "with an argument that responds to #to_str" do it "coerces using #to_str" do o = mock('string') @@ -96,6 +104,14 @@ describe "Time.new with a utc_offset argument" do Time.new(2000, 1, 1, 0, 0, 0, 86400 - 1).utc_offset.should == (86400 - 1) lambda { Time.new(2000, 1, 1, 0, 0, 0, 86400) }.should raise_error(ArgumentError) end + + it "raises ArgumentError if the seconds argument is negative" do + lambda { Time.new(2000, 1, 1, 0, 0, -1) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if the utc_offset argument is greater than or equal to 10e9" do + lambda { Time.new(2000, 1, 1, 0, 0, 0, 1000000000) }.should raise_error(ArgumentError) + end end ruby_version_is "2.6" do diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb index fb1e1eb594..81015ac21e 100644 --- a/spec/ruby/language/regexp/back-references_spec.rb +++ b/spec/ruby/language/regexp/back-references_spec.rb @@ -46,4 +46,8 @@ describe "Regexps with back-references" do it "resets nested \<n> backreference before match of outer subexpression" do /(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"] end + + it "can match an optional quote, followed by content, followed by a matching quote, as the whole string" do + /^("|)(.*)\1$/.match('x').to_a.should == ["x", "", "x"] + end end diff --git a/spec/ruby/language/regexp_spec.rb b/spec/ruby/language/regexp_spec.rb index 19f4871e6e..6da29ee7a2 100644 --- a/spec/ruby/language/regexp_spec.rb +++ b/spec/ruby/language/regexp_spec.rb @@ -167,4 +167,31 @@ describe "Literal Regexps" do pattern.to_s.should == ref end end + + ruby_version_is '2.4' do + it "support handling unicode 9.0 characters with POSIX bracket expressions" do + char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A + /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase + char_uppercase = "\u{104B0}" # OSAGE CAPITAL LETTER A + /[[:upper:]]/.match(char_uppercase).to_s.should == char_uppercase + end + end + + ruby_version_is ""..."2.4" do + it "does not support handling unicode 9.0 characters with POSIX bracket expressions" do + char_lowercase = "\u{104D8}" # OSAGE SMALL LETTER A + /[[:lower:]]/.match(char_lowercase).should == nil + + char_uppercase = "\u{104B0}" # OSAGE CAPITAL LETTER A + /[[:upper:]]/.match(char_lowercase).should == nil + end + + it "supports handling unicode 8.0 characters with POSIX bracket expressions" do + char_lowercase = "\u{A7B5}" # LATIN SMALL LETTER BETA + /[[:lower:]]/.match(char_lowercase).to_s.should == char_lowercase + + char_uppercase = "\u{A7B4}" # LATIN CAPITAL LETTER BETA + /[[:upper:]]/.match(char_uppercase).to_s.should == char_uppercase + end + end end diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb index 22b66c2d48..3a18b150dd 100644 --- a/spec/ruby/library/bigdecimal/divmod_spec.rb +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -38,9 +38,9 @@ describe "BigDecimal#mod_part_of_divmod" do it "raises ZeroDivisionError if other is zero" do bd5667 = BigDecimal("5667.19") - lambda { bd5667.send(@method, 0) }.should raise_error(ZeroDivisionError) - lambda { bd5667.send(@method, BigDecimal("0")) }.should raise_error(ZeroDivisionError) - lambda { @zero.send(@method, @zero) }.should raise_error(ZeroDivisionError) + lambda { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError) + lambda { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError) + lambda { @zero.mod_part_of_divmod(@zero) }.should raise_error(ZeroDivisionError) end end diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb index acc2e943f0..75741c5050 100644 --- a/spec/ruby/library/bigdecimal/to_s_spec.rb +++ b/spec/ruby/library/bigdecimal/to_s_spec.rb @@ -15,8 +15,16 @@ describe "BigDecimal#to_s" do @bigneg.to_s.kind_of?(String).should == true end - it "the default format looks like 0.xxxxEnn" do - @bigdec.to_s.should =~ /^0\.[0-9]*E[0-9]*$/i + ruby_version_is ''...'2.4' do + it "the default format looks like 0.xxxxEnn" do + @bigdec.to_s.should =~ /^0\.[0-9]*E[0-9]*$/ + end + end + + ruby_version_is '2.4' do + it "the default format looks like 0.xxxxenn" do + @bigdec.to_s.should =~ /^0\.[0-9]*e[0-9]*$/ + end end it "takes an optional argument" do @@ -63,10 +71,12 @@ describe "BigDecimal#to_s" do end it "can use conventional floating point notation" do - @bigdec.to_s("F").should == @bigdec_str - @bigneg.to_s("F").should == @bigneg_str - str2 = "+123.45678901 23456789" - BigDecimal('123.45678901234567890').to_s('+8F').should == str2 + %w[f F].each do |format_char| + @bigdec.to_s(format_char).should == @bigdec_str + @bigneg.to_s(format_char).should == @bigneg_str + str2 = "+123.45678901 23456789" + BigDecimal('123.45678901234567890').to_s("+8#{format_char}").should == str2 + end end end diff --git a/spec/ruby/library/datetime/add_spec.rb b/spec/ruby/library/datetime/add_spec.rb new file mode 100644 index 0000000000..a6d98914bd --- /dev/null +++ b/spec/ruby/library/datetime/add_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#+" do + it "is able to add sub-millisecond precision values" do + datetime = DateTime.new(2017) + (datetime + 0.00001).to_time.usec.should == 864000 + end +end diff --git a/spec/ruby/library/datetime/subtract_spec.rb b/spec/ruby/library/datetime/subtract_spec.rb new file mode 100644 index 0000000000..ed88533246 --- /dev/null +++ b/spec/ruby/library/datetime/subtract_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#-" do + it "is able to subtract sub-millisecond precision values" do + date = DateTime.new(2017) + ((date + 0.00001) - date).should == Rational(1, 100000) + end +end diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb index 6c4b7c5676..ad5048fedd 100644 --- a/spec/ruby/library/socket/unixsocket/open_spec.rb +++ b/spec/ruby/library/socket/unixsocket/open_spec.rb @@ -19,7 +19,7 @@ describe "UNIXSocket.open" do end it "opens a unix socket on the specified file and yields it to the block" do - UNIXSocket.send(@method, @path) do |client| + UNIXSocket.open(@path) do |client| client.addr[0].should == "AF_UNIX" client.closed?.should == false end diff --git a/spec/ruby/library/stringio/getbyte_spec.rb b/spec/ruby/library/stringio/getbyte_spec.rb index 99737614b9..3daa3d8e02 100644 --- a/spec/ruby/library/stringio/getbyte_spec.rb +++ b/spec/ruby/library/stringio/getbyte_spec.rb @@ -8,9 +8,9 @@ describe "StringIO#getbyte" do it "returns the 8-bit byte at the current position" do io = StringIO.new("example") - io.send(@method).should == 101 - io.send(@method).should == 120 - io.send(@method).should == 97 + io.getbyte.should == 101 + io.getbyte.should == 120 + io.getbyte.should == 97 end end diff --git a/spec/ruby/library/stringio/getc_spec.rb b/spec/ruby/library/stringio/getc_spec.rb index 9c0a28a1f0..263d418316 100644 --- a/spec/ruby/library/stringio/getc_spec.rb +++ b/spec/ruby/library/stringio/getc_spec.rb @@ -8,9 +8,9 @@ describe "StringIO#getc" do it "returns the character at the current position" do io = StringIO.new("example") - io.send(@method).should == ?e - io.send(@method).should == ?x - io.send(@method).should == ?a + io.getc.should == ?e + io.getc.should == ?x + io.getc.should == ?a end end diff --git a/spec/ruby/library/stringio/getch_spec.rb b/spec/ruby/library/stringio/getch_spec.rb index cd846705de..06670a178c 100644 --- a/spec/ruby/library/stringio/getch_spec.rb +++ b/spec/ruby/library/stringio/getch_spec.rb @@ -12,32 +12,32 @@ describe "StringIO#getch" do it "returns the character at the current position" do io = StringIO.new("example") - io.send(@method).should == ?e - io.send(@method).should == ?x - io.send(@method).should == ?a + io.getch.should == ?e + io.getch.should == ?x + io.getch.should == ?a end with_feature :encoding do it "increments #pos by the byte size of the character in multibyte strings" do io = StringIO.new("föóbar") - io.send(@method); io.pos.should == 1 # "f" has byte size 1 - io.send(@method); io.pos.should == 3 # "ö" has byte size 2 - io.send(@method); io.pos.should == 5 # "ó" has byte size 2 - io.send(@method); io.pos.should == 6 # "b" has byte size 1 + io.getch; io.pos.should == 1 # "f" has byte size 1 + io.getch; io.pos.should == 3 # "ö" has byte size 2 + io.getch; io.pos.should == 5 # "ó" has byte size 2 + io.getch; io.pos.should == 6 # "b" has byte size 1 end end it "returns nil at the end of the string" do # empty string case io = StringIO.new("") - io.send(@method).should == nil - io.send(@method).should == nil + io.getch.should == nil + io.getch.should == nil # non-empty string case io = StringIO.new("a") - io.send(@method) # skip one - io.send(@method).should == nil + io.getch # skip one + io.getch.should == nil end describe "StringIO#getch when self is not readable" do diff --git a/spec/ruby/library/stringio/readbyte_spec.rb b/spec/ruby/library/stringio/readbyte_spec.rb index b87b88b9c2..41a0911293 100644 --- a/spec/ruby/library/stringio/readbyte_spec.rb +++ b/spec/ruby/library/stringio/readbyte_spec.rb @@ -8,10 +8,10 @@ describe "StringIO#readbyte" do it "reads the next 8-bit byte from self's current position" do io = StringIO.new("example") - io.send(@method).should == 101 + io.readbyte.should == 101 io.pos = 4 - io.send(@method).should == 112 + io.readbyte.should == 112 end end diff --git a/spec/ruby/library/stringio/readchar_spec.rb b/spec/ruby/library/stringio/readchar_spec.rb index 1d2ad45b9a..38944819a2 100644 --- a/spec/ruby/library/stringio/readchar_spec.rb +++ b/spec/ruby/library/stringio/readchar_spec.rb @@ -8,10 +8,10 @@ describe "StringIO#readchar" do it "reads the next 8-bit byte from self's current position" do io = StringIO.new("example") - io.send(@method).should == ?e + io.readchar.should == ?e io.pos = 4 - io.send(@method).should == ?p + io.readchar.should == ?p end end diff --git a/spec/ruby/library/zlib/crc32_spec.rb b/spec/ruby/library/zlib/crc32_spec.rb index 5c39586ed9..02f95fd92c 100644 --- a/spec/ruby/library/zlib/crc32_spec.rb +++ b/spec/ruby/library/zlib/crc32_spec.rb @@ -24,6 +24,8 @@ describe "Zlib.crc32" do Zlib.crc32(test_string, 1).should == 1809313411 Zlib.crc32(test_string, 2**8).should == 1722745982 Zlib.crc32(test_string, 2**16).should == 1932511220 + Zlib.crc32("p", ~305419896).should == 4046865307 + Zlib.crc32("p", -305419897).should == 4046865307 lambda { Zlib.crc32(test_string, 2**128) }.should raise_error(RangeError) end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 88b3e47833..583b33d736 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -139,21 +139,21 @@ describe "C-API Encoding function" do it_behaves_like :rb_enc_get_index, :rb_enc_get_index it "returns the index of the encoding of a Symbol" do - @s.send(@method, :symbol).should >= 0 + @s.rb_enc_get_index(:symbol).should >= 0 end it "returns -1 as the index of nil" do - @s.send(@method, nil).should == -1 + @s.rb_enc_get_index(nil).should == -1 end it "returns -1 as the index for immediates" do - @s.send(@method, 1).should == -1 + @s.rb_enc_get_index(1).should == -1 end ruby_version_is "2.6" do it "returns -1 for an object without an encoding" do obj = Object.new - @s.send(@method, obj).should == -1 + @s.rb_enc_get_index(obj).should == -1 end end end diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 7a817b7436..541b58b48c 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -813,6 +813,15 @@ describe "CApiObject" do it "returns nil if the instance variable has not been initialized" do @o.rb_ivar_get(@test, :@bar).should == nil end + + it "returns nil if the instance variable has not been initialized and is not a valid Ruby name" do + @o.rb_ivar_get(@test, :bar).should == nil + end + + it 'returns the instance variable when it is not a valid Ruby name' do + @o.rb_ivar_set(@test, :foo, 27) + @o.rb_ivar_get(@test, :foo).should == 27 + end end describe "rb_ivar_set" do @@ -820,6 +829,15 @@ describe "CApiObject" do @o.rb_ivar_set(@test, :@foo, 42).should == 42 @test.instance_eval { @foo }.should == 42 end + + it "sets and returns the instance variable on an object" do + @o.rb_ivar_set(@test, :@foo, 42).should == 42 + @test.instance_eval { @foo }.should == 42 + end + + it 'sets and returns the instance variable when it is not a valid Ruby name' do + @o.rb_ivar_set(@test, :foo, 27).should == 27 + end end describe "rb_ivar_defined" do @@ -830,6 +848,10 @@ describe "CApiObject" do it "returns false if the instance variable is not defined" do @o.rb_ivar_defined(@test, :@bar).should == false end + + it "does not throw an error if the instance variable is not a valid Ruby name" do + @o.rb_ivar_defined(@test, :bar).should == false + end end end end diff --git a/spec/ruby/security/cve_2018_16396_spec.rb b/spec/ruby/security/cve_2018_16396_spec.rb new file mode 100644 index 0000000000..e462e0022d --- /dev/null +++ b/spec/ruby/security/cve_2018_16396_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' + +describe "Array#pack" do + + it "resists CVE-2018-16396 by tainting output based on input" do + "aAZBbHhuMmPp".each_char do |f| + ["123456".taint].pack(f).tainted?.should be_true + end + end + +end + +describe "String#unpack" do + + it "resists CVE-2018-16396 by tainting output based on input" do + "aAZBbHhuMm".each_char do |f| + "123456".taint.unpack(f).first.tainted?.should be_true + end + end + +end diff --git a/spec/ruby/shared/basicobject/send.rb b/spec/ruby/shared/basicobject/send.rb index f8c63c5522..2b79ab4c2c 100644 --- a/spec/ruby/shared/basicobject/send.rb +++ b/spec/ruby/shared/basicobject/send.rb @@ -29,6 +29,13 @@ describe :basicobject_send, shared: true do SendSpecs::Foo.send(@method, :bar).should == 'done' end + it "raises a TypeError if the method name is not a string or symbol" do + -> { SendSpecs.send(@method, nil) }.should raise_error(TypeError, /not a symbol nor a string/) + -> { SendSpecs.send(@method, 42) }.should raise_error(TypeError, /not a symbol nor a string/) + -> { SendSpecs.send(@method, 3.14) }.should raise_error(TypeError, /not a symbol nor a string/) + -> { SendSpecs.send(@method, true) }.should raise_error(TypeError, /not a symbol nor a string/) + end + it "raises a NameError if the corresponding method can't be found" do class SendSpecs::Foo def bar diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 70d638fff9..4128a636ad 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -41,7 +41,7 @@ describe :kernel_raise, shared: true do lambda { @object.raise(nil) }.should raise_error(TypeError) end - it "re-raises the rescued exception" do + it "re-raises the previously rescued exception if no exception is specified" do lambda do begin raise Exception, "outer" @@ -60,6 +60,22 @@ describe :kernel_raise, shared: true do ScratchPad.recorded.should be_nil end + it "re-raises a previously rescued exception without overwriting the backtrace" do + begin + raise 'raised' + rescue => raised + begin + raise_again_line = __LINE__; raise raised + rescue => raised_again + # This spec is written using #backtrace and matching the line number + # from the string, as backtrace_locations is a more advanced + # method that is not always supported by implementations. + + raised_again.backtrace.first.should_not include(":#{raise_again_line}:") + end + end + end + it "allows Exception, message, and backtrace parameters" do lambda do @object.raise(ArgumentError, "message", caller) |