diff options
Diffstat (limited to 'spec/ruby/core/array')
157 files changed, 12917 insertions, 0 deletions
diff --git a/spec/ruby/core/array/all_spec.rb b/spec/ruby/core/array/all_spec.rb new file mode 100644 index 0000000000..680e8c26fa --- /dev/null +++ b/spec/ruby/core/array/all_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#all?" do + @value_to_return = -> _ { true } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :all? + + it "ignores the block if there is an argument" do + -> { + ['bar', 'foobar'].all?(/bar/) { false }.should == true + }.should complain(/given block not used/) + end +end diff --git a/spec/ruby/core/array/allocate_spec.rb b/spec/ruby/core/array/allocate_spec.rb new file mode 100644 index 0000000000..04f7c0d0ad --- /dev/null +++ b/spec/ruby/core/array/allocate_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +describe "Array.allocate" do + it "returns an instance of Array" do + ary = Array.allocate + ary.should be_an_instance_of(Array) + end + + it "returns a fully-formed instance of Array" do + ary = Array.allocate + ary.size.should == 0 + ary << 1 + ary.should == [1] + end + + it "does not accept any arguments" do + -> { Array.allocate(1) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/array/any_spec.rb b/spec/ruby/core/array/any_spec.rb new file mode 100644 index 0000000000..b51ce62f0f --- /dev/null +++ b/spec/ruby/core/array/any_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#any?" do + describe 'with no block given (a default block of { |x| x } is implicit)' do + it "is false if the array is empty" do + empty_array = [] + empty_array.should_not.any? + end + + it "is false if the array is not empty, but all the members of the array are falsy" do + falsy_array = [false, nil, false] + falsy_array.should_not.any? + end + + it "is true if the array has any truthy members" do + not_empty_array = ['anything', nil] + not_empty_array.should.any? + end + end + + describe 'with a block given' do + @value_to_return = -> _ { false } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :any? + + it 'is false if the array is empty' do + empty_array = [] + empty_array.any? {|v| 1 == 1 }.should == false + end + + it 'is true if the block returns true for any member of the array' do + array_with_members = [false, false, true, false] + array_with_members.any? {|v| v == true }.should == true + end + + it 'is false if the block returns false for all members of the array' do + array_with_members = [false, false, true, false] + array_with_members.any? {|v| v == 42 }.should == false + end + end + + describe 'when given a pattern argument' do + it "ignores the block if there is an argument" do + -> { + ['bar', 'foobar'].any?(/bar/) { false }.should == true + }.should complain(/given block not used/) + end + end +end diff --git a/spec/ruby/core/array/append_spec.rb b/spec/ruby/core/array/append_spec.rb new file mode 100644 index 0000000000..c12473dc07 --- /dev/null +++ b/spec/ruby/core/array/append_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/push' + +describe "Array#<<" do + it "pushes the object onto the end of the array" do + ([ 1, 2 ] << "c" << "d" << [ 3, 4 ]).should == [1, 2, "c", "d", [3, 4]] + end + + it "returns self to allow chaining" do + a = [] + b = a + (a << 1).should equal(b) + (a << 2 << 3).should equal(b) + end + + it "correctly resizes the Array" do + a = [] + a.size.should == 0 + a << :foo + a.size.should == 1 + a << :bar << :baz + a.size.should == 3 + + a = [1, 2, 3] + a.shift + a.shift + a.shift + a << :foo + a.should == [:foo] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array << 5 }.should raise_error(FrozenError) + end +end + +describe "Array#append" do + it_behaves_like :array_push, :append +end diff --git a/spec/ruby/core/array/array_spec.rb b/spec/ruby/core/array/array_spec.rb new file mode 100644 index 0000000000..855f17348f --- /dev/null +++ b/spec/ruby/core/array/array_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Array" do + it "includes Enumerable" do + Array.include?(Enumerable).should == true + end +end diff --git a/spec/ruby/core/array/assoc_spec.rb b/spec/ruby/core/array/assoc_spec.rb new file mode 100644 index 0000000000..f0be3de795 --- /dev/null +++ b/spec/ruby/core/array/assoc_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#assoc" do + it "returns the first array whose 1st item is == obj or nil" do + s1 = ["colors", "red", "blue", "green"] + s2 = [:letters, "a", "b", "c"] + s3 = [4] + s4 = ["colors", "cyan", "yellow", "magenta"] + s5 = [:letters, "a", "i", "u"] + s_nil = [nil, nil] + a = [s1, s2, s3, s4, s5, s_nil] + a.assoc(s1.first).should equal(s1) + a.assoc(s2.first).should equal(s2) + a.assoc(s3.first).should equal(s3) + a.assoc(s4.first).should equal(s1) + a.assoc(s5.first).should equal(s2) + a.assoc(s_nil.first).should equal(s_nil) + a.assoc(4).should equal(s3) + a.assoc("key not in array").should be_nil + end + + it "calls == on first element of each array" do + key1 = 'it' + key2 = mock('key2') + items = [['not it', 1], [ArraySpecs::AssocKey.new, 2], ['na', 3]] + + items.assoc(key1).should equal(items[1]) + items.assoc(key2).should be_nil + end + + it "ignores any non-Array elements" do + [1, 2, 3].assoc(2).should be_nil + s1 = [4] + s2 = [5, 4, 3] + a = ["foo", [], s1, s2, nil, []] + a.assoc(s1.first).should equal(s1) + a.assoc(s2.first).should equal(s2) + end + + it "calls to_ary on non-array elements" do + s1 = [1, 2] + s2 = ArraySpecs::ArrayConvertible.new(2, 3) + a = [s1, s2] + + s1.should_not_receive(:to_ary) + a.assoc(s1.first).should equal(s1) + + a.assoc(2).should == [2, 3] + s2.called.should equal(:to_ary) + end +end diff --git a/spec/ruby/core/array/at_spec.rb b/spec/ruby/core/array/at_spec.rb new file mode 100644 index 0000000000..8bc789fef7 --- /dev/null +++ b/spec/ruby/core/array/at_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#at" do + it "returns the (n+1)'th element for the passed index n" do + a = [1, 2, 3, 4, 5, 6] + a.at(0).should == 1 + a.at(1).should == 2 + a.at(5).should == 6 + end + + it "returns nil if the given index is greater than or equal to the array's length" do + a = [1, 2, 3, 4, 5, 6] + a.at(6).should == nil + a.at(7).should == nil + end + + it "returns the (-n)'th element from the last, for the given negative index n" do + a = [1, 2, 3, 4, 5, 6] + a.at(-1).should == 6 + a.at(-2).should == 5 + a.at(-6).should == 1 + end + + it "returns nil if the given index is less than -len, where len is length of the array" do + a = [1, 2, 3, 4, 5, 6] + a.at(-7).should == nil + a.at(-8).should == nil + end + + it "does not extend the array unless the given index is out of range" do + a = [1, 2, 3, 4, 5, 6] + a.length.should == 6 + a.at(100) + a.length.should == 6 + a.at(-100) + a.length.should == 6 + end + + it "tries to convert the passed argument to an Integer using #to_int" do + a = ["a", "b", "c"] + a.at(0.5).should == "a" + + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + a.at(obj).should == "c" + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [].at("cat") }.should raise_error(TypeError) + end + + it "raises an ArgumentError when 2 or more arguments are passed" do + -> { [:a, :b].at(0,1) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/array/bsearch_index_spec.rb b/spec/ruby/core/array/bsearch_index_spec.rb new file mode 100644 index 0000000000..94d85b37f3 --- /dev/null +++ b/spec/ruby/core/array/bsearch_index_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../spec_helper' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#bsearch_index" do + context "when not passed a block" do + before :each do + @enum = [1, 2, 42, 100, 666].bsearch_index + end + + it "returns an Enumerator" do + @enum.should be_an_instance_of(Enumerator) + end + + it "returns an Enumerator with unknown size" do + @enum.size.should be_nil + end + + it "returns index of element when block condition is satisfied" do + @enum.each { |x| x >= 33 }.should == 2 + end + end + + it "raises a TypeError when block returns a String" do + -> { [1, 2, 3].bsearch_index { "not ok" } }.should raise_error(TypeError) + end + + it "returns nil when block is empty" do + [1, 2, 3].bsearch_index {}.should be_nil + end + + context "minimum mode" do + before :each do + @array = [0, 4, 7, 10, 12] + end + + it "returns index of first element which satisfies the block" do + @array.bsearch_index { |x| x >= 4 }.should == 1 + @array.bsearch_index { |x| x >= 6 }.should == 2 + @array.bsearch_index { |x| x >= -1 }.should == 0 + end + + it "returns nil when block condition is never satisfied" do + @array.bsearch_index { false }.should be_nil + @array.bsearch_index { |x| x >= 100 }.should be_nil + end + end + + context "find any mode" do + before :each do + @array = [0, 4, 7, 10, 12] + end + + it "returns the index of any matched elements where element is between 4 <= x < 8" do + [1, 2].should include(@array.bsearch_index { |x| 1 - x / 4 }) + end + + it "returns the index of any matched elements where element is between 8 <= x < 10" do + @array.bsearch_index { |x| 4 - x / 2 }.should be_nil + end + + it "returns nil when block never returns 0" do + @array.bsearch_index { |x| 1 }.should be_nil + @array.bsearch_index { |x| -1 }.should be_nil + end + + context "magnitude does not effect the result" do + it "returns the index of any matched elements where element is between 4n <= xn < 8n" do + [1, 2].should include(@array.bsearch_index { |x| (1 - x / 4) * (2**100) }) + end + + it "returns nil when block never returns 0" do + @array.bsearch_index { |x| 1 * (2**100) }.should be_nil + @array.bsearch_index { |x| (-1) * (2**100) }.should be_nil + end + + it "handles values from Integer#coerce" do + [1, 2].should include(@array.bsearch_index { |x| (2**100).coerce((1 - x / 4) * (2**100)).first }) + end + end + end +end diff --git a/spec/ruby/core/array/bsearch_spec.rb b/spec/ruby/core/array/bsearch_spec.rb new file mode 100644 index 0000000000..8fa6245dbf --- /dev/null +++ b/spec/ruby/core/array/bsearch_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#bsearch" do + it "returns an Enumerator when not passed a block" do + [1].bsearch.should be_an_instance_of(Enumerator) + end + + it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3] + + it "raises a TypeError if the block returns an Object" do + -> { [1].bsearch { Object.new } }.should raise_error(TypeError) + end + + it "raises a TypeError if the block returns a String" do + -> { [1].bsearch { "1" } }.should raise_error(TypeError) + end + + context "with a block returning true or false" do + it "returns nil if the block returns false for every element" do + [0, 1, 2, 3].bsearch { |x| x > 3 }.should be_nil + end + + it "returns nil if the block returns nil for every element" do + [0, 1, 2, 3].bsearch { |x| nil }.should be_nil + end + + it "returns element at zero if the block returns true for every element" do + [0, 1, 2, 3].bsearch { |x| x < 4 }.should == 0 + + end + + it "returns the element at the smallest index for which block returns true" do + [0, 1, 3, 4].bsearch { |x| x >= 2 }.should == 3 + [0, 1, 3, 4].bsearch { |x| x >= 1 }.should == 1 + end + end + + context "with a block returning negative, zero, positive numbers" do + it "returns nil if the block returns less than zero for every element" do + [0, 1, 2, 3].bsearch { |x| x <=> 5 }.should be_nil + end + + it "returns nil if the block returns greater than zero for every element" do + [0, 1, 2, 3].bsearch { |x| x <=> -1 }.should be_nil + + end + + it "returns nil if the block never returns zero" do + [0, 1, 3, 4].bsearch { |x| x <=> 2 }.should be_nil + end + + it "accepts (+/-)Float::INFINITY from the block" do + [0, 1, 3, 4].bsearch { |x| Float::INFINITY }.should be_nil + [0, 1, 3, 4].bsearch { |x| -Float::INFINITY }.should be_nil + end + + it "returns an element at an index for which block returns 0.0" do + result = [0, 1, 2, 3, 4].bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 } + result.should == 2 + end + + it "returns an element at an index for which block returns 0" do + result = [0, 1, 2, 3, 4].bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 } + [1, 2].should include(result) + end + end + + context "with a block that calls break" do + it "returns nil if break is called without a value" do + ['a', 'b', 'c'].bsearch { |v| break }.should be_nil + end + + it "returns nil if break is called with a nil value" do + ['a', 'b', 'c'].bsearch { |v| break nil }.should be_nil + end + + it "returns object if break is called with an object" do + ['a', 'b', 'c'].bsearch { |v| break 1234 }.should == 1234 + ['a', 'b', 'c'].bsearch { |v| break 'hi' }.should == 'hi' + ['a', 'b', 'c'].bsearch { |v| break [42] }.should == [42] + end + end +end diff --git a/spec/ruby/core/array/clear_spec.rb b/spec/ruby/core/array/clear_spec.rb new file mode 100644 index 0000000000..81ba56e01e --- /dev/null +++ b/spec/ruby/core/array/clear_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#clear" do + it "removes all elements" do + a = [1, 2, 3, 4] + a.clear.should equal(a) + a.should == [] + end + + it "returns self" do + a = [1] + a.should equal a.clear + end + + it "leaves the Array empty" do + a = [1] + a.clear + a.should.empty? + a.size.should == 0 + end + + it "does not accept any arguments" do + -> { [1].clear(true) }.should raise_error(ArgumentError) + end + + it "raises a FrozenError on a frozen array" do + a = [1] + a.freeze + -> { a.clear }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/clone_spec.rb b/spec/ruby/core/array/clone_spec.rb new file mode 100644 index 0000000000..e22a6c6d53 --- /dev/null +++ b/spec/ruby/core/array/clone_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/clone' + +describe "Array#clone" do + it_behaves_like :array_clone, :clone + + it "copies frozen status from the original" do + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + a.freeze + aa = a.clone + bb = b.clone + + aa.should.frozen? + bb.should_not.frozen? + end + + it "copies singleton methods" do + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + def a.a_singleton_method; end + aa = a.clone + bb = b.clone + + a.respond_to?(:a_singleton_method).should be_true + b.respond_to?(:a_singleton_method).should be_false + aa.respond_to?(:a_singleton_method).should be_true + bb.respond_to?(:a_singleton_method).should be_false + end +end diff --git a/spec/ruby/core/array/collect_spec.rb b/spec/ruby/core/array/collect_spec.rb new file mode 100644 index 0000000000..0ad4c283b1 --- /dev/null +++ b/spec/ruby/core/array/collect_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/collect' + +describe "Array#collect" do + it_behaves_like :array_collect, :collect +end + +describe "Array#collect!" do + it_behaves_like :array_collect_b, :collect! +end diff --git a/spec/ruby/core/array/combination_spec.rb b/spec/ruby/core/array/combination_spec.rb new file mode 100644 index 0000000000..f16d6f98fc --- /dev/null +++ b/spec/ruby/core/array/combination_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' + +describe "Array#combination" do + before :each do + @array = [1, 2, 3, 4] + end + + it "returns an enumerator when no block is provided" do + @array.combination(2).should be_an_instance_of(Enumerator) + end + + it "returns self when a block is given" do + @array.combination(2){}.should equal(@array) + end + + it "yields nothing for out of bounds length and return self" do + @array.combination(5).to_a.should == [] + @array.combination(-1).to_a.should == [] + end + + it "yields the expected combinations" do + @array.combination(3).to_a.sort.should == [[1,2,3],[1,2,4],[1,3,4],[2,3,4]] + end + + it "yields nothing if the argument is out of bounds" do + @array.combination(-1).to_a.should == [] + @array.combination(5).to_a.should == [] + end + + it "yields a copy of self if the argument is the size of the receiver" do + r = @array.combination(4).to_a + r.should == [@array] + r[0].should_not equal(@array) + end + + it "yields [] when length is 0" do + @array.combination(0).to_a.should == [[]] # one combination of length 0 + [].combination(0).to_a.should == [[]] # one combination of length 0 + end + + it "yields a partition consisting of only singletons" do + @array.combination(1).to_a.sort.should == [[1],[2],[3],[4]] + end + + it "generates from a defensive copy, ignoring mutations" do + accum = [] + @array.combination(2) do |x| + accum << x + @array[0] = 1 + end + accum.should == [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] + end + + describe "when no block is given" do + describe "returned Enumerator" do + describe "size" do + it "returns 0 when the number of combinations is < 0" do + @array.combination(-1).size.should == 0 + [].combination(-2).size.should == 0 + end + it "returns the binomial coefficient between the array size the number of combinations" do + @array.combination(5).size.should == 0 + @array.combination(4).size.should == 1 + @array.combination(3).size.should == 4 + @array.combination(2).size.should == 6 + @array.combination(1).size.should == 4 + @array.combination(0).size.should == 1 + [].combination(0).size.should == 1 + [].combination(1).size.should == 0 + end + end + end + end +end diff --git a/spec/ruby/core/array/compact_spec.rb b/spec/ruby/core/array/compact_spec.rb new file mode 100644 index 0000000000..83b3fa2a89 --- /dev/null +++ b/spec/ruby/core/array/compact_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#compact" do + it "returns a copy of array with all nil elements removed" do + a = [1, 2, 4] + a.compact.should == [1, 2, 4] + a = [1, nil, 2, 4] + a.compact.should == [1, 2, 4] + a = [1, 2, 4, nil] + a.compact.should == [1, 2, 4] + a = [nil, 1, 2, 4] + a.compact.should == [1, 2, 4] + end + + it "does not return self" do + a = [1, 2, 3] + a.compact.should_not equal(a) + end + + it "does not return subclass instance for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3, nil].compact.should be_an_instance_of(Array) + end +end + +describe "Array#compact!" do + it "removes all nil elements" do + a = ['a', nil, 'b', false, 'c'] + a.compact!.should equal(a) + a.should == ["a", "b", false, "c"] + a = [nil, 'a', 'b', false, 'c'] + a.compact!.should equal(a) + a.should == ["a", "b", false, "c"] + a = ['a', 'b', false, 'c', nil] + a.compact!.should equal(a) + a.should == ["a", "b", false, "c"] + end + + it "returns self if some nil elements are removed" do + a = ['a', nil, 'b', false, 'c'] + a.compact!.should equal a + end + + it "returns nil if there are no nil elements to remove" do + [1, 2, false, 3].compact!.should == nil + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.compact! }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/comparison_spec.rb b/spec/ruby/core/array/comparison_spec.rb new file mode 100644 index 0000000000..5d1c3265f1 --- /dev/null +++ b/spec/ruby/core/array/comparison_spec.rb @@ -0,0 +1,97 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#<=>" do + it "calls <=> left to right and return first non-0 result" do + [-1, +1, nil, "foobar"].each do |result| + lhs = Array.new(3) { mock("#{result}") } + rhs = Array.new(3) { mock("#{result}") } + + lhs[0].should_receive(:<=>).with(rhs[0]).and_return(0) + lhs[1].should_receive(:<=>).with(rhs[1]).and_return(result) + lhs[2].should_not_receive(:<=>) + + (lhs <=> rhs).should == result + end + end + + it "returns 0 if the arrays are equal" do + ([] <=> []).should == 0 + ([1, 2, 3, 4, 5, 6] <=> [1, 2, 3, 4, 5.0, 6.0]).should == 0 + end + + it "returns -1 if the array is shorter than the other array" do + ([] <=> [1]).should == -1 + ([1, 1] <=> [1, 1, 1]).should == -1 + end + + it "returns +1 if the array is longer than the other array" do + ([1] <=> []).should == +1 + ([1, 1, 1] <=> [1, 1]).should == +1 + end + + it "returns -1 if the arrays have same length and a pair of corresponding elements returns -1 for <=>" do + eq_l = mock('an object equal to the other') + eq_r = mock('an object equal to the other') + eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0) + + less = mock('less than the other') + greater = mock('greater then the other') + less.should_receive(:<=>).with(greater).any_number_of_times.and_return(-1) + + rest = mock('an rest element of the arrays') + rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0) + lhs = [eq_l, eq_l, less, rest] + rhs = [eq_r, eq_r, greater, rest] + + (lhs <=> rhs).should == -1 + end + + it "returns +1 if the arrays have same length and a pair of corresponding elements returns +1 for <=>" do + eq_l = mock('an object equal to the other') + eq_r = mock('an object equal to the other') + eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0) + + greater = mock('greater then the other') + less = mock('less than the other') + greater.should_receive(:<=>).with(less).any_number_of_times.and_return(+1) + + rest = mock('an rest element of the arrays') + rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0) + lhs = [eq_l, eq_l, greater, rest] + rhs = [eq_r, eq_r, less, rest] + + (lhs <=> rhs).should == +1 + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + (empty <=> empty).should == 0 + (empty <=> []).should == 1 + ([] <=> empty).should == -1 + + (ArraySpecs.recursive_array <=> []).should == 1 + ([] <=> ArraySpecs.recursive_array).should == -1 + + (ArraySpecs.recursive_array <=> ArraySpecs.empty_recursive_array).should == nil + + array = ArraySpecs.recursive_array + (array <=> array).should == 0 + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('to_ary') + obj.stub!(:to_ary).and_return([1, 2, 3]) + ([4, 5] <=> obj).should == ([4, 5] <=> obj.to_ary) + end + + it "does not call #to_ary on Array subclasses" do + obj = ArraySpecs::ToAryArray[5, 6, 7] + obj.should_not_receive(:to_ary) + ([5, 6, 7] <=> obj).should == 0 + end + + it "returns nil when the argument is not array-like" do + ([] <=> false).should be_nil + end +end diff --git a/spec/ruby/core/array/concat_spec.rb b/spec/ruby/core/array/concat_spec.rb new file mode 100644 index 0000000000..f3cab9c17c --- /dev/null +++ b/spec/ruby/core/array/concat_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#concat" do + it "returns the array itself" do + ary = [1,2,3] + ary.concat([4,5,6]).equal?(ary).should be_true + end + + it "appends the elements in the other array" do + ary = [1, 2, 3] + ary.concat([9, 10, 11]).should equal(ary) + ary.should == [1, 2, 3, 9, 10, 11] + ary.concat([]) + ary.should == [1, 2, 3, 9, 10, 11] + end + + it "does not loop endlessly when argument is self" do + ary = ["x", "y"] + ary.concat(ary).should == ["x", "y", "x", "y"] + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('to_ary') + obj.should_receive(:to_ary).and_return(["x", "y"]) + [4, 5, 6].concat(obj).should == [4, 5, 6, "x", "y"] + end + + it "does not call #to_ary on Array subclasses" do + obj = ArraySpecs::ToAryArray[5, 6, 7] + obj.should_not_receive(:to_ary) + [].concat(obj).should == [5, 6, 7] + end + + it "raises a FrozenError when Array is frozen and modification occurs" do + -> { ArraySpecs.frozen_array.concat [1] }.should raise_error(FrozenError) + end + + # see [ruby-core:23666] + it "raises a FrozenError when Array is frozen and no modification occurs" do + -> { ArraySpecs.frozen_array.concat([]) }.should raise_error(FrozenError) + end + + it "appends elements to an Array with enough capacity that has been shifted" do + ary = [1, 2, 3, 4, 5] + 2.times { ary.shift } + 2.times { ary.pop } + ary.concat([5, 6]).should == [3, 5, 6] + end + + it "appends elements to an Array without enough capacity that has been shifted" do + ary = [1, 2, 3, 4] + 3.times { ary.shift } + ary.concat([5, 6]).should == [4, 5, 6] + end + + it "takes multiple arguments" do + ary = [1, 2] + ary.concat [3, 4] + ary.should == [1, 2, 3, 4] + end + + it "concatenates the initial value when given arguments contain 2 self" do + ary = [1, 2] + ary.concat ary, ary + ary.should == [1, 2, 1, 2, 1, 2] + end + + it "returns self when given no arguments" do + ary = [1, 2] + ary.concat.should equal(ary) + ary.should == [1, 2] + end +end diff --git a/spec/ruby/core/array/constructor_spec.rb b/spec/ruby/core/array/constructor_spec.rb new file mode 100644 index 0000000000..6f36074c45 --- /dev/null +++ b/spec/ruby/core/array/constructor_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array.[]" do + it "returns a new array populated with the given elements" do + obj = Object.new + Array.[](5, true, nil, 'a', "Ruby", obj).should == [5, true, nil, "a", "Ruby", obj] + + a = ArraySpecs::MyArray.[](5, true, nil, 'a', "Ruby", obj) + a.should be_an_instance_of(ArraySpecs::MyArray) + a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect + end +end + +describe "Array[]" do + it "is a synonym for .[]" do + obj = Object.new + Array[5, true, nil, 'a', "Ruby", obj].should == Array.[](5, true, nil, "a", "Ruby", obj) + + a = ArraySpecs::MyArray[5, true, nil, 'a', "Ruby", obj] + a.should be_an_instance_of(ArraySpecs::MyArray) + a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect + end +end diff --git a/spec/ruby/core/array/count_spec.rb b/spec/ruby/core/array/count_spec.rb new file mode 100644 index 0000000000..e778233c16 --- /dev/null +++ b/spec/ruby/core/array/count_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#count" do + it "returns the number of elements" do + [:a, :b, :c].count.should == 3 + end + + it "returns the number of elements that equal the argument" do + [:a, :b, :b, :c].count(:b).should == 2 + end + + it "returns the number of element for which the block evaluates to true" do + [:a, :b, :c].count { |s| s != :b }.should == 2 + end + + it "ignores the block if there is an argument" do + -> { + [:a, :b, :b, :c].count(:b) { |e| e.size > 10 }.should == 2 + }.should complain(/given block not used/) + end + + context "when a block argument given" do + it_behaves_like :array_iterable_and_tolerating_size_increasing, :count + end +end diff --git a/spec/ruby/core/array/cycle_spec.rb b/spec/ruby/core/array/cycle_spec.rb new file mode 100644 index 0000000000..7219b49883 --- /dev/null +++ b/spec/ruby/core/array/cycle_spec.rb @@ -0,0 +1,101 @@ +require_relative '../../spec_helper' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#cycle" do + before :each do + ScratchPad.record [] + + @array = [1, 2, 3] + @prc = -> x { ScratchPad << x } + end + + it "does not yield and returns nil when the array is empty and passed value is an integer" do + [].cycle(6, &@prc).should be_nil + ScratchPad.recorded.should == [] + end + + it "does not yield and returns nil when the array is empty and passed value is nil" do + [].cycle(nil, &@prc).should be_nil + ScratchPad.recorded.should == [] + end + + it "does not yield and returns nil when passed 0" do + @array.cycle(0, &@prc).should be_nil + ScratchPad.recorded.should == [] + end + + it "iterates the array 'count' times yielding each item to the block" do + @array.cycle(2, &@prc) + ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3] + end + + it "iterates indefinitely when not passed a count" do + @array.cycle do |x| + ScratchPad << x + break if ScratchPad.recorded.size > 7 + end + ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2] + end + + it "iterates indefinitely when passed nil" do + @array.cycle(nil) do |x| + ScratchPad << x + break if ScratchPad.recorded.size > 7 + end + ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2] + end + + it "does not rescue StopIteration when not passed a count" do + -> do + @array.cycle { raise StopIteration } + end.should raise_error(StopIteration) + end + + it "does not rescue StopIteration when passed a count" do + -> do + @array.cycle(3) { raise StopIteration } + end.should raise_error(StopIteration) + end + + it "iterates the array Integer(count) times when passed a Float count" do + @array.cycle(2.7, &@prc) + ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3] + end + + it "calls #to_int to convert count to an Integer" do + count = mock("cycle count 2") + count.should_receive(:to_int).and_return(2) + + @array.cycle(count, &@prc) + ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3] + end + + it "raises a TypeError if #to_int does not return an Integer" do + count = mock("cycle count 2") + count.should_receive(:to_int).and_return("2") + + -> { @array.cycle(count, &@prc) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed a String" do + -> { @array.cycle("4") { } }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Object" do + -> { @array.cycle(mock("cycle count")) { } }.should raise_error(TypeError) + end + + it "raises a TypeError if passed true" do + -> { @array.cycle(true) { } }.should raise_error(TypeError) + end + + it "raises a TypeError if passed false" do + -> { @array.cycle(false) { } }.should raise_error(TypeError) + end + + before :all do + @object = [1, 2, 3, 4] + @empty_object = [] + end + it_should_behave_like :enumeratorized_with_cycle_size +end diff --git a/spec/ruby/core/array/deconstruct_spec.rb b/spec/ruby/core/array/deconstruct_spec.rb new file mode 100644 index 0000000000..ad67abe47b --- /dev/null +++ b/spec/ruby/core/array/deconstruct_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Array#deconstruct" do + it "returns self" do + array = [1] + + array.deconstruct.should equal array + end +end diff --git a/spec/ruby/core/array/delete_at_spec.rb b/spec/ruby/core/array/delete_at_spec.rb new file mode 100644 index 0000000000..80ec643702 --- /dev/null +++ b/spec/ruby/core/array/delete_at_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#delete_at" do + it "removes the element at the specified index" do + a = [1, 2, 3, 4] + a.delete_at(2) + a.should == [1, 2, 4] + a.delete_at(-1) + a.should == [1, 2] + end + + it "returns the removed element at the specified index" do + a = [1, 2, 3, 4] + a.delete_at(2).should == 3 + a.delete_at(-1).should == 4 + end + + it "returns nil and makes no modification if the index is out of range" do + a = [1, 2] + a.delete_at(3).should == nil + a.should == [1, 2] + a.delete_at(-3).should == nil + a.should == [1, 2] + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(-1) + [1, 2].delete_at(obj).should == 2 + end + + it "accepts negative indices" do + a = [1, 2] + a.delete_at(-2).should == 1 + end + + it "raises a FrozenError on a frozen array" do + -> { [1,2,3].freeze.delete_at(0) }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/delete_if_spec.rb b/spec/ruby/core/array/delete_if_spec.rb new file mode 100644 index 0000000000..10972eee0e --- /dev/null +++ b/spec/ruby/core/array/delete_if_spec.rb @@ -0,0 +1,82 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/enumeratorize' +require_relative 'shared/delete_if' +require_relative 'shared/iterable_and_tolerating_size_increasing' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#delete_if" do + before do + @a = [ "a", "b", "c" ] + end + + it "removes each element for which block returns true" do + @a = [ "a", "b", "c" ] + @a.delete_if { |x| x >= "b" } + @a.should == ["a"] + end + + it "returns self" do + @a.delete_if{ true }.equal?(@a).should be_true + end + + it_behaves_like :enumeratorize, :delete_if + + it "returns self when called on an Array emptied with #shift" do + array = [1] + array.shift + array.delete_if { |x| true }.should equal(array) + end + + it "returns an Enumerator if no block given, and the enumerator can modify the original array" do + enum = @a.delete_if + enum.should be_an_instance_of(Enumerator) + @a.should_not be_empty + enum.each { true } + @a.should be_empty + end + + it "returns an Enumerator if no block given, and the array is frozen" do + @a.freeze.delete_if.should be_an_instance_of(Enumerator) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.delete_if {} }.should raise_error(FrozenError) + end + + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.delete_if {} }.should raise_error(FrozenError) + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.delete_if { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "only removes elements for which the block returns true, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.delete_if do |e| + case e + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + a.should == [1, 3, 4] + end + + it_behaves_like :enumeratorized_with_origin_size, :delete_if, [1,2,3] + it_behaves_like :delete_if, :delete_if + + @value_to_return = -> _ { false } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :delete_if +end diff --git a/spec/ruby/core/array/delete_spec.rb b/spec/ruby/core/array/delete_spec.rb new file mode 100644 index 0000000000..dddbbe6bd3 --- /dev/null +++ b/spec/ruby/core/array/delete_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#delete" do + it "removes elements that are #== to object" do + x = mock('delete') + def x.==(other) 3 == other end + + a = [1, 2, 3, x, 4, 3, 5, x] + a.delete mock('not contained') + a.should == [1, 2, 3, x, 4, 3, 5, x] + + a.delete 3 + a.should == [1, 2, 4, 5] + end + + it "calculates equality correctly for reference values" do + a = ["foo", "bar", "foo", "quux", "foo"] + a.delete "foo" + a.should == ["bar","quux"] + end + + it "returns object or nil if no elements match object" do + [1, 2, 4, 5].delete(1).should == 1 + [1, 2, 4, 5].delete(3).should == nil + end + + it "may be given a block that is executed if no element matches object" do + [1].delete(1) {:not_found}.should == 1 + [].delete('a') {:not_found}.should == :not_found + end + + it "returns nil if the array is empty due to a shift" do + a = [1] + a.shift + a.delete(nil).should == nil + end + + it "returns nil on a frozen array if a modification does not take place" do + [1, 2, 3].freeze.delete(0).should == nil + end + + it "raises a FrozenError on a frozen array" do + -> { [1, 2, 3].freeze.delete(1) }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/difference_spec.rb b/spec/ruby/core/array/difference_spec.rb new file mode 100644 index 0000000000..9f7d4c4a1a --- /dev/null +++ b/spec/ruby/core/array/difference_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/difference' + +describe "Array#difference" do + it_behaves_like :array_binary_difference, :difference + + it "returns a copy when called without any parameter" do + x = [1, 2, 3, 2] + x.difference.should == x + x.difference.should_not equal x + end + + it "does not return subclass instances for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].difference.should be_an_instance_of(Array) + end + + it "accepts multiple arguments" do + x = [1, 2, 3, 1] + x.difference([], [0, 1], [3, 4], [3]).should == [2] + end +end diff --git a/spec/ruby/core/array/dig_spec.rb b/spec/ruby/core/array/dig_spec.rb new file mode 100644 index 0000000000..f2d8ff47fd --- /dev/null +++ b/spec/ruby/core/array/dig_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' + +describe "Array#dig" do + + it "returns #at with one arg" do + ['a'].dig(0).should == 'a' + ['a'].dig(1).should be_nil + end + + it "recurses array elements" do + a = [ [ 1, [2, '3'] ] ] + a.dig(0, 0).should == 1 + a.dig(0, 1, 1).should == '3' + a.dig(0, -1, 0).should == 2 + end + + it "returns the nested value specified if the sequence includes a key" do + a = [42, { foo: :bar }] + a.dig(1, :foo).should == :bar + end + + it "raises a TypeError for a non-numeric index" do + -> { + ['a'].dig(:first) + }.should raise_error(TypeError) + end + + it "raises a TypeError if any intermediate step does not respond to #dig" do + a = [1, 2] + -> { + a.dig(0, 1) + }.should raise_error(TypeError) + end + + it "raises an ArgumentError if no arguments provided" do + -> { + [10].dig() + }.should raise_error(ArgumentError) + end + + it "returns nil if any intermediate step is nil" do + a = [[1, [2, 3]]] + a.dig(1, 2, 3).should == nil + end + + it "calls #dig on the result of #at with the remaining arguments" do + h = [[nil, [nil, nil, 42]]] + h[0].should_receive(:dig).with(1, 2).and_return(42) + h.dig(0, 1, 2).should == 42 + end + +end diff --git a/spec/ruby/core/array/drop_spec.rb b/spec/ruby/core/array/drop_spec.rb new file mode 100644 index 0000000000..5926c291b8 --- /dev/null +++ b/spec/ruby/core/array/drop_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#drop" do + it "removes the specified number of elements from the start of the array" do + [1, 2, 3, 4, 5].drop(2).should == [3, 4, 5] + end + + it "raises an ArgumentError if the number of elements specified is negative" do + -> { [1, 2].drop(-3) }.should raise_error(ArgumentError) + end + + it "returns an empty Array if all elements are dropped" do + [1, 2].drop(2).should == [] + end + + it "returns an empty Array when called on an empty Array" do + [].drop(0).should == [] + end + + it "does not remove any elements when passed zero" do + [1, 2].drop(0).should == [1, 2] + end + + it "returns an empty Array if more elements than exist are dropped" do + [1, 2].drop(3).should == [] + end + + it 'acts correctly after a shift' do + ary = [nil, 1, 2] + ary.shift + ary.drop(1).should == [2] + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(2) + + [1, 2, 3].drop(obj).should == [3] + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [1, 2].drop("cat") }.should raise_error(TypeError) + end + + it "raises a TypeError when the passed argument isn't an integer and #to_int returns non-Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("cat") + + -> { [1, 2].drop(obj) }.should raise_error(TypeError) + end + + it 'returns a Array instance for Array subclasses' do + ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should be_an_instance_of(Array) + end +end diff --git a/spec/ruby/core/array/drop_while_spec.rb b/spec/ruby/core/array/drop_while_spec.rb new file mode 100644 index 0000000000..bd46e8b882 --- /dev/null +++ b/spec/ruby/core/array/drop_while_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#drop_while" do + @value_to_return = -> _ { true } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :drop_while + + it "removes elements from the start of the array while the block evaluates to true" do + [1, 2, 3, 4].drop_while { |n| n < 4 }.should == [4] + end + + it "removes elements from the start of the array until the block returns nil" do + [1, 2, 3, nil, 5].drop_while { |n| n }.should == [nil, 5] + end + + it "removes elements from the start of the array until the block returns false" do + [1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5] + end + + it 'returns a Array instance for Array subclasses' do + ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should be_an_instance_of(Array) + end +end diff --git a/spec/ruby/core/array/dup_spec.rb b/spec/ruby/core/array/dup_spec.rb new file mode 100644 index 0000000000..17f467d5fc --- /dev/null +++ b/spec/ruby/core/array/dup_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/clone' + +describe "Array#dup" do + it_behaves_like :array_clone, :dup # FIX: no, clone and dup are not alike + + it "does not copy frozen status from the original" do + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + a.freeze + aa = a.dup + bb = b.dup + + aa.frozen?.should be_false + bb.frozen?.should be_false + end + + it "does not copy singleton methods" do + a = [1, 2, 3, 4] + b = [1, 2, 3, 4] + def a.a_singleton_method; end + aa = a.dup + bb = b.dup + + a.respond_to?(:a_singleton_method).should be_true + b.respond_to?(:a_singleton_method).should be_false + aa.respond_to?(:a_singleton_method).should be_false + bb.respond_to?(:a_singleton_method).should be_false + end +end diff --git a/spec/ruby/core/array/each_index_spec.rb b/spec/ruby/core/array/each_index_spec.rb new file mode 100644 index 0000000000..3a4bca9251 --- /dev/null +++ b/spec/ruby/core/array/each_index_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/enumeratorize' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +describe "Array#each_index" do + before :each do + ScratchPad.record [] + end + + it "passes the index of each element to the block" do + a = ['a', 'b', 'c', 'd'] + a.each_index { |i| ScratchPad << i } + ScratchPad.recorded.should == [0, 1, 2, 3] + end + + it "returns self" do + a = [:a, :b, :c] + a.each_index { |i| }.should equal(a) + end + + it "is not confused by removing elements from the front" do + a = [1, 2, 3] + + a.shift + ScratchPad.record [] + a.each_index { |i| ScratchPad << i } + ScratchPad.recorded.should == [0, 1] + + a.shift + ScratchPad.record [] + a.each_index { |i| ScratchPad << i } + ScratchPad.recorded.should == [0] + end + + it_behaves_like :enumeratorize, :each_index + it_behaves_like :enumeratorized_with_origin_size, :each_index, [1,2,3] +end + +describe "Array#each_index" do + it "tolerates increasing an array size during iteration" do + array = [:a, :b, :c] + ScratchPad.record [] + i = 0 + + array.each_index do |index| + ScratchPad << index + array << i if i < 100 + i += 1 + end + + ScratchPad.recorded.should == (0..102).to_a # element indices + end +end diff --git a/spec/ruby/core/array/each_spec.rb b/spec/ruby/core/array/each_spec.rb new file mode 100644 index 0000000000..f4b5b758d0 --- /dev/null +++ b/spec/ruby/core/array/each_spec.rb @@ -0,0 +1,82 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/enumeratorize' +require_relative 'shared/iterable_and_tolerating_size_increasing' +require_relative '../enumerable/shared/enumeratorized' + +# Mutating the array while it is being iterated is discouraged as it can result in confusing behavior. +# Yet a Ruby implementation must not crash in such a case, and following the simple CRuby behavior makes sense. +# CRuby simply reads the array storage and checks the size for every iteration; +# like `i = 0; while i < size; yield self[i]; i += 1; end` + +describe "Array#each" do + it "yields each element to the block" do + a = [] + x = [1, 2, 3] + x.each { |item| a << item }.should equal(x) + a.should == [1, 2, 3] + end + + it "yields each element to the block even if the array is changed during iteration" do + a = [1, 2, 3, 4, 5] + iterated = [] + a.each { |x| iterated << x; a << x+5 if x.even? } + iterated.should == [1, 2, 3, 4, 5, 7, 9] + end + + it "yields only elements that are still in the array" do + a = [0, 1, 2, 3, 4] + iterated = [] + a.each { |x| iterated << x; a.pop if x.even? } + iterated.should == [0, 1, 2] + end + + it "yields elements based on an internal index" do + a = [0, 1, 2, 3, 4] + iterated = [] + a.each { |x| iterated << x; a.shift if x.even? } + iterated.should == [0, 2, 4] + end + + it "yields the same element multiple times if inserting while iterating" do + a = [1, 2] + iterated = [] + a.each { |x| iterated << x; a.unshift(0) if a.size == 2 } + iterated.should == [1, 1, 2] + end + + it "yields each element to a block that takes multiple arguments" do + a = [[1, 2], :a, [3, 4]] + b = [] + + a.each { |x, y| b << x } + b.should == [1, :a, 3] + + b = [] + a.each { |x, y| b << y } + b.should == [2, nil, 4] + end + + it "yields elements added to the end of the array by the block" do + a = [2] + iterated = [] + a.each { |x| iterated << x; x.times { a << 0 } } + + iterated.should == [2, 0, 0] + end + + it "does not yield elements deleted from the end of the array" do + a = [2, 3, 1] + iterated = [] + a.each { |x| iterated << x; a.delete_at(2) if x == 3 } + + iterated.should == [2, 3] + end + + it_behaves_like :enumeratorize, :each + it_behaves_like :enumeratorized_with_origin_size, :each, [1,2,3] +end + +describe "Array#each" do + it_behaves_like :array_iterable_and_tolerating_size_increasing, :each +end diff --git a/spec/ruby/core/array/element_reference_spec.rb b/spec/ruby/core/array/element_reference_spec.rb new file mode 100644 index 0000000000..31e5578a09 --- /dev/null +++ b/spec/ruby/core/array/element_reference_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/slice' + +describe "Array#[]" do + it_behaves_like :array_slice, :[] +end + +describe "Array.[]" do + it "[] should return a new array populated with the given elements" do + array = Array[1, 'a', nil] + array[0].should == 1 + array[1].should == 'a' + array[2].should == nil + end + + it "when applied to a literal nested array, unpacks its elements into the containing array" do + Array[1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5] + end + + it "when applied to a nested referenced array, unpacks its elements into the containing array" do + splatted_array = Array[3, 4, 5] + Array[1, 2, *splatted_array].should == [1, 2, 3, 4, 5] + end + + it "can unpack 2 or more nested referenced array" do + splatted_array = Array[3, 4, 5] + splatted_array2 = Array[6, 7, 8] + Array[1, 2, *splatted_array, *splatted_array2].should == [1, 2, 3, 4, 5, 6, 7, 8] + end + + it "constructs a nested Hash for tailing key-value pairs" do + Array[1, 2, 3 => 4, 5 => 6].should == [1, 2, { 3 => 4, 5 => 6 }] + end + + describe "with a subclass of Array" do + before :each do + ScratchPad.clear + end + + it "returns an instance of the subclass" do + ArraySpecs::MyArray[1, 2, 3].should be_an_instance_of(ArraySpecs::MyArray) + end + + it "does not call #initialize on the subclass instance" do + ArraySpecs::MyArray[1, 2, 3].should == [1, 2, 3] + ScratchPad.recorded.should be_nil + end + end +end diff --git a/spec/ruby/core/array/element_set_spec.rb b/spec/ruby/core/array/element_set_spec.rb new file mode 100644 index 0000000000..df5ca9582e --- /dev/null +++ b/spec/ruby/core/array/element_set_spec.rb @@ -0,0 +1,537 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#[]=" do + it "sets the value of the element at index" do + a = [1, 2, 3, 4] + a[2] = 5 + a[-1] = 6 + a[5] = 3 + a.should == [1, 2, 5, 6, nil, 3] + + a = [] + a[4] = "e" + a.should == [nil, nil, nil, nil, "e"] + a[3] = "d" + a.should == [nil, nil, nil, "d", "e"] + a[0] = "a" + a.should == ["a", nil, nil, "d", "e"] + a[-3] = "C" + a.should == ["a", nil, "C", "d", "e"] + a[-1] = "E" + a.should == ["a", nil, "C", "d", "E"] + a[-5] = "A" + a.should == ["A", nil, "C", "d", "E"] + a[5] = "f" + a.should == ["A", nil, "C", "d", "E", "f"] + a[1] = [] + a.should == ["A", [], "C", "d", "E", "f"] + a[-1] = nil + a.should == ["A", [], "C", "d", "E", nil] + end + + it "sets the section defined by [start,length] to other" do + a = [1, 2, 3, 4, 5, 6] + a[0, 1] = 2 + a[3, 2] = ['a', 'b', 'c', 'd'] + a.should == [2, 2, 3, "a", "b", "c", "d", 6] + end + + it "replaces the section defined by [start,length] with the given values" do + a = [1, 2, 3, 4, 5, 6] + a[3, 2] = 'a', 'b', 'c', 'd' + a.should == [1, 2, 3, "a", "b", "c", "d", 6] + end + + it "just sets the section defined by [start,length] to other even if other is nil" do + a = ['a', 'b', 'c', 'd', 'e'] + a[1, 3] = nil + a.should == ["a", nil, "e"] + end + + it "returns nil if the rhs is nil" do + a = [1, 2, 3] + (a[1, 3] = nil).should == nil + (a[1..3] = nil).should == nil + end + + it "sets the section defined by range to other" do + a = [6, 5, 4, 3, 2, 1] + a[1...2] = 9 + a[3..6] = [6, 6, 6] + a.should == [6, 9, 4, 6, 6, 6] + end + + it "replaces the section defined by range with the given values" do + a = [6, 5, 4, 3, 2, 1] + a[3..6] = :a, :b, :c + a.should == [6, 5, 4, :a, :b, :c] + end + + it "just sets the section defined by range to other even if other is nil" do + a = [1, 2, 3, 4, 5] + a[0..1] = nil + a.should == [nil, 3, 4, 5] + end + + it 'expands and nil-pads the array if section assigned by range is outside array boundaries' do + a = ['a'] + a[3..4] = ['b', 'c'] + a.should == ['a', nil, nil, 'b', 'c'] + end + + it "calls to_int on its start and length arguments" do + obj = mock('to_int') + obj.stub!(:to_int).and_return(2) + + a = [1, 2, 3, 4] + a[obj, 0] = [9] + a.should == [1, 2, 9, 3, 4] + a[obj, obj] = [] + a.should == [1, 2, 4] + a[obj] = -1 + a.should == [1, 2, -1] + end + + it "checks frozen before attempting to coerce arguments" do + a = [1,2,3,4].freeze + -> {a[:foo] = 1}.should raise_error(FrozenError) + -> {a[:foo, :bar] = 1}.should raise_error(FrozenError) + end + + it "sets elements in the range arguments when passed ranges" do + ary = [1, 2, 3] + rhs = [nil, [], ["x"], ["x", "y"]] + (0 .. ary.size + 2).each do |a| + (a .. ary.size + 3).each do |b| + rhs.each do |c| + ary1 = ary.dup + ary1[a .. b] = c + ary2 = ary.dup + ary2[a, 1 + b-a] = c + ary1.should == ary2 + + ary1 = ary.dup + ary1[a ... b] = c + ary2 = ary.dup + ary2[a, b-a] = c + ary1.should == ary2 + end + end + end + end + + it "inserts the given elements with [range] which the range is zero-width" do + ary = [1, 2, 3] + ary[1...1] = 0 + ary.should == [1, 0, 2, 3] + ary[1...1] = [5] + ary.should == [1, 5, 0, 2, 3] + ary[1...1] = :a, :b, :c + ary.should == [1, :a, :b, :c, 5, 0, 2, 3] + end + + it "inserts the given elements with [start, length] which length is zero" do + ary = [1, 2, 3] + ary[1, 0] = 0 + ary.should == [1, 0, 2, 3] + ary[1, 0] = [5] + ary.should == [1, 5, 0, 2, 3] + ary[1, 0] = :a, :b, :c + ary.should == [1, :a, :b, :c, 5, 0, 2, 3] + end + + # Now we only have to test cases where the start, length interface would + # have raise an exception because of negative size + it "inserts the given elements with [range] which the range has negative width" do + ary = [1, 2, 3] + ary[1..0] = 0 + ary.should == [1, 0, 2, 3] + ary[1..0] = [4, 3] + ary.should == [1, 4, 3, 0, 2, 3] + ary[1..0] = :a, :b, :c + ary.should == [1, :a, :b, :c, 4, 3, 0, 2, 3] + end + + it "just inserts nil if the section defined by range is zero-width and the rhs is nil" do + ary = [1, 2, 3] + ary[1...1] = nil + ary.should == [1, nil, 2, 3] + end + + it "just inserts nil if the section defined by range has negative width and the rhs is nil" do + ary = [1, 2, 3] + ary[1..0] = nil + ary.should == [1, nil, 2, 3] + end + + it "does nothing if the section defined by range is zero-width and the rhs is an empty array" do + ary = [1, 2, 3] + ary[1...1] = [] + ary.should == [1, 2, 3] + end + + it "does nothing if the section defined by range has negative width and the rhs is an empty array" do + ary = [1, 2, 3, 4, 5] + ary[1...0] = [] + ary.should == [1, 2, 3, 4, 5] + ary[-2..2] = [] + ary.should == [1, 2, 3, 4, 5] + end + + it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do + from = mock('from') + to = mock('to') + + # So we can construct a range out of them... + def from.<=>(o) 0 end + def to.<=>(o) 0 end + + def from.to_int() 1 end + def to.to_int() -2 end + + a = [1, 2, 3, 4] + + a[from .. to] = ["a", "b", "c"] + a.should == [1, "a", "b", "c", 4] + + a[to .. from] = ["x"] + a.should == [1, "a", "b", "x", "c", 4] + -> { a["a" .. "b"] = [] }.should raise_error(TypeError) + -> { a[from .. "b"] = [] }.should raise_error(TypeError) + end + + it "raises an IndexError when passed indexes out of bounds" do + a = [1, 2, 3, 4] + -> { a[-5] = "" }.should raise_error(IndexError) + -> { a[-5, -1] = "" }.should raise_error(IndexError) + -> { a[-5, 0] = "" }.should raise_error(IndexError) + -> { a[-5, 1] = "" }.should raise_error(IndexError) + -> { a[-5, 2] = "" }.should raise_error(IndexError) + -> { a[-5, 10] = "" }.should raise_error(IndexError) + + -> { a[-5..-5] = "" }.should raise_error(RangeError) + -> { a[-5...-5] = "" }.should raise_error(RangeError) + -> { a[-5..-4] = "" }.should raise_error(RangeError) + -> { a[-5...-4] = "" }.should raise_error(RangeError) + -> { a[-5..10] = "" }.should raise_error(RangeError) + -> { a[-5...10] = "" }.should raise_error(RangeError) + + # ok + a[0..-9] = [1] + a.should == [1, 1, 2, 3, 4] + end + + it "calls to_ary on its rhs argument for multi-element sets" do + obj = mock('to_ary') + def obj.to_ary() [1, 2, 3] end + ary = [1, 2] + ary[0, 0] = obj + ary.should == [1, 2, 3, 1, 2] + ary[1, 10] = obj + ary.should == [1, 1, 2, 3] + end + + it "does not call to_ary on rhs array subclasses for multi-element sets" do + ary = [] + ary[0, 0] = ArraySpecs::ToAryArray[5, 6, 7] + ary.should == [5, 6, 7] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array[0, 0] = [] }.should raise_error(FrozenError) + end +end + +describe "Array#[]= with [index]" do + it "returns value assigned if idx is inside array" do + a = [1, 2, 3, 4, 5] + (a[3] = 6).should == 6 + end + + it "returns value assigned if idx is right beyond right array boundary" do + a = [1, 2, 3, 4, 5] + (a[5] = 6).should == 6 + end + + it "returns value assigned if idx far beyond right array boundary" do + a = [1, 2, 3, 4, 5] + (a[10] = 6).should == 6 + end + + it "sets the value of the element at index" do + a = [1, 2, 3, 4] + a[2] = 5 + a[-1] = 6 + a[5] = 3 + a.should == [1, 2, 5, 6, nil, 3] + end + + it "sets the value of the element if it is right beyond the array boundary" do + a = [1, 2, 3, 4] + a[4] = 8 + a.should == [1, 2, 3, 4, 8] + end + +end + +describe "Array#[]= with [index, count]" do + it "returns non-array value if non-array value assigned" do + a = [1, 2, 3, 4, 5] + (a[2, 3] = 10).should == 10 + end + + it "returns array if array assigned" do + a = [1, 2, 3, 4, 5] + (a[2, 3] = [4, 5]).should == [4, 5] + end + + it "accepts a frozen String literal as RHS" do + a = ['a', 'b', 'c'] + a[0, 2] = 'd'.freeze + a.should == ['d', 'c'] + end + + it "just sets the section defined by [start,length] to nil even if the rhs is nil" do + a = ['a', 'b', 'c', 'd', 'e'] + a[1, 3] = nil + a.should == ["a", nil, "e"] + end + + it "just sets the section defined by [start,length] to nil if negative index within bounds, cnt > 0 and the rhs is nil" do + a = ['a', 'b', 'c', 'd', 'e'] + a[-3, 2] = nil + a.should == ["a", "b", nil, "e"] + end + + it "replaces the section defined by [start,length] to other" do + a = [1, 2, 3, 4, 5, 6] + a[0, 1] = 2 + a[3, 2] = ['a', 'b', 'c', 'd'] + a.should == [2, 2, 3, "a", "b", "c", "d", 6] + end + + it "replaces the section to other if idx < 0 and cnt > 0" do + a = [1, 2, 3, 4, 5, 6] + a[-3, 2] = ["x", "y", "z"] + a.should == [1, 2, 3, "x", "y", "z", 6] + end + + it "replaces the section to other even if cnt spanning beyond the array boundary" do + a = [1, 2, 3, 4, 5] + a[-1, 3] = [7, 8] + a.should == [1, 2, 3, 4, 7, 8] + end + + it "pads the Array with nils if the span is past the end" do + a = [1, 2, 3, 4, 5] + a[10, 1] = [1] + a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1] + + b = [1, 2, 3, 4, 5] + b[10, 0] = [1] + a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1] + + c = [1, 2, 3, 4, 5] + c[10, 0] = [] + c.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil] + end + + it "inserts other section in place defined by idx" do + a = [1, 2, 3, 4, 5] + a[3, 0] = [7, 8] + a.should == [1, 2, 3, 7, 8, 4, 5] + + b = [1, 2, 3, 4, 5] + b[1, 0] = b + b.should == [1, 1, 2, 3, 4, 5, 2, 3, 4, 5] + end + + it "raises an IndexError when passed start and negative length" do + a = [1, 2, 3, 4] + -> { a[-2, -1] = "" }.should raise_error(IndexError) + -> { a[0, -1] = "" }.should raise_error(IndexError) + -> { a[2, -1] = "" }.should raise_error(IndexError) + -> { a[4, -1] = "" }.should raise_error(IndexError) + -> { a[10, -1] = "" }.should raise_error(IndexError) + -> { [1, 2, 3, 4, 5][2, -1] = [7, 8] }.should raise_error(IndexError) + end +end + +describe "Array#[]= with [m..n]" do + it "returns non-array value if non-array value assigned" do + a = [1, 2, 3, 4, 5] + (a[2..4] = 10).should == 10 + (a.[]=(2..4, 10)).should == 10 + end + + it "returns array if array assigned" do + a = [1, 2, 3, 4, 5] + (a[2..4] = [7, 8]).should == [7, 8] + (a.[]=(2..4, [7, 8])).should == [7, 8] + end + + it "just sets the section defined by range to nil even if the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[0..1] = nil + a.should == [nil, 3, 4, 5] + end + + it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[-3..-2] = nil + a.should == [1, 2, nil, 5] + end + + it "replaces the section defined by range" do + a = [6, 5, 4, 3, 2, 1] + a[1...2] = 9 + a[3..6] = [6, 6, 6] + a.should == [6, 9, 4, 6, 6, 6] + end + + it "replaces the section if m and n < 0" do + a = [1, 2, 3, 4, 5] + a[-3..-2] = [7, 8, 9] + a.should == [1, 2, 7, 8, 9, 5] + end + + it "replaces the section if m < 0 and n > 0" do + a = [1, 2, 3, 4, 5] + a[-4..3] = [8] + a.should == [1, 8, 5] + end + + it "inserts the other section at m if m > n" do + a = [1, 2, 3, 4, 5] + a[3..1] = [8] + a.should == [1, 2, 3, 8, 4, 5] + end + + it "inserts at the end if m > the array size" do + a = [1, 2, 3] + a[3..3] = [4] + a.should == [1, 2, 3, 4] + a[5..7] = [6] + a.should == [1, 2, 3, 4, nil, 6] + end + + describe "Range subclasses" do + before :each do + @range_incl = ArraySpecs::MyRange.new(1, 2) + @range_excl = ArraySpecs::MyRange.new(-3, -1, true) + end + + it "accepts Range subclasses" do + a = [1, 2, 3, 4] + + a[@range_incl] = ["a", "b"] + a.should == [1, "a", "b", 4] + a[@range_excl] = ["A", "B"] + a.should == [1, "A", "B", 4] + end + + it "returns non-array value if non-array value assigned" do + a = [1, 2, 3, 4, 5] + (a[@range_incl] = 10).should == 10 + (a.[]=(@range_incl, 10)).should == 10 + end + + it "returns array if array assigned" do + a = [1, 2, 3, 4, 5] + (a[@range_incl] = [7, 8]).should == [7, 8] + a.[]=(@range_incl, [7, 8]).should == [7, 8] + end + end +end + +describe "Array#[]= with [m..]" do + it "just sets the section defined by range to nil even if the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[eval("(2..)")] = nil + a.should == [1, 2, nil] + end + + it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[eval("(-3..)")] = nil + a.should == [1, 2, nil] + end + + it "replaces the section defined by range" do + a = [6, 5, 4, 3, 2, 1] + a[eval("(3...)")] = 9 + a.should == [6, 5, 4, 9] + a[eval("(2..)")] = [7, 7, 7] + a.should == [6, 5, 7, 7, 7] + end + + it "replaces the section if m and n < 0" do + a = [1, 2, 3, 4, 5] + a[eval("(-3..)")] = [7, 8, 9] + a.should == [1, 2, 7, 8, 9] + end + + it "inserts at the end if m > the array size" do + a = [1, 2, 3] + a[eval("(3..)")] = [4] + a.should == [1, 2, 3, 4] + a[eval("(5..)")] = [6] + a.should == [1, 2, 3, 4, nil, 6] + end +end + +describe "Array#[]= with [..n] and [...n]" do + it "just sets the section defined by range to nil even if the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[(..2)] = nil + a.should == [nil, 4, 5] + a[(...2)] = nil + a.should == [nil, 5] + end + + it "just sets the section defined by range to nil if n < 0 and the rhs is nil" do + a = [1, 2, 3, 4, 5] + a[(..-3)] = nil + a.should == [nil, 4, 5] + a[(...-1)] = [nil, 5] + end + + it "replaces the section defined by range" do + a = [6, 5, 4, 3, 2, 1] + a[(...3)] = 9 + a.should == [9, 3, 2, 1] + a[(..2)] = [7, 7, 7, 7, 7] + a.should == [7, 7, 7, 7, 7, 1] + end + + it "replaces the section if n < 0" do + a = [1, 2, 3, 4, 5] + a[(..-2)] = [7, 8, 9] + a.should == [7, 8, 9, 5] + end + + it "replaces everything if n > the array size" do + a = [1, 2, 3] + a[(...7)] = [4] + a.should == [4] + end + + it "inserts at the beginning if n < negative the array size" do + a = [1, 2, 3] + a[(..-7)] = [4] + a.should == [4, 1, 2, 3] + a[(...-10)] = [6] + a.should == [6, 4, 1, 2, 3] + end +end + +describe "Array#[] after a shift" do + it "works for insertion" do + a = [1,2] + a.shift + a.shift + a[0,0] = [3,4] + a.should == [3,4] + end +end diff --git a/spec/ruby/core/array/empty_spec.rb b/spec/ruby/core/array/empty_spec.rb new file mode 100644 index 0000000000..f70b1b6ebe --- /dev/null +++ b/spec/ruby/core/array/empty_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#empty?" do + it "returns true if the array has no elements" do + [].should.empty? + [1].should_not.empty? + [1, 2].should_not.empty? + end +end diff --git a/spec/ruby/core/array/eql_spec.rb b/spec/ruby/core/array/eql_spec.rb new file mode 100644 index 0000000000..8565b94c60 --- /dev/null +++ b/spec/ruby/core/array/eql_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/eql' + +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].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(:eql?) + + [1, 2, 3].should_not eql(obj) + end +end diff --git a/spec/ruby/core/array/equal_value_spec.rb b/spec/ruby/core/array/equal_value_spec.rb new file mode 100644 index 0000000000..a82e07b218 --- /dev/null +++ b/spec/ruby/core/array/equal_value_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/eql' + +describe "Array#==" do + it_behaves_like :array_eql, :== + + it "compares with an equivalent Array-like object using #to_ary" do + obj = mock('array-like') + obj.should_receive(:respond_to?).at_least(1).with(:to_ary).and_return(true) + obj.should_receive(:==).with([1]).at_least(1).and_return(true) + + ([1] == obj).should be_true + ([[1]] == [obj]).should be_true + ([[[1], 3], 2] == [[obj, 3], 2]).should be_true + + # recursive arrays + arr1 = [[1]] + arr1 << arr1 + arr2 = [obj] + arr2 << arr2 + (arr1 == arr2).should be_true + (arr2 == arr1).should be_true + end + + it "returns false if any corresponding elements are not #==" do + a = ["a", "b", "c"] + b = ["a", "b", "not equal value"] + a.should_not == b + + c = mock("c") + c.should_receive(:==).and_return(false) + ["a", "b", c].should_not == a + end + + it "returns true if corresponding elements are #==" do + [].should == [] + ["a", "c", 7].should == ["a", "c", 7] + + [1, 2, 3].should == [1.0, 2.0, 3.0] + + obj = mock('5') + obj.should_receive(:==).and_return(true) + [obj].should == [5] + end + + # See https://bugs.ruby-lang.org/issues/1720 + it "returns true for [NaN] == [NaN] because Array#== first checks with #equal? and NaN.equal?(NaN) is true" do + [Float::NAN].should == [Float::NAN] + end +end diff --git a/spec/ruby/core/array/fetch_spec.rb b/spec/ruby/core/array/fetch_spec.rb new file mode 100644 index 0000000000..b81c0b48d7 --- /dev/null +++ b/spec/ruby/core/array/fetch_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#fetch" do + it "returns the element at the passed index" do + [1, 2, 3].fetch(1).should == 2 + [nil].fetch(0).should == nil + end + + it "counts negative indices backwards from end" do + [1, 2, 3, 4].fetch(-1).should == 4 + end + + it "raises an IndexError if there is no element at index" do + -> { [1, 2, 3].fetch(3) }.should raise_error(IndexError) + -> { [1, 2, 3].fetch(-4) }.should raise_error(IndexError) + -> { [].fetch(0) }.should raise_error(IndexError) + end + + it "returns default if there is no element at index if passed a default value" do + [1, 2, 3].fetch(5, :not_found).should == :not_found + [1, 2, 3].fetch(5, nil).should == nil + [1, 2, 3].fetch(-4, :not_found).should == :not_found + [nil].fetch(0, :not_found).should == nil + end + + it "returns the value of block if there is no element at index if passed a block" do + [1, 2, 3].fetch(9) { |i| i * i }.should == 81 + [1, 2, 3].fetch(-9) { |i| i * i }.should == 81 + end + + it "passes the original index argument object to the block, not the converted Integer" do + o = mock('5') + def o.to_int(); 5; end + + [1, 2, 3].fetch(o) { |i| i }.should equal(o) + end + + it "gives precedence to the default block over the default argument" do + -> { + @result = [1, 2, 3].fetch(9, :foo) { |i| i * i } + }.should complain(/block supersedes default value argument/) + @result.should == 81 + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + ["a", "b", "c"].fetch(obj).should == "c" + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [].fetch("cat") }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/array/fetch_values_spec.rb b/spec/ruby/core/array/fetch_values_spec.rb new file mode 100644 index 0000000000..cf377b3b71 --- /dev/null +++ b/spec/ruby/core/array/fetch_values_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#fetch_values" do + before :each do + @array = [:a, :b, :c] + end + + ruby_version_is "3.4" do + describe "with matched indexes" do + it "returns the values for indexes" do + @array.fetch_values(0).should == [:a] + @array.fetch_values(0, 2).should == [:a, :c] + @array.fetch_values(-1).should == [:c] + end + + it "returns the values for indexes ordered in the order of the requested indexes" do + @array.fetch_values(2, 0).should == [:c, :a] + end + end + + describe "with unmatched indexes" do + it "raises a index error if no block is provided" do + -> { @array.fetch_values(0, 1, 44) }.should raise_error(IndexError, "index 44 outside of array bounds: -3...3") + end + + it "returns the default value from block" do + @array.fetch_values(44) { |index| "`#{index}' is not found" }.should == ["`44' is not found"] + @array.fetch_values(0, 44) { |index| "`#{index}' is not found" }.should == [:a, "`44' is not found"] + end + end + + describe "without keys" do + it "returns an empty Array" do + @array.fetch_values.should == [] + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + @array.fetch_values(obj).should == [:c] + end + + it "does not support a Range object as argument" do + -> { + @array.fetch_values(1..2) + }.should raise_error(TypeError, "no implicit conversion of Range into Integer") + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [].fetch_values("cat") }.should raise_error(TypeError, "no implicit conversion of String into Integer") + end + end +end diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb new file mode 100644 index 0000000000..2c3b5d9e84 --- /dev/null +++ b/spec/ruby/core/array/fill_spec.rb @@ -0,0 +1,374 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#fill" do + before :all do + @never_passed = -> i do + raise ExpectationNotMetError, "the control path should not pass here" + end + end + + it "returns self" do + ary = [1, 2, 3] + ary.fill(:a).should equal(ary) + end + + it "is destructive" do + ary = [1, 2, 3] + ary.fill(:a) + ary.should == [:a, :a, :a] + end + + it "does not replicate the filler" do + ary = [1, 2, 3, 4] + str = +"x" + ary.fill(str).should == [str, str, str, str] + str << "y" + ary.should == [str, str, str, str] + ary[0].should equal(str) + ary[1].should equal(str) + ary[2].should equal(str) + ary[3].should equal(str) + end + + it "replaces all elements in the array with the filler if not given a index nor a length" do + ary = ['a', 'b', 'c', 'duh'] + ary.fill(8).should == [8, 8, 8, 8] + + str = "x" + ary.fill(str).should == [str, str, str, str] + end + + it "replaces all elements with the value of block (index given to block)" do + [nil, nil, nil, nil].fill { |i| i * 2 }.should == [0, 2, 4, 6] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.fill('x') }.should raise_error(FrozenError) + end + + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.fill('x') }.should raise_error(FrozenError) + end + + it "raises an ArgumentError if 4 or more arguments are passed when no block given" do + [].fill('a').should == [] + [].fill('a', 1).should == [] + [].fill('a', 1, 2).should == [nil, 'a', 'a'] + -> { [].fill('a', 1, 2, true) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if no argument passed and no block given" do + -> { [].fill }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if 3 or more arguments are passed when a block given" do + [].fill() {|i|}.should == [] + [].fill(1) {|i|}.should == [] + [].fill(1, 2) {|i|}.should == [nil, nil, nil] + -> { [].fill(1, 2, true) {|i|} }.should raise_error(ArgumentError) + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.fill { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "only changes elements before error is raised, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.fill do |i| + case i + when 0 then -1 + when 1 then -2 + when 2 then raise StandardError, 'Oops' + else 0 + end + end + rescue StandardError + end + + a.should == [-1, -2, 3, 4] + end + + it "tolerates increasing an array size during iteration" do + array = [:a, :b, :c] + ScratchPad.record [] + i = 0 + + array.fill do |index| + ScratchPad << index + array << i if i < 100 + i++ + index + end + + ScratchPad.recorded.should == [0, 1, 2] + end +end + +describe "Array#fill with (filler, index, length)" do + it "replaces length elements beginning with the index with the filler if given an index and a length" do + ary = [1, 2, 3, 4, 5, 6] + ary.fill('x', 2, 3).should == [1, 2, 'x', 'x', 'x', 6] + end + + it "replaces length elements beginning with the index with the value of block" do + [true, false, true, false, true, false, true].fill(1, 4) { |i| i + 3 }.should == [true, 4, 5, 6, 7, false, true] + end + + it "replaces all elements after the index if given an index and no length" do + ary = [1, 2, 3] + ary.fill('x', 1).should == [1, 'x', 'x'] + ary.fill(1){|i| i*2}.should == [1, 2, 4] + end + + it "replaces all elements after the index if given an index and nil as a length" do + a = [1, 2, 3] + a.fill('x', 1, nil).should == [1, 'x', 'x'] + a.fill(1, nil){|i| i*2}.should == [1, 2, 4] + a.fill('y', nil).should == ['y', 'y', 'y'] + end + + it "replaces the last (-n) elements if given an index n which is negative and no length" do + a = [1, 2, 3, 4, 5] + a.fill('x', -2).should == [1, 2, 3, 'x', 'x'] + a.fill(-2){|i| i.to_s}.should == [1, 2, 3, '3', '4'] + end + + it "replaces the last (-n) elements if given an index n which is negative and nil as a length" do + a = [1, 2, 3, 4, 5] + a.fill('x', -2, nil).should == [1, 2, 3, 'x', 'x'] + a.fill(-2, nil){|i| i.to_s}.should == [1, 2, 3, '3', '4'] + end + + it "makes no modifications if given an index greater than end and no length" do + [1, 2, 3, 4, 5].fill('a', 5).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill(5, &@never_passed).should == [1, 2, 3, 4, 5] + end + + it "makes no modifications if given an index greater than end and nil as a length" do + [1, 2, 3, 4, 5].fill('a', 5, nil).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill(5, nil, &@never_passed).should == [1, 2, 3, 4, 5] + end + + it "replaces length elements beginning with start index if given an index >= 0 and a length >= 0" do + [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill('a', 2, 2).should == [1, 2, "a", "a", 5] + + [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill(2, 2){|i| i*2}.should == [1, 2, 4, 6, 5] + end + + it "increases the Array size when necessary" do + a = [1, 2, 3] + a.size.should == 3 + a.fill 'a', 0, 10 + a.size.should == 10 + end + + it "pads between the last element and the index with nil if given an index which is greater than size of the array" do + [1, 2, 3, 4, 5].fill('a', 8, 5).should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a'] + [1, 2, 3, 4, 5].fill(8, 5){|i| 'a'}.should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a'] + end + + it "replaces length elements beginning with the (-n)th if given an index n < 0 and a length > 0" do + [1, 2, 3, 4, 5].fill('a', -2, 2).should == [1, 2, 3, "a", "a"] + [1, 2, 3, 4, 5].fill('a', -2, 4).should == [1, 2, 3, "a", "a", "a", "a"] + + [1, 2, 3, 4, 5].fill(-2, 2){|i| 'a'}.should == [1, 2, 3, "a", "a"] + [1, 2, 3, 4, 5].fill(-2, 4){|i| 'a'}.should == [1, 2, 3, "a", "a", "a", "a"] + end + + it "starts at 0 if the negative index is before the start of the array" do + [1, 2, 3, 4, 5].fill('a', -25, 3).should == ['a', 'a', 'a', 4, 5] + [1, 2, 3, 4, 5].fill('a', -10, 10).should == %w|a a a a a a a a a a| + + [1, 2, 3, 4, 5].fill(-25, 3){|i| 'a'}.should == ['a', 'a', 'a', 4, 5] + [1, 2, 3, 4, 5].fill(-10, 10){|i| 'a'}.should == %w|a a a a a a a a a a| + end + + it "makes no modifications if the given length <= 0" do + [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill('a', -2, 0).should == [1, 2, 3, 4, 5] + + [1, 2, 3, 4, 5].fill('a', 2, -2).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill('a', -2, -2).should == [1, 2, 3, 4, 5] + + [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill(-2, 0, &@never_passed).should == [1, 2, 3, 4, 5] + + [1, 2, 3, 4, 5].fill(2, -2, &@never_passed).should == [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5].fill(-2, -2, &@never_passed).should == [1, 2, 3, 4, 5] + end + + # See: https://blade.ruby-lang.org/ruby-core/17481 + it "does not raise an exception if the given length is negative and its absolute value does not exceed the index" do + [1, 2, 3, 4].fill('a', 3, -1).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill('a', 3, -2).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill('a', 3, -3).should == [1, 2, 3, 4] + + [1, 2, 3, 4].fill(3, -1, &@never_passed).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill(3, -2, &@never_passed).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill(3, -3, &@never_passed).should == [1, 2, 3, 4] + end + + it "does not raise an exception even if the given length is negative and its absolute value exceeds the index" do + [1, 2, 3, 4].fill('a', 3, -4).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill('a', 3, -5).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill('a', 3, -10000).should == [1, 2, 3, 4] + + [1, 2, 3, 4].fill(3, -4, &@never_passed).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill(3, -5, &@never_passed).should == [1, 2, 3, 4] + [1, 2, 3, 4].fill(3, -10000, &@never_passed).should == [1, 2, 3, 4] + end + + it "tries to convert the second and third arguments to Integers using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2, 2) + filler = mock('filler') + filler.should_not_receive(:to_int) + [1, 2, 3, 4, 5].fill(filler, obj, obj).should == [1, 2, filler, filler, 5] + end + + it "raises a TypeError if the index is not numeric" do + -> { [].fill 'a', true }.should raise_error(TypeError) + + obj = mock('nonnumeric') + -> { [].fill('a', obj) }.should raise_error(TypeError) + end + + it "raises a TypeError when the length is not numeric" do + -> { [1, 2, 3].fill("x", 1, "foo") }.should raise_error(TypeError, /no implicit conversion of String into Integer/) + -> { [1, 2, 3].fill("x", 1, :"foo") }.should raise_error(TypeError, /no implicit conversion of Symbol into Integer/) + -> { [1, 2, 3].fill("x", 1, Object.new) }.should raise_error(TypeError, /no implicit conversion of Object into Integer/) + end + + not_supported_on :opal do + it "raises an ArgumentError or RangeError for too-large sizes" do + error_types = [RangeError, ArgumentError] + arr = [1, 2, 3] + -> { arr.fill(10, 1, fixnum_max) }.should raise_error { |err| error_types.should include(err.class) } + -> { arr.fill(10, 1, bignum_value) }.should raise_error(RangeError) + end + end +end + +describe "Array#fill with (filler, range)" do + it "replaces elements in range with object" do + [1, 2, 3, 4, 5, 6].fill(8, 0..3).should == [8, 8, 8, 8, 5, 6] + [1, 2, 3, 4, 5, 6].fill(8, 0...3).should == [8, 8, 8, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', 4..6).should == [1, 2, 3, 4, 'x', 'x', 'x'] + [1, 2, 3, 4, 5, 6].fill('x', 4...6).should == [1, 2, 3, 4, 'x', 'x'] + [1, 2, 3, 4, 5, 6].fill('x', -2..-1).should == [1, 2, 3, 4, 'x', 'x'] + [1, 2, 3, 4, 5, 6].fill('x', -2...-1).should == [1, 2, 3, 4, 'x', 6] + [1, 2, 3, 4, 5, 6].fill('x', -2...-2).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', -2..-2).should == [1, 2, 3, 4, 'x', 6] + [1, 2, 3, 4, 5, 6].fill('x', -2..0).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', 0...0).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', 1..1).should == [1, 'x', 3, 4, 5, 6] + end + + it "replaces all elements in range with the value of block" do + [1, 1, 1, 1, 1, 1].fill(1..6) { |i| i + 1 }.should == [1, 2, 3, 4, 5, 6, 7] + end + + it "increases the Array size when necessary" do + [1, 2, 3].fill('x', 1..6).should == [1, 'x', 'x', 'x', 'x', 'x', 'x'] + [1, 2, 3].fill(1..6){|i| i+1}.should == [1, 2, 3, 4, 5, 6, 7] + end + + it "raises a TypeError with range and length argument" do + -> { [].fill('x', 0 .. 2, 5) }.should raise_error(TypeError) + end + + it "replaces elements between the (-m)th to the last and the (n+1)th from the first if given an range m..n where m < 0 and n >= 0" do + [1, 2, 3, 4, 5, 6].fill('x', -4..4).should == [1, 2, 'x', 'x', 'x', 6] + [1, 2, 3, 4, 5, 6].fill('x', -4...4).should == [1, 2, 'x', 'x', 5, 6] + + [1, 2, 3, 4, 5, 6].fill(-4..4){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6] + [1, 2, 3, 4, 5, 6].fill(-4...4){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6] + end + + it "replaces elements between the (-m)th and (-n)th to the last if given an range m..n where m < 0 and n < 0" do + [1, 2, 3, 4, 5, 6].fill('x', -4..-2).should == [1, 2, 'x', 'x', 'x', 6] + [1, 2, 3, 4, 5, 6].fill('x', -4...-2).should == [1, 2, 'x', 'x', 5, 6] + + [1, 2, 3, 4, 5, 6].fill(-4..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6] + [1, 2, 3, 4, 5, 6].fill(-4...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6] + end + + it "replaces elements between the (m+1)th from the first and (-n)th to the last if given an range m..n where m >= 0 and n < 0" do + [1, 2, 3, 4, 5, 6].fill('x', 2..-2).should == [1, 2, 'x', 'x', 'x', 6] + [1, 2, 3, 4, 5, 6].fill('x', 2...-2).should == [1, 2, 'x', 'x', 5, 6] + + [1, 2, 3, 4, 5, 6].fill(2..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6] + [1, 2, 3, 4, 5, 6].fill(2...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6] + end + + it "makes no modifications if given an range which implies a section of zero width" do + [1, 2, 3, 4, 5, 6].fill('x', 2...2).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', -4...2).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', -4...-4).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', 2...-4).should == [1, 2, 3, 4, 5, 6] + + [1, 2, 3, 4, 5, 6].fill(2...2, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(-4...2, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(-4...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(2...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6] + end + + it "makes no modifications if given an range which implies a section of negative width" do + [1, 2, 3, 4, 5, 6].fill('x', 2..1).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', -4..1).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', -2..-4).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill('x', 2..-5).should == [1, 2, 3, 4, 5, 6] + + [1, 2, 3, 4, 5, 6].fill(2..1, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(-4..1, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(-2..-4, &@never_passed).should == [1, 2, 3, 4, 5, 6] + [1, 2, 3, 4, 5, 6].fill(2..-5, &@never_passed).should == [1, 2, 3, 4, 5, 6] + end + + it "raises an exception if some of the given range lies before the first of the array" do + -> { [1, 2, 3].fill('x', -5..-3) }.should raise_error(RangeError) + -> { [1, 2, 3].fill('x', -5...-3) }.should raise_error(RangeError) + -> { [1, 2, 3].fill('x', -5..-4) }.should raise_error(RangeError) + + -> { [1, 2, 3].fill(-5..-3, &@never_passed) }.should raise_error(RangeError) + -> { [1, 2, 3].fill(-5...-3, &@never_passed) }.should raise_error(RangeError) + -> { [1, 2, 3].fill(-5..-4, &@never_passed) }.should raise_error(RangeError) + end + + it "tries to convert the start and end of the passed range to Integers using #to_int" do + obj = mock('to_int') + def obj.<=>(rhs); rhs == self ? 0 : nil end + obj.should_receive(:to_int).twice.and_return(2) + filler = mock('filler') + filler.should_not_receive(:to_int) + [1, 2, 3, 4, 5].fill(filler, obj..obj).should == [1, 2, filler, 4, 5] + end + + it "raises a TypeError if the start or end of the passed range is not numeric" do + obj = mock('nonnumeric') + def obj.<=>(rhs); rhs == self ? 0 : nil end + -> { [].fill('a', obj..obj) }.should raise_error(TypeError) + end + + it "works with endless ranges" do + [1, 2, 3, 4].fill('x', eval("(1..)")).should == [1, 'x', 'x', 'x'] + [1, 2, 3, 4].fill('x', eval("(3...)")).should == [1, 2, 3, 'x'] + [1, 2, 3, 4].fill(eval("(1..)")) { |x| x + 2 }.should == [1, 3, 4, 5] + [1, 2, 3, 4].fill(eval("(3...)")) { |x| x + 2 }.should == [1, 2, 3, 5] + end + + it "works with beginless ranges" do + [1, 2, 3, 4].fill('x', (..2)).should == ["x", "x", "x", 4] + [1, 2, 3, 4].fill((...2)) { |x| x + 2 }.should == [2, 3, 3, 4] + end +end diff --git a/spec/ruby/core/array/filter_spec.rb b/spec/ruby/core/array/filter_spec.rb new file mode 100644 index 0000000000..156ad14f9c --- /dev/null +++ b/spec/ruby/core/array/filter_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require_relative 'shared/select' + +describe "Array#filter" do + it_behaves_like :array_select, :filter +end + +describe "Array#filter!" do + it "returns nil if no changes were made in the array" do + [1, 2, 3].filter! { true }.should be_nil + end + + it_behaves_like :keep_if, :filter! +end diff --git a/spec/ruby/core/array/find_index_spec.rb b/spec/ruby/core/array/find_index_spec.rb new file mode 100644 index 0000000000..759472024a --- /dev/null +++ b/spec/ruby/core/array/find_index_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/index' + +describe "Array#find_index" do + it_behaves_like :array_index, :find_index +end diff --git a/spec/ruby/core/array/first_spec.rb b/spec/ruby/core/array/first_spec.rb new file mode 100644 index 0000000000..66eeba6565 --- /dev/null +++ b/spec/ruby/core/array/first_spec.rb @@ -0,0 +1,93 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#first" do + it "returns the first element" do + %w{a b c}.first.should == 'a' + [nil].first.should == nil + end + + it "returns nil if self is empty" do + [].first.should == nil + end + + it "returns the first count elements if given a count" do + [true, false, true, nil, false].first(2).should == [true, false] + end + + it "returns an empty array when passed count on an empty array" do + [].first(0).should == [] + [].first(1).should == [] + [].first(2).should == [] + end + + it "returns an empty array when passed count == 0" do + [1, 2, 3, 4, 5].first(0).should == [] + end + + it "returns an array containing the first element when passed count == 1" do + [1, 2, 3, 4, 5].first(1).should == [1] + end + + it "raises an ArgumentError when count is negative" do + -> { [1, 2].first(-1) }.should raise_error(ArgumentError) + end + + it "raises a RangeError when count is a Bignum" do + -> { [].first(bignum_value) }.should raise_error(RangeError) + end + + it "returns the entire array when count > length" do + [1, 2, 3, 4, 5, 9].first(10).should == [1, 2, 3, 4, 5, 9] + end + + it "returns an array which is independent to the original when passed count" do + ary = [1, 2, 3, 4, 5] + ary.first(0).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + ary.first(1).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + ary.first(6).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.first.should equal(empty) + + ary = ArraySpecs.head_recursive_array + ary.first.should equal(ary) + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + [1, 2, 3, 4, 5].first(obj).should == [1, 2] + end + + it "raises a TypeError if the passed argument is not numeric" do + -> { [1,2].first(nil) }.should raise_error(TypeError) + -> { [1,2].first("a") }.should raise_error(TypeError) + + obj = mock("nonnumeric") + -> { [1,2].first(obj) }.should raise_error(TypeError) + end + + it "does not return subclass instance when passed count on Array subclasses" do + ArraySpecs::MyArray[].first(0).should be_an_instance_of(Array) + ArraySpecs::MyArray[].first(2).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].first(0).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].first(1).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].first(2).should be_an_instance_of(Array) + end + + it "is not destructive" do + a = [1, 2, 3] + a.first + a.should == [1, 2, 3] + a.first(2) + a.should == [1, 2, 3] + a.first(3) + a.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb new file mode 100644 index 0000000000..05283c0f74 --- /dev/null +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -0,0 +1,592 @@ +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) + format += count.to_s unless format == 'P' || format == 'p' + format *= repeat if repeat + format.dup # because it may then become tainted + end +end + +module ArraySpecs + SampleRange = 0..1000 + SampleCount = 1000 + + def self.frozen_array + [1,2,3].freeze + end + + def self.empty_frozen_array + [].freeze + end + + def self.recursive_array + a = [1, 'two', 3.0] + 5.times { a << a } + a + end + + def self.head_recursive_array + a = [] + 5.times { a << a } + a << 1 << 'two' << 3.0 + a + end + + def self.empty_recursive_array + a = [] + a << a + a + end + + # Chi squared critical values for tests with n degrees of freedom at 99% confidence. + # Values obtained from NIST Engineering Statistic Handbook at + # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm + + CHI_SQUARED_CRITICAL_VALUES = [ + 0, + 6.635, 9.210, 11.345, 13.277, 15.086, 16.812, 18.475, 20.090, 21.666, 23.209, + 24.725, 26.217, 27.688, 29.141, 30.578, 32.000, 33.409, 34.805, 36.191, 37.566, + 38.932, 40.289, 41.638, 42.980, 44.314, 45.642, 46.963, 48.278, 49.588, 50.892, + 52.191, 53.486, 54.776, 56.061, 57.342, 58.619, 59.893, 61.162, 62.428, 63.691, + 64.950, 66.206, 67.459, 68.710, 69.957, 71.201, 72.443, 73.683, 74.919, 76.154, + 77.386, 78.616, 79.843, 81.069, 82.292, 83.513, 84.733, 85.950, 87.166, 88.379, + 89.591, 90.802, 92.010, 93.217, 94.422, 95.626, 96.828, 98.028, 99.228, 100.425, + 101.621, 102.816, 104.010, 105.202, 106.393, 107.583, 108.771, 109.958, 111.144, 112.329, + 113.512, 114.695, 115.876, 117.057, 118.236, 119.414, 120.591, 121.767, 122.942, 124.116, + 125.289, 126.462, 127.633, 128.803, 129.973, 131.141, 132.309, 133.476, 134.642, 135.807, + ] + + def self.measure_sample_fairness(size, samples, iters) + ary = Array.new(size) { |x| x } + expected = iters.fdiv size + (samples).times do |i| + chi_results = [] + 3.times do + counts = Array.new(size, 0) + iters.times do + x = ary.sample(samples)[i] + counts[x] += 1 + end + chi_squared = counts.sum {|count| (count - expected) ** 2} / expected + chi_results << chi_squared + break if chi_squared <= CHI_SQUARED_CRITICAL_VALUES[size] + end + + chi_results.min.should <= CHI_SQUARED_CRITICAL_VALUES[size] + end + end + + def self.measure_sample_fairness_large_sample_size(size, samples, iters) + ary = Array.new(size) { |x| x } + counts = Array.new(size, 0) + expected = (iters * samples).fdiv size + iters.times do + ary.sample(samples).each do |sample| + counts[sample] += 1 + end + end + chi_squared = counts.sum {|count| (count - expected) ** 2} / expected + + # Chi squared critical values for tests with 4 degrees of freedom + # Values obtained from NIST Engineering Statistic Handbook at + # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm + + chi_squared.should <= CHI_SQUARED_CRITICAL_VALUES[size] + end + + class MyArray < Array + # The #initialize method has a different signature than Array to help + # catch places in the specs that do not assert the #initialize is not + # called when Array methods make new instances. + def initialize(a, b) + self << a << b + ScratchPad.record :my_array_initialize + end + end + + class Sexp < Array + def initialize(*args) + super(args) + end + end + + # TODO: replace specs that use this with #should_not_receive(:to_ary) + # expectations on regular objects (e.g. Array instances). + class ToAryArray < Array + def to_ary() ["to_ary", "was", "called!"] end + end + + class MyRange < Range; end + + class AssocKey + def ==(other); other == 'it'; end + end + + class D + def <=>(obj) + return 4 <=> obj unless obj.class == D + 0 + end + end + + class SubArray < Array + def initialize(*args) + ScratchPad.record args + end + end + + class ArrayConvertible + attr_accessor :called + def initialize(*values, &block) + @values = values; + end + + def to_a + self.called = :to_a + @values + end + + def to_ary + self.called = :to_ary + @values + end + end + + class ArrayMethodMissing + def initialize(*values, &block) + @values = values; + end + + def method_missing(name, *args) + @values + end + end + + class SortSame + def <=>(other); 0; end + def ==(other); true; end + end + + class UFOSceptic + def <=>(other); raise "N-uh, UFO:s do not exist!"; end + end + + class MockForCompared + @@count = 0 + @@compared = false + def initialize + @@compared = false + @order = (@@count += 1) + end + def <=>(rhs) + @@compared = true + return rhs.order <=> self.order + end + def self.compared? + @@compared + end + + protected + attr_accessor :order + end + + class ComparableWithInteger + include Comparable + def initialize(num) + @num = num + end + + def <=>(fixnum) + @num <=> fixnum + end + end + + class Uncomparable + def <=>(obj) + nil + end + end + + def self.universal_pack_object + obj = mock("string float int".freeze) + obj.stub!(:to_int).and_return(1) + obj.stub!(:to_str).and_return("1") + obj.stub!(:to_f).and_return(1.0) + obj + end + + LargeArray = [ + "test_create_table_with_force_true_does_not_drop_nonexisting_table", + "test_add_table", + "assert_difference", + "assert_operator", + "instance_variables", + "class", + "instance_variable_get", + "__class__", + "expects", + "assert_no_difference", + "name", + "assert_blank", + "assert_not_same", + "is_a?", + "test_add_table_with_decimals", + "test_create_table_with_timestamps_should_create_datetime_columns", + "assert_present", + "assert_no_match", + "__instance_of__", + "assert_deprecated", + "assert", + "assert_throws", + "kind_of?", + "try", + "__instance_variable_get__", + "object_id", + "timeout", + "instance_variable_set", + "assert_nothing_thrown", + "__instance_variable_set__", + "copy_object", + "test_create_table_with_timestamps_should_create_datetime_columns_with_options", + "assert_not_deprecated", + "assert_in_delta", + "id", + "copy_metaclass", + "test_create_table_without_a_block", + "dup", + "assert_not_nil", + "send", + "__instance_variables__", + "to_sql", + "mock", + "assert_send", + "instance_variable_defined?", + "clone", + "require", + "test_migrator", + "__instance_variable_defined_eh__", + "frozen?", + "test_add_column_not_null_with_default", + "freeze", + "test_migrator_one_up", + "test_migrator_one_down", + "singleton_methods", + "method_exists?", + "create_fixtures", + "test_migrator_one_up_one_down", + "test_native_decimal_insert_manual_vs_automatic", + "instance_exec", + "__is_a__", + "test_migrator_double_up", + "stub", + "private_methods", + "stubs", + "test_migrator_double_down", + "fixture_path", + "private_singleton_methods", + "stub_everything", + "test_migrator_one_up_with_exception_and_rollback", + "sequence", + "protected_methods", + "enum_for", + "test_finds_migrations", + "run_before_mocha", + "states", + "protected_singleton_methods", + "to_json", + "instance_values", + "==", + "mocha_setup", + "public_methods", + "test_finds_pending_migrations", + "mocha_verify", + "assert_kind_of", + "===", + "=~", + "test_relative_migrations", + "mocha_teardown", + "gem", + "mocha", + "test_only_loads_pending_migrations", + "test_add_column_with_precision_and_scale", + "require_or_load", + "eql?", + "require_dependency", + "test_native_types", + "test_target_version_zero_should_run_only_once", + "extend", + "to_matcher", + "unloadable", + "require_association", + "hash", + "__id__", + "load_dependency", + "equals", + "test_migrator_db_has_no_schema_migrations_table", + "test_migrator_verbosity", + "kind_of", + "to_yaml", + "to_bool", + "test_migrator_verbosity_off", + "taint", + "test_migrator_going_down_due_to_version_target", + "tainted?", + "mocha_inspect", + "test_migrator_rollback", + "vim", + "untaint", + "taguri=", + "test_migrator_forward", + "test_schema_migrations_table_name", + "test_proper_table_name", + "all_of", + "test_add_drop_table_with_prefix_and_suffix", + "_setup_callbacks", + "setup", + "Not", + "test_create_table_with_binary_column", + "assert_not_equal", + "enable_warnings", + "acts_like?", + "Rational", + "_removed_setup_callbacks", + "Table", + "bind", + "any_of", + "__method__", + "test_migrator_with_duplicates", + "_teardown_callbacks", + "method", + "test_migrator_with_duplicate_names", + "_removed_teardown_callbacks", + "any_parameters", + "test_migrator_with_missing_version_numbers", + "test_add_remove_single_field_using_string_arguments", + "test_create_table_with_custom_sequence_name", + "test_add_remove_single_field_using_symbol_arguments", + "_one_time_conditions_valid_14?", + "_one_time_conditions_valid_16?", + "run_callbacks", + "anything", + "silence_warnings", + "instance_variable_names", + "_fixture_path", + "copy_instance_variables_from", + "fixture_path?", + "has_entry", + "__marshal__", + "_fixture_table_names", + "__kind_of__", + "fixture_table_names?", + "test_add_rename", + "assert_equal", + "_fixture_class_names", + "fixture_class_names?", + "has_entries", + "_use_transactional_fixtures", + "people", + "test_rename_column_using_symbol_arguments", + "use_transactional_fixtures?", + "instance_eval", + "blank?", + "with_warnings", + "__nil__", + "load", + "metaclass", + "_use_instantiated_fixtures", + "has_key", + "class_eval", + "present?", + "test_rename_column", + "teardown", + "use_instantiated_fixtures?", + "method_name", + "silence_stderr", + "presence", + "test_rename_column_preserves_default_value_not_null", + "silence_stream", + "_pre_loaded_fixtures", + "__metaclass__", + "__fixnum__", + "pre_loaded_fixtures?", + "has_value", + "suppress", + "to_yaml_properties", + "test_rename_nonexistent_column", + "test_add_index", + "includes", + "find_correlate_in", + "equality_predicate_sql", + "assert_nothing_raised", + "let", + "not_predicate_sql", + "test_rename_column_with_sql_reserved_word", + "singleton_class", + "test_rename_column_with_an_index", + "display", + "taguri", + "to_yaml_style", + "test_remove_column_with_index", + "size", + "current_adapter?", + "test_remove_column_with_multi_column_index", + "respond_to?", + "test_change_type_of_not_null_column", + "is_a", + "to_a", + "test_rename_table_for_sqlite_should_work_with_reserved_words", + "require_library_or_gem", + "setup_fixtures", + "equal?", + "teardown_fixtures", + "nil?", + "fixture_table_names", + "fixture_class_names", + "test_create_table_without_id", + "use_transactional_fixtures", + "test_add_column_with_primary_key_attribute", + "repair_validations", + "use_instantiated_fixtures", + "instance_of?", + "test_create_table_adds_id", + "test_rename_table", + "pre_loaded_fixtures", + "to_enum", + "test_create_table_with_not_null_column", + "instance_of", + "test_change_column_nullability", + "optionally", + "test_rename_table_with_an_index", + "run", + "test_change_column", + "default_test", + "assert_raise", + "test_create_table_with_defaults", + "assert_nil", + "flunk", + "regexp_matches", + "duplicable?", + "reset_mocha", + "stubba_method", + "filter_backtrace", + "test_create_table_with_limits", + "responds_with", + "stubba_object", + "test_change_column_with_nil_default", + "assert_block", + "__show__", + "assert_date_from_db", + "__respond_to_eh__", + "run_in_transaction?", + "inspect", + "assert_sql", + "test_change_column_with_new_default", + "yaml_equivalent", + "build_message", + "to_s", + "test_change_column_default", + "assert_queries", + "pending", + "as_json", + "assert_no_queries", + "test_change_column_quotes_column_names", + "assert_match", + "test_keeping_default_and_notnull_constraint_on_change", + "methods", + "connection_allow_concurrency_setup", + "connection_allow_concurrency_teardown", + "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", + "__send__", + "make_connection", + "assert_raises", + "tap", + "with_kcode", + "assert_instance_of", + "test_create_table_with_primary_key_prefix_as_table_name", + "assert_respond_to", + "test_change_column_default_to_null", + "assert_same", + "__extend__", + ] + + LargeTestArraySorted = [ + "test_add_column_not_null_with_default", + "test_add_column_with_precision_and_scale", + "test_add_column_with_primary_key_attribute", + "test_add_drop_table_with_prefix_and_suffix", + "test_add_index", + "test_add_remove_single_field_using_string_arguments", + "test_add_remove_single_field_using_symbol_arguments", + "test_add_rename", + "test_add_table", + "test_add_table_with_decimals", + "test_change_column", + "test_change_column_default", + "test_change_column_default_to_null", + "test_change_column_nullability", + "test_change_column_quotes_column_names", + "test_change_column_with_new_default", + "test_change_column_with_nil_default", + "test_change_type_of_not_null_column", + "test_create_table_adds_id", + "test_create_table_with_binary_column", + "test_create_table_with_custom_sequence_name", + "test_create_table_with_defaults", + "test_create_table_with_force_true_does_not_drop_nonexisting_table", + "test_create_table_with_limits", + "test_create_table_with_not_null_column", + "test_create_table_with_primary_key_prefix_as_table_name", + "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", + "test_create_table_with_timestamps_should_create_datetime_columns", + "test_create_table_with_timestamps_should_create_datetime_columns_with_options", + "test_create_table_without_a_block", + "test_create_table_without_id", + "test_finds_migrations", + "test_finds_pending_migrations", + "test_keeping_default_and_notnull_constraint_on_change", + "test_migrator", + "test_migrator_db_has_no_schema_migrations_table", + "test_migrator_double_down", + "test_migrator_double_up", + "test_migrator_forward", + "test_migrator_going_down_due_to_version_target", + "test_migrator_one_down", + "test_migrator_one_up", + "test_migrator_one_up_one_down", + "test_migrator_one_up_with_exception_and_rollback", + "test_migrator_rollback", + "test_migrator_verbosity", + "test_migrator_verbosity_off", + "test_migrator_with_duplicate_names", + "test_migrator_with_duplicates", + "test_migrator_with_missing_version_numbers", + "test_native_decimal_insert_manual_vs_automatic", + "test_native_types", + "test_only_loads_pending_migrations", + "test_proper_table_name", + "test_relative_migrations", + "test_remove_column_with_index", + "test_remove_column_with_multi_column_index", + "test_rename_column", + "test_rename_column_preserves_default_value_not_null", + "test_rename_column_using_symbol_arguments", + "test_rename_column_with_an_index", + "test_rename_column_with_sql_reserved_word", + "test_rename_nonexistent_column", + "test_rename_table", + "test_rename_table_for_sqlite_should_work_with_reserved_words", + "test_rename_table_with_an_index", + "test_schema_migrations_table_name", + "test_target_version_zero_should_run_only_once", + ] + + class PrivateToAry + private + + def to_ary + [1, 2, 3] + end + end +end diff --git a/spec/ruby/core/array/fixtures/encoded_strings.rb b/spec/ruby/core/array/fixtures/encoded_strings.rb new file mode 100644 index 0000000000..b5888d86ae --- /dev/null +++ b/spec/ruby/core/array/fixtures/encoded_strings.rb @@ -0,0 +1,69 @@ +# encoding: utf-8 +module ArraySpecs + def self.array_with_usascii_and_7bit_utf8_strings + [ + 'foo'.dup.force_encoding('US-ASCII'), + 'bar' + ] + end + + def self.array_with_usascii_and_utf8_strings + [ + 'foo'.dup.force_encoding('US-ASCII'), + 'báz' + ] + end + + def self.array_with_7bit_utf8_and_usascii_strings + [ + 'bar', + 'foo'.dup.force_encoding('US-ASCII') + ] + end + + def self.array_with_utf8_and_usascii_strings + [ + 'báz', + 'bar', + 'foo'.dup.force_encoding('US-ASCII') + ] + end + + def self.array_with_usascii_and_utf8_strings + [ + 'foo'.dup.force_encoding('US-ASCII'), + 'bar', + 'báz' + ] + end + + def self.array_with_utf8_and_7bit_binary_strings + [ + 'bar', + 'báz', + 'foo'.dup.force_encoding('BINARY') + ] + end + + def self.array_with_utf8_and_binary_strings + [ + 'bar', + 'báz', + [255].pack('C').force_encoding('BINARY') + ] + end + + def self.array_with_usascii_and_7bit_binary_strings + [ + 'bar'.dup.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('BINARY') + ] + end + + def self.array_with_usascii_and_binary_strings + [ + 'bar'.dup.force_encoding('US-ASCII'), + [255].pack('C').force_encoding('BINARY') + ] + end +end diff --git a/spec/ruby/core/array/flatten_spec.rb b/spec/ruby/core/array/flatten_spec.rb new file mode 100644 index 0000000000..8c97000c79 --- /dev/null +++ b/spec/ruby/core/array/flatten_spec.rb @@ -0,0 +1,266 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#flatten" do + it "returns a one-dimensional flattening recursively" do + [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []].flatten.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4] + end + + it "takes an optional argument that determines the level of recursion" do + [ 1, 2, [3, [4, 5] ] ].flatten(1).should == [1, 2, 3, [4, 5]] + end + + it "returns dup when the level of recursion is 0" do + a = [ 1, 2, [3, [4, 5] ] ] + a.flatten(0).should == a + a.flatten(0).should_not equal(a) + end + + it "ignores negative levels" do + [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-1).should == [1, 2, 3, 4, 5, 6] + [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-10).should == [1, 2, 3, 4, 5, 6] + end + + it "tries to convert passed Objects to Integers using #to_int" do + obj = mock("Converted to Integer") + obj.should_receive(:to_int).and_return(1) + + [ 1, 2, [3, [4, 5] ] ].flatten(obj).should == [1, 2, 3, [4, 5]] + end + + it "raises a TypeError when the passed Object can't be converted to an Integer" do + obj = mock("Not converted") + -> { [ 1, 2, [3, [4, 5] ] ].flatten(obj) }.should raise_error(TypeError) + end + + it "does not call flatten on elements" do + obj = mock('[1,2]') + obj.should_not_receive(:flatten) + [obj, obj].flatten.should == [obj, obj] + + obj = [5, 4] + obj.should_not_receive(:flatten) + [obj, obj].flatten.should == [5, 4, 5, 4] + end + + it "raises an ArgumentError on recursive arrays" do + x = [] + x << x + -> { x.flatten }.should raise_error(ArgumentError) + + x = [] + y = [] + x << y + y << x + -> { x.flatten }.should raise_error(ArgumentError) + end + + it "flattens any element which responds to #to_ary, using the return value of said method" do + x = mock("[3,4]") + x.should_receive(:to_ary).at_least(:once).and_return([3, 4]) + [1, 2, x, 5].flatten.should == [1, 2, 3, 4, 5] + + y = mock("MyArray[]") + y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[]) + [y].flatten.should == [] + + z = mock("[2,x,y,5]") + z.should_receive(:to_ary).and_return([2, x, y, 5]) + [1, z, 6].flatten.should == [1, 2, 3, 4, 5, 6] + end + + it "does not call #to_ary on elements beyond the given level" do + obj = mock("1") + obj.should_not_receive(:to_ary) + [[obj]].flatten(1) + end + + it "returns Array instance for Array subclasses" do + ArraySpecs::MyArray[].flatten.should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].flatten.should be_an_instance_of(Array) + ArraySpecs::MyArray[1, [2], 3].flatten.should be_an_instance_of(Array) + ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == [1, 2, 3, 4] + [ArraySpecs::MyArray[1, 2, 3]].flatten.should be_an_instance_of(Array) + end + + it "is not destructive" do + ary = [1, [2, 3]] + ary.flatten + ary.should == [1, [2, 3]] + end + + describe "with a non-Array object in the Array" do + before :each do + @obj = mock("Array#flatten") + ScratchPad.record [] + end + + it "does not call #to_ary if the method is not defined" do + [@obj].flatten.should == [@obj] + end + + it "does not raise an exception if #to_ary returns nil" do + @obj.should_receive(:to_ary).and_return(nil) + [@obj].flatten.should == [@obj] + end + + it "raises a TypeError if #to_ary does not return an Array" do + @obj.should_receive(:to_ary).and_return(1) + -> { [@obj].flatten }.should raise_error(TypeError) + end + + it "calls respond_to_missing?(:to_ary, true) to try coercing" do + def @obj.respond_to_missing?(*args) ScratchPad << args; false end + [@obj].flatten.should == [@obj] + ScratchPad.recorded.should == [[:to_ary, true]] + end + + it "does not call #to_ary if not defined when #respond_to_missing? returns false" do + def @obj.respond_to_missing?(name, priv) ScratchPad << name; false end + + [@obj].flatten.should == [@obj] + ScratchPad.recorded.should == [:to_ary] + end + + it "calls #to_ary if not defined when #respond_to_missing? returns true" do + def @obj.respond_to_missing?(name, priv) ScratchPad << name; true end + + -> { [@obj].flatten }.should raise_error(NoMethodError) + ScratchPad.recorded.should == [:to_ary] + end + + it "calls #method_missing if defined" do + @obj.should_receive(:method_missing).with(:to_ary).and_return([1, 2, 3]) + [@obj].flatten.should == [1, 2, 3] + end + end + + it "performs respond_to? and method_missing-aware checks when coercing elements to array" do + bo = BasicObject.new + [bo].flatten.should == [bo] + + def bo.method_missing(name, *) + [1,2] + end + + [bo].flatten.should == [1,2] + + def bo.respond_to?(name, *) + false + end + + [bo].flatten.should == [bo] + + def bo.respond_to?(name, *) + true + end + + [bo].flatten.should == [1,2] + end +end + +describe "Array#flatten!" do + it "modifies array to produce a one-dimensional flattening recursively" do + a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []] + a.flatten! + a.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4] + end + + it "returns self if made some modifications" do + a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []] + a.flatten!.should equal(a) + end + + it "returns nil if no modifications took place" do + a = [1, 2, 3] + a.flatten!.should == nil + a = [1, [2, 3]] + a.flatten!.should_not == nil + end + + it "should not check modification by size" do + a = [1, 2, [3]] + a.flatten!.should_not == nil + a.should == [1, 2, 3] + end + + it "takes an optional argument that determines the level of recursion" do + [ 1, 2, [3, [4, 5] ] ].flatten!(1).should == [1, 2, 3, [4, 5]] + end + + # redmine #1440 + it "returns nil when the level of recursion is 0" do + a = [ 1, 2, [3, [4, 5] ] ] + a.flatten!(0).should == nil + end + + it "treats negative levels as no arguments" do + [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-1).should == [1, 2, 3, 4, 5, 6] + [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-10).should == [1, 2, 3, 4, 5, 6] + end + + it "tries to convert passed Objects to Integers using #to_int" do + obj = mock("Converted to Integer") + obj.should_receive(:to_int).and_return(1) + + [ 1, 2, [3, [4, 5] ] ].flatten!(obj).should == [1, 2, 3, [4, 5]] + end + + it "raises a TypeError when the passed Object can't be converted to an Integer" do + obj = mock("Not converted") + -> { [ 1, 2, [3, [4, 5] ] ].flatten!(obj) }.should raise_error(TypeError) + end + + it "does not call flatten! on elements" do + obj = mock('[1,2]') + obj.should_not_receive(:flatten!) + [obj, obj].flatten!.should == nil + + obj = [5, 4] + obj.should_not_receive(:flatten!) + [obj, obj].flatten!.should == [5, 4, 5, 4] + end + + it "raises an ArgumentError on recursive arrays" do + x = [] + x << x + -> { x.flatten! }.should raise_error(ArgumentError) + + x = [] + y = [] + x << y + y << x + -> { x.flatten! }.should raise_error(ArgumentError) + end + + it "flattens any elements which responds to #to_ary, using the return value of said method" do + x = mock("[3,4]") + x.should_receive(:to_ary).at_least(:once).and_return([3, 4]) + [1, 2, x, 5].flatten!.should == [1, 2, 3, 4, 5] + + y = mock("MyArray[]") + y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[]) + [y].flatten!.should == [] + + z = mock("[2,x,y,5]") + z.should_receive(:to_ary).and_return([2, x, y, 5]) + [1, z, 6].flatten!.should == [1, 2, 3, 4, 5, 6] + + ary = [ArraySpecs::MyArray[1, 2, 3]] + ary.flatten! + ary.should be_an_instance_of(Array) + ary.should == [1, 2, 3] + end + + it "raises a FrozenError on frozen arrays when the array is modified" do + nested_ary = [1, 2, []] + nested_ary.freeze + -> { nested_ary.flatten! }.should raise_error(FrozenError) + end + + # see [ruby-core:23663] + it "raises a FrozenError on frozen arrays when the array would not be modified" do + -> { ArraySpecs.frozen_array.flatten! }.should raise_error(FrozenError) + -> { ArraySpecs.empty_frozen_array.flatten! }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/frozen_spec.rb b/spec/ruby/core/array/frozen_spec.rb new file mode 100644 index 0000000000..3ba54be46b --- /dev/null +++ b/spec/ruby/core/array/frozen_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#frozen?" do + it "returns true if array is frozen" do + a = [1, 2, 3] + a.should_not.frozen? + a.freeze + a.should.frozen? + end + + it "returns false for an array being sorted by #sort" do + a = [1, 2, 3] + a.sort { |x,y| a.should_not.frozen?; x <=> y } + end +end diff --git a/spec/ruby/core/array/hash_spec.rb b/spec/ruby/core/array/hash_spec.rb new file mode 100644 index 0000000000..f3bcc83fce --- /dev/null +++ b/spec/ruby/core/array/hash_spec.rb @@ -0,0 +1,83 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#hash" do + it "returns the same fixnum for arrays with the same content" do + [].respond_to?(:hash).should == true + + [[], [1, 2, 3]].each do |ary| + ary.hash.should == ary.dup.hash + ary.hash.should be_an_instance_of(Integer) + end + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + -> { empty.hash }.should_not raise_error + + array = ArraySpecs.recursive_array + -> { array.hash }.should_not raise_error + end + + it "returns the same hash for equal recursive arrays" do + rec = []; rec << rec + rec.hash.should == [rec].hash + rec.hash.should == [[rec]].hash + # This is because rec.eql?([[rec]]) + # Remember that if two objects are eql? + # then the need to have the same hash + # Check the Array#eql? specs! + end + + it "returns the same hash for equal recursive arrays through hashes" do + h = {} ; rec = [h] ; h[:x] = rec + rec.hash.should == [h].hash + rec.hash.should == [{x: rec}].hash + # Like above, this is because rec.eql?([{x: rec}]) + end + + it "calls to_int on result of calling hash on each element" do + ary = Array.new(5) do + obj = mock('0') + obj.should_receive(:hash).and_return(obj) + obj.should_receive(:to_int).and_return(0) + obj + end + + ary.hash + + + hash = mock('1') + hash.should_receive(:to_int).and_return(1.hash) + + obj = mock('@hash') + obj.instance_variable_set(:@hash, hash) + def obj.hash() @hash end + + [obj].hash.should == [1].hash + end + + it "ignores array class differences" do + ArraySpecs::MyArray[].hash.should == [].hash + ArraySpecs::MyArray[1, 2].hash.should == [1, 2].hash + end + + it "returns same hash code for arrays with the same content" do + a = [1, 2, 3, 4] + a.fill 'a', 0..3 + b = %w|a a a a| + a.hash.should == b.hash + end + + it "returns the same value if arrays are #eql?" do + a = [1, 2, 3, 4] + a.fill 'a', 0..3 + b = %w|a a a a| + a.hash.should == b.hash + a.should eql(b) + end + + it "produces different hashes for nested arrays with different values and empty terminator" do + [1, [1, []]].hash.should_not == [2, [2, []]].hash + end +end diff --git a/spec/ruby/core/array/include_spec.rb b/spec/ruby/core/array/include_spec.rb new file mode 100644 index 0000000000..227173218f --- /dev/null +++ b/spec/ruby/core/array/include_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#include?" do + it "returns true if object is present, false otherwise" do + [1, 2, "a", "b"].include?("c").should == false + [1, 2, "a", "b"].include?("a").should == true + end + + it "determines presence by using element == obj" do + o = mock('') + + [1, 2, "a", "b"].include?(o).should == false + + def o.==(other); other == 'a'; end + + [1, 2, o, "b"].include?('a').should == true + + [1, 2.0, 3].include?(2).should == true + end + + it "calls == on elements from left to right until success" do + key = "x" + one = mock('one') + two = mock('two') + three = mock('three') + one.should_receive(:==).any_number_of_times.and_return(false) + two.should_receive(:==).any_number_of_times.and_return(true) + three.should_not_receive(:==) + ary = [one, two, three] + ary.include?(key).should == true + end +end diff --git a/spec/ruby/core/array/index_spec.rb b/spec/ruby/core/array/index_spec.rb new file mode 100644 index 0000000000..3acb7d0ef3 --- /dev/null +++ b/spec/ruby/core/array/index_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/index' + +describe "Array#index" do + it_behaves_like :array_index, :index +end diff --git a/spec/ruby/core/array/initialize_spec.rb b/spec/ruby/core/array/initialize_spec.rb new file mode 100644 index 0000000000..b9fa77b16e --- /dev/null +++ b/spec/ruby/core/array/initialize_spec.rb @@ -0,0 +1,158 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#initialize" do + before :each do + ScratchPad.clear + end + + it "is private" do + Array.should have_private_instance_method("initialize") + end + + it "is called on subclasses" do + b = ArraySpecs::SubArray.new :size_or_array, :obj + + b.should == [] + ScratchPad.recorded.should == [:size_or_array, :obj] + end + + it "preserves the object's identity even when changing its value" do + a = [1, 2, 3] + a.send(:initialize).should equal(a) + a.should_not == [1, 2, 3] + end + + it "raises an ArgumentError if passed 3 or more arguments" do + -> do + [1, 2].send :initialize, 1, 'x', true + end.should raise_error(ArgumentError) + -> do + [1, 2].send(:initialize, 1, 'x', true) {} + end.should raise_error(ArgumentError) + end + + it "raises a FrozenError on frozen arrays" do + -> do + ArraySpecs.frozen_array.send :initialize + end.should raise_error(FrozenError) + -> do + ArraySpecs.frozen_array.send :initialize, ArraySpecs.frozen_array + end.should raise_error(FrozenError) + end + + it "calls #to_ary to convert the value to an array, even if it's private" do + a = ArraySpecs::PrivateToAry.new + [].send(:initialize, a).should == [1, 2, 3] + end +end + +describe "Array#initialize with no arguments" do + it "makes the array empty" do + [1, 2, 3].send(:initialize).should be_empty + end + + it "does not use the given block" do + -> { + -> { [1, 2, 3].send(:initialize) { raise } }.should_not raise_error + }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) + end +end + +describe "Array#initialize with (array)" do + it "replaces self with the other array" do + b = [4, 5, 6] + [1, 2, 3].send(:initialize, b).should == b + end + + it "does not use the given block" do + ->{ [1, 2, 3].send(:initialize) { raise } }.should_not raise_error + end + + it "calls #to_ary to convert the value to an array" do + a = mock("array") + a.should_receive(:to_ary).and_return([1, 2]) + a.should_not_receive(:to_int) + [].send(:initialize, a).should == [1, 2] + end + + it "does not call #to_ary on instances of Array or subclasses of Array" do + a = [1, 2] + a.should_not_receive(:to_ary) + [].send(:initialize, a).should == a + end + + it "raises a TypeError if an Array type argument and a default object" do + -> { [].send(:initialize, [1, 2], 1) }.should raise_error(TypeError) + end +end + +describe "Array#initialize with (size, object=nil)" do + it "sets the array to size and fills with the object" do + a = [] + obj = [3] + a.send(:initialize, 2, obj).should == [obj, obj] + a[0].should equal(obj) + a[1].should equal(obj) + + b = [] + b.send(:initialize, 3, 14).should == [14, 14, 14] + b.should == [14, 14, 14] + end + + it "sets the array to size and fills with nil when object is omitted" do + [].send(:initialize, 3).should == [nil, nil, nil] + end + + it "raises an ArgumentError if size is negative" do + -> { [].send(:initialize, -1, :a) }.should raise_error(ArgumentError) + -> { [].send(:initialize, -1) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if size is too large" do + -> { [].send(:initialize, fixnum_max+1) }.should raise_error(ArgumentError) + end + + it "calls #to_int to convert the size argument to an Integer when object is given" do + obj = mock('1') + obj.should_receive(:to_int).and_return(1) + [].send(:initialize, obj, :a).should == [:a] + end + + it "calls #to_int to convert the size argument to an Integer when object is not given" do + obj = mock('1') + obj.should_receive(:to_int).and_return(1) + [].send(:initialize, obj).should == [nil] + end + + it "raises a TypeError if the size argument is not an Integer type" do + obj = mock('nonnumeric') + obj.stub!(:to_ary).and_return([1, 2]) + ->{ [].send(:initialize, obj, :a) }.should raise_error(TypeError) + end + + it "yields the index of the element and sets the element to the value of the block" do + [].send(:initialize, 3) { |i| i.to_s }.should == ['0', '1', '2'] + end + + it "uses the block value instead of using the default value" do + -> { + @result = [].send(:initialize, 3, :obj) { |i| i.to_s } + }.should complain(/block supersedes default value argument/) + @result.should == ['0', '1', '2'] + end + + it "returns the value passed to break" do + [].send(:initialize, 3) { break :a }.should == :a + end + + it "sets the array to the values returned by the block before break is executed" do + a = [1, 2, 3] + a.send(:initialize, 3) do |i| + break if i == 2 + i.to_s + end + + a.should == ['0', '1'] + end +end diff --git a/spec/ruby/core/array/insert_spec.rb b/spec/ruby/core/array/insert_spec.rb new file mode 100644 index 0000000000..9e1757f68b --- /dev/null +++ b/spec/ruby/core/array/insert_spec.rb @@ -0,0 +1,78 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#insert" do + it "returns self" do + ary = [] + ary.insert(0).should equal(ary) + ary.insert(0, :a).should equal(ary) + end + + it "inserts objects before the element at index for non-negative index" do + ary = [] + ary.insert(0, 3).should == [3] + ary.insert(0, 1, 2).should == [1, 2, 3] + ary.insert(0).should == [1, 2, 3] + + # Let's just assume insert() always modifies the array from now on. + ary.insert(1, 'a').should == [1, 'a', 2, 3] + ary.insert(0, 'b').should == ['b', 1, 'a', 2, 3] + ary.insert(5, 'c').should == ['b', 1, 'a', 2, 3, 'c'] + ary.insert(7, 'd').should == ['b', 1, 'a', 2, 3, 'c', nil, 'd'] + ary.insert(10, 5, 4).should == ['b', 1, 'a', 2, 3, 'c', nil, 'd', nil, nil, 5, 4] + end + + it "appends objects to the end of the array for index == -1" do + [1, 3, 3].insert(-1, 2, 'x', 0.5).should == [1, 3, 3, 2, 'x', 0.5] + end + + it "inserts objects after the element at index with negative index" do + ary = [] + ary.insert(-1, 3).should == [3] + ary.insert(-2, 2).should == [2, 3] + ary.insert(-3, 1).should == [1, 2, 3] + ary.insert(-2, -3).should == [1, 2, -3, 3] + ary.insert(-1, []).should == [1, 2, -3, 3, []] + ary.insert(-2, 'x', 'y').should == [1, 2, -3, 3, 'x', 'y', []] + ary = [1, 2, 3] + end + + it "pads with nils if the index to be inserted to is past the end" do + [].insert(5, 5).should == [nil, nil, nil, nil, nil, 5] + end + + it "can insert before the first element with a negative index" do + [1, 2, 3].insert(-4, -3).should == [-3, 1, 2, 3] + end + + it "raises an IndexError if the negative index is out of bounds" do + -> { [].insert(-2, 1) }.should raise_error(IndexError) + -> { [1].insert(-3, 2) }.should raise_error(IndexError) + end + + it "does nothing of no object is passed" do + [].insert(0).should == [] + [].insert(-1).should == [] + [].insert(10).should == [] + [].insert(-2).should == [] + end + + it "tries to convert the passed position argument to an Integer using #to_int" do + obj = mock('2') + obj.should_receive(:to_int).and_return(2) + [].insert(obj, 'x').should == [nil, nil, 'x'] + end + + it "raises an ArgumentError if no argument passed" do + -> { [].insert() }.should raise_error(ArgumentError) + end + + it "raises a FrozenError on frozen arrays when the array is modified" do + -> { ArraySpecs.frozen_array.insert(0, 'x') }.should raise_error(FrozenError) + end + + # see [ruby-core:23666] + it "raises a FrozenError on frozen arrays when the array would not be modified" do + -> { ArraySpecs.frozen_array.insert(0) }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/inspect_spec.rb b/spec/ruby/core/array/inspect_spec.rb new file mode 100644 index 0000000000..0832224f5a --- /dev/null +++ b/spec/ruby/core/array/inspect_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/inspect' + +describe "Array#inspect" do + it_behaves_like :array_inspect, :inspect +end diff --git a/spec/ruby/core/array/intersect_spec.rb b/spec/ruby/core/array/intersect_spec.rb new file mode 100644 index 0000000000..456aa26c6e --- /dev/null +++ b/spec/ruby/core/array/intersect_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe 'Array#intersect?' do + describe 'when at least one element in two Arrays is the same' do + it 'returns true' do + [1, 2].intersect?([2, 3, 4]).should == true + [2, 3, 4].intersect?([1, 2]).should == true + end + end + + describe 'when there are no elements in common between two Arrays' do + it 'returns false' do + [0, 1, 2].intersect?([3, 4]).should == false + [3, 4].intersect?([0, 1, 2]).should == false + [3, 4].intersect?([]).should == false + [].intersect?([0, 1, 2]).should == false + end + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('[1,2,3]') + obj.should_receive(:to_ary).and_return([1, 2, 3]) + + [1, 2].intersect?(obj).should == true + end + + it "determines equivalence between elements in the sense of eql?" do + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.stub!(:eql?).and_return(true) + obj2.stub!(:eql?).and_return(true) + + [obj1].intersect?([obj2]).should == true + + obj1 = mock('3') + obj2 = mock('4') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.stub!(:eql?).and_return(false) + obj2.stub!(:eql?).and_return(false) + + [obj1].intersect?([obj2]).should == false + end + + it "does not call to_ary on array subclasses" do + [5, 6].intersect?(ArraySpecs::ToAryArray[1, 2, 5, 6]).should == true + end + + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.stub!(:hash).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + [x].intersect?([x]).should == true + end + + it "has semantic of !(a & b).empty?" do + [].intersect?([]).should == false + [nil].intersect?([nil]).should == true + end +end diff --git a/spec/ruby/core/array/intersection_spec.rb b/spec/ruby/core/array/intersection_spec.rb new file mode 100644 index 0000000000..e01a68d389 --- /dev/null +++ b/spec/ruby/core/array/intersection_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/intersection' + +describe "Array#&" do + it_behaves_like :array_intersection, :& +end + +describe "Array#intersection" do + it_behaves_like :array_intersection, :intersection + + it "accepts multiple arguments" do + [1, 2, 3, 4].intersection([1, 2, 3], [2, 3, 4]).should == [2, 3] + end + + it "preserves elements order from original array" do + [1, 2, 3, 4].intersection([3, 2, 1]).should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/join_spec.rb b/spec/ruby/core/array/join_spec.rb new file mode 100644 index 0000000000..e78ea6f9e1 --- /dev/null +++ b/spec/ruby/core/array/join_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/join' + +describe "Array#join" do + it_behaves_like :array_join_with_string_separator, :join + it_behaves_like :array_join_with_default_separator, :join + + it "does not separate elements when the passed separator is nil" do + [1, 2, 3].join(nil).should == '123' + end + + it "calls #to_str to convert the separator to a String" do + sep = mock("separator") + sep.should_receive(:to_str).and_return(", ") + [1, 2].join(sep).should == "1, 2" + end + + it "does not call #to_str on the separator if the array is empty" do + sep = mock("separator") + sep.should_not_receive(:to_str) + [].join(sep).should == "" + end + + it "raises a TypeError if the separator cannot be coerced to a String by calling #to_str" do + obj = mock("not a string") + -> { [1, 2].join(obj) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed false as the separator" do + -> { [1, 2].join(false) }.should raise_error(TypeError) + end +end + +describe "Array#join with $," do + before :each do + @before_separator = $, + end + + after :each do + suppress_warning {$, = @before_separator} + end + + it "separates elements with default separator when the passed separator is nil" do + suppress_warning { + $, = "_" + [1, 2, 3].join(nil).should == '1_2_3' + } + end +end diff --git a/spec/ruby/core/array/keep_if_spec.rb b/spec/ruby/core/array/keep_if_spec.rb new file mode 100644 index 0000000000..40f7329b7c --- /dev/null +++ b/spec/ruby/core/array/keep_if_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/keep_if' + +describe "Array#keep_if" do + it "returns the same array if no changes were made" do + array = [1, 2, 3] + array.keep_if { true }.should equal(array) + end + + it_behaves_like :keep_if, :keep_if +end diff --git a/spec/ruby/core/array/last_spec.rb b/spec/ruby/core/array/last_spec.rb new file mode 100644 index 0000000000..d6fefada09 --- /dev/null +++ b/spec/ruby/core/array/last_spec.rb @@ -0,0 +1,87 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#last" do + it "returns the last element" do + [1, 1, 1, 1, 2].last.should == 2 + end + + it "returns nil if self is empty" do + [].last.should == nil + end + + it "returns the last count elements if given a count" do + [1, 2, 3, 4, 5, 9].last(3).should == [4, 5, 9] + end + + it "returns an empty array when passed a count on an empty array" do + [].last(0).should == [] + [].last(1).should == [] + end + + it "returns an empty array when count == 0" do + [1, 2, 3, 4, 5].last(0).should == [] + end + + it "returns an array containing the last element when passed count == 1" do + [1, 2, 3, 4, 5].last(1).should == [5] + end + + it "raises an ArgumentError when count is negative" do + -> { [1, 2].last(-1) }.should raise_error(ArgumentError) + end + + it "returns the entire array when count > length" do + [1, 2, 3, 4, 5, 9].last(10).should == [1, 2, 3, 4, 5, 9] + end + + it "returns an array which is independent to the original when passed count" do + ary = [1, 2, 3, 4, 5] + ary.last(0).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + ary.last(1).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + ary.last(6).replace([1,2]) + ary.should == [1, 2, 3, 4, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.last.should equal(empty) + + array = ArraySpecs.recursive_array + array.last.should equal(array) + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + [1, 2, 3, 4, 5].last(obj).should == [4, 5] + end + + it "raises a TypeError if the passed argument is not numeric" do + -> { [1,2].last(nil) }.should raise_error(TypeError) + -> { [1,2].last("a") }.should raise_error(TypeError) + + obj = mock("nonnumeric") + -> { [1,2].last(obj) }.should raise_error(TypeError) + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[].last(0).should be_an_instance_of(Array) + ArraySpecs::MyArray[].last(2).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].last(0).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].last(1).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].last(2).should be_an_instance_of(Array) + end + + it "is not destructive" do + a = [1, 2, 3] + a.last + a.should == [1, 2, 3] + a.last(2) + a.should == [1, 2, 3] + a.last(3) + a.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/length_spec.rb b/spec/ruby/core/array/length_spec.rb new file mode 100644 index 0000000000..a90c001300 --- /dev/null +++ b/spec/ruby/core/array/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/length' + +describe "Array#length" do + it_behaves_like :array_length, :length +end diff --git a/spec/ruby/core/array/map_spec.rb b/spec/ruby/core/array/map_spec.rb new file mode 100644 index 0000000000..0c7f3afa8c --- /dev/null +++ b/spec/ruby/core/array/map_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/collect' + +describe "Array#map" do + it_behaves_like :array_collect, :map +end + +describe "Array#map!" do + it_behaves_like :array_collect_b, :map! +end diff --git a/spec/ruby/core/array/max_spec.rb b/spec/ruby/core/array/max_spec.rb new file mode 100644 index 0000000000..d1c64519d0 --- /dev/null +++ b/spec/ruby/core/array/max_spec.rb @@ -0,0 +1,116 @@ +require_relative '../../spec_helper' + +describe "Array#max" do + it "is defined on Array" do + [1].method(:max).owner.should equal Array + end + + it "returns nil with no values" do + [].max.should == nil + end + + it "returns only element in one element array" do + [1].max.should == 1 + end + + it "returns largest value with multiple elements" do + [1,2].max.should == 2 + [2,1].max.should == 2 + end + + describe "given a block with one argument" do + it "yields in turn the last length-1 values from the array" do + ary = [] + result = [1,2,3,4,5].max {|x| ary << x; x} + + ary.should == [2,3,4,5] + result.should == 5 + end + end +end + +# From Enumerable#max, copied for better readability +describe "Array#max" do + before :each do + @a = [2, 4, 6, 8, 10] + + @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"] + @e_ints = [333, 22, 666666, 55555, 1010101010] + end + + it "max should return the maximum element" do + [18, 42].max.should == 42 + [2, 5, 3, 6, 1, 4].max.should == 6 + end + + it "returns the maximum element (basics cases)" do + [55].max.should == 55 + + [11,99].max.should == 99 + [99,11].max.should == 99 + [2, 33, 4, 11].max.should == 33 + + [1,2,3,4,5].max.should == 5 + [5,4,3,2,1].max.should == 5 + [1,4,3,5,2].max.should == 5 + [5,5,5,5,5].max.should == 5 + + ["aa","tt"].max.should == "tt" + ["tt","aa"].max.should == "tt" + ["2","33","4","11"].max.should == "4" + + @e_strs.max.should == "666666" + @e_ints.max.should == 1010101010 + end + + it "returns nil for an empty Enumerable" do + [].max.should == nil + end + + it "raises a NoMethodError for elements without #<=>" do + -> do + [BasicObject.new, BasicObject.new].max + end.should raise_error(NoMethodError) + end + + it "raises an ArgumentError for incomparable elements" do + -> do + [11,"22"].max + end.should raise_error(ArgumentError) + -> do + [11,12,22,33].max{|a, b| nil} + end.should raise_error(ArgumentError) + end + + it "returns the maximum element (with block)" do + # with a block + ["2","33","4","11"].max {|a,b| a <=> b }.should == "4" + [ 2 , 33 , 4 , 11 ].max {|a,b| a <=> b }.should == 33 + + ["2","33","4","11"].max {|a,b| b <=> a }.should == "11" + [ 2 , 33 , 4 , 11 ].max {|a,b| b <=> a }.should == 2 + + @e_strs.max {|a,b| a.length <=> b.length }.should == "1010101010" + + @e_strs.max {|a,b| a <=> b }.should == "666666" + @e_strs.max {|a,b| a.to_i <=> b.to_i }.should == "1010101010" + + @e_ints.max {|a,b| a <=> b }.should == 1010101010 + @e_ints.max {|a,b| a.to_s <=> b.to_s }.should == 666666 + end + + it "returns the minimum for enumerables that contain nils" do + arr = [nil, nil, true] + arr.max { |a, b| + x = a.nil? ? 1 : a ? 0 : -1 + y = b.nil? ? 1 : b ? 0 : -1 + x <=> y + }.should == nil + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = [[1,2], [3,4,5], [6,7,8,9]] + multi.max.should == [6, 7, 8, 9] + end + +end diff --git a/spec/ruby/core/array/min_spec.rb b/spec/ruby/core/array/min_spec.rb new file mode 100644 index 0000000000..3bdef0dd00 --- /dev/null +++ b/spec/ruby/core/array/min_spec.rb @@ -0,0 +1,121 @@ +require_relative '../../spec_helper' + +describe "Array#min" do + it "is defined on Array" do + [1].method(:max).owner.should equal Array + end + + it "returns nil with no values" do + [].min.should == nil + end + + it "returns only element in one element array" do + [1].min.should == 1 + end + + it "returns smallest value with multiple elements" do + [1,2].min.should == 1 + [2,1].min.should == 1 + end + + describe "given a block with one argument" do + it "yields in turn the last length-1 values from the array" do + ary = [] + result = [1,2,3,4,5].min {|x| ary << x; x} + + ary.should == [2,3,4,5] + result.should == 1 + end + end +end + +# From Enumerable#min, copied for better readability +describe "Array#min" do + before :each do + @a = [2, 4, 6, 8, 10] + + @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"] + @e_ints = [ 333, 22, 666666, 55555, 1010101010] + end + + it "min should return the minimum element" do + [18, 42].min.should == 18 + [2, 5, 3, 6, 1, 4].min.should == 1 + end + + it "returns the minimum (basic cases)" do + [55].min.should == 55 + + [11,99].min.should == 11 + [99,11].min.should == 11 + [2, 33, 4, 11].min.should == 2 + + [1,2,3,4,5].min.should == 1 + [5,4,3,2,1].min.should == 1 + [4,1,3,5,2].min.should == 1 + [5,5,5,5,5].min.should == 5 + + ["aa","tt"].min.should == "aa" + ["tt","aa"].min.should == "aa" + ["2","33","4","11"].min.should == "11" + + @e_strs.min.should == "1" + @e_ints.min.should == 22 + end + + it "returns nil for an empty Enumerable" do + [].min.should be_nil + end + + it "raises a NoMethodError for elements without #<=>" do + -> do + [BasicObject.new, BasicObject.new].min + end.should raise_error(NoMethodError) + end + + it "raises an ArgumentError for incomparable elements" do + -> do + [11,"22"].min + end.should raise_error(ArgumentError) + -> do + [11,12,22,33].min{|a, b| nil} + end.should raise_error(ArgumentError) + end + + it "returns the minimum when using a block rule" do + ["2","33","4","11"].min {|a,b| a <=> b }.should == "11" + [ 2 , 33 , 4 , 11 ].min {|a,b| a <=> b }.should == 2 + + ["2","33","4","11"].min {|a,b| b <=> a }.should == "4" + [ 2 , 33 , 4 , 11 ].min {|a,b| b <=> a }.should == 33 + + [ 1, 2, 3, 4 ].min {|a,b| 15 }.should == 1 + + [11,12,22,33].min{|a, b| 2 }.should == 11 + @i = -2 + [11,12,22,33].min{|a, b| @i += 1 }.should == 12 + + @e_strs.min {|a,b| a.length <=> b.length }.should == "1" + + @e_strs.min {|a,b| a <=> b }.should == "1" + @e_strs.min {|a,b| a.to_i <=> b.to_i }.should == "1" + + @e_ints.min {|a,b| a <=> b }.should == 22 + @e_ints.min {|a,b| a.to_s <=> b.to_s }.should == 1010101010 + end + + it "returns the minimum for enumerables that contain nils" do + arr = [nil, nil, true] + arr.min { |a, b| + x = a.nil? ? -1 : a ? 0 : 1 + y = b.nil? ? -1 : b ? 0 : 1 + x <=> y + }.should == nil + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = [[1,2], [3,4,5], [6,7,8,9]] + multi.min.should == [1, 2] + end + +end diff --git a/spec/ruby/core/array/minmax_spec.rb b/spec/ruby/core/array/minmax_spec.rb new file mode 100644 index 0000000000..e11fe63347 --- /dev/null +++ b/spec/ruby/core/array/minmax_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require_relative '../../shared/enumerable/minmax' + +describe "Array#minmax" do + before :each do + @enum = [6, 4, 5, 10, 8] + @empty_enum = [] + @incomparable_enum = [BasicObject.new, BasicObject.new] + @incompatible_enum = [11, "22"] + @strs = ["333", "2", "60", "55555", "1010", "111"] + end + + it_behaves_like :enumerable_minmax, :minmax +end diff --git a/spec/ruby/core/array/minus_spec.rb b/spec/ruby/core/array/minus_spec.rb new file mode 100644 index 0000000000..cb1bf56d76 --- /dev/null +++ b/spec/ruby/core/array/minus_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/difference' + +describe "Array#-" do + it_behaves_like :array_binary_difference, :- +end diff --git a/spec/ruby/core/array/multiply_spec.rb b/spec/ruby/core/array/multiply_spec.rb new file mode 100644 index 0000000000..eca51142fb --- /dev/null +++ b/spec/ruby/core/array/multiply_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/join' + +describe "Array#*" do + it "tries to convert the passed argument to a String using #to_str" do + obj = mock('separator') + obj.should_receive(:to_str).and_return('::') + ([1, 2, 3, 4] * obj).should == '1::2::3::4' + end + + it "tires to convert the passed argument to an Integer using #to_int" do + obj = mock('count') + obj.should_receive(:to_int).and_return(2) + ([1, 2, 3, 4] * obj).should == [1, 2, 3, 4, 1, 2, 3, 4] + end + + it "raises a TypeError if the argument can neither be converted to a string nor an integer" do + obj = mock('not a string or integer') + ->{ [1,2] * obj }.should raise_error(TypeError) + end + + it "converts the passed argument to a String rather than an Integer" do + obj = mock('2') + def obj.to_int() 2 end + def obj.to_str() "2" end + ([:a, :b, :c] * obj).should == "a2b2c" + end + + it "raises a TypeError is the passed argument is nil" do + ->{ [1,2] * nil }.should raise_error(TypeError) + end + + it "raises an ArgumentError when passed 2 or more arguments" do + ->{ [1,2].send(:*, 1, 2) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed no arguments" do + ->{ [1,2].send(:*) }.should raise_error(ArgumentError) + end +end + +describe "Array#* with an integer" do + it "concatenates n copies of the array when passed an integer" do + ([ 1, 2, 3 ] * 0).should == [] + ([ 1, 2, 3 ] * 1).should == [1, 2, 3] + ([ 1, 2, 3 ] * 3).should == [1, 2, 3, 1, 2, 3, 1, 2, 3] + ([] * 10).should == [] + end + + it "does not return self even if the passed integer is 1" do + ary = [1, 2, 3] + (ary * 1).should_not equal(ary) + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + (empty * 0).should == [] + (empty * 1).should == empty + (empty * 3).should == [empty, empty, empty] + + array = ArraySpecs.recursive_array + (array * 0).should == [] + (array * 1).should == array + end + + it "raises an ArgumentError when passed a negative integer" do + -> { [ 1, 2, 3 ] * -1 }.should raise_error(ArgumentError) + -> { [] * -1 }.should raise_error(ArgumentError) + end + + describe "with a subclass of Array" do + before :each do + ScratchPad.clear + + @array = ArraySpecs::MyArray[1, 2, 3, 4, 5] + end + + it "returns an Array instance" do + (@array * 0).should be_an_instance_of(Array) + (@array * 1).should be_an_instance_of(Array) + (@array * 2).should be_an_instance_of(Array) + end + + it "does not call #initialize on the subclass instance" do + (@array * 2).should == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] + ScratchPad.recorded.should be_nil + end + end +end + +describe "Array#* with a string" do + it_behaves_like :array_join_with_string_separator, :* +end diff --git a/spec/ruby/core/array/new_spec.rb b/spec/ruby/core/array/new_spec.rb new file mode 100644 index 0000000000..b50a4857b0 --- /dev/null +++ b/spec/ruby/core/array/new_spec.rb @@ -0,0 +1,124 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array.new" do + it "returns an instance of Array" do + Array.new.should be_an_instance_of(Array) + end + + it "returns an instance of a subclass" do + ArraySpecs::MyArray.new(1, 2).should be_an_instance_of(ArraySpecs::MyArray) + end + + it "raises an ArgumentError if passed 3 or more arguments" do + -> do + [1, 2].send :initialize, 1, 'x', true + end.should raise_error(ArgumentError) + -> do + [1, 2].send(:initialize, 1, 'x', true) {} + end.should raise_error(ArgumentError) + end +end + +describe "Array.new with no arguments" do + it "returns an empty array" do + Array.new.should be_empty + end + + it "does not use the given block" do + -> { + -> { Array.new { raise } }.should_not raise_error + }.should complain(/warning: given block not used/, verbose: true) + end +end + +describe "Array.new with (array)" do + it "returns an array initialized to the other array" do + b = [4, 5, 6] + Array.new(b).should == b + end + + it "does not use the given block" do + ->{ Array.new([1, 2]) { raise } }.should_not raise_error + end + + it "calls #to_ary to convert the value to an array" do + a = mock("array") + a.should_receive(:to_ary).and_return([1, 2]) + a.should_not_receive(:to_int) + Array.new(a).should == [1, 2] + end + + it "does not call #to_ary on instances of Array or subclasses of Array" do + a = [1, 2] + a.should_not_receive(:to_ary) + Array.new(a) + end + + it "raises a TypeError if an Array type argument and a default object" do + -> { Array.new([1, 2], 1) }.should raise_error(TypeError) + end +end + +describe "Array.new with (size, object=nil)" do + it "returns an array of size filled with object" do + obj = [3] + a = Array.new(2, obj) + a.should == [obj, obj] + a[0].should equal(obj) + a[1].should equal(obj) + + Array.new(3, 14).should == [14, 14, 14] + end + + it "returns an array of size filled with nil when object is omitted" do + Array.new(3).should == [nil, nil, nil] + end + + it "raises an ArgumentError if size is negative" do + -> { Array.new(-1, :a) }.should raise_error(ArgumentError) + -> { Array.new(-1) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if size is too large" do + -> { Array.new(fixnum_max+1) }.should raise_error(ArgumentError) + end + + it "calls #to_int to convert the size argument to an Integer when object is given" do + obj = mock('1') + obj.should_receive(:to_int).and_return(1) + Array.new(obj, :a).should == [:a] + end + + it "calls #to_int to convert the size argument to an Integer when object is not given" do + obj = mock('1') + obj.should_receive(:to_int).and_return(1) + Array.new(obj).should == [nil] + end + + it "raises a TypeError if the size argument is not an Integer type" do + obj = mock('nonnumeric') + obj.stub!(:to_ary).and_return([1, 2]) + ->{ Array.new(obj, :a) }.should raise_error(TypeError) + end + + it "yields the index of the element and sets the element to the value of the block" do + Array.new(3) { |i| i.to_s }.should == ['0', '1', '2'] + end + + it "uses the block value instead of using the default value" do + -> { + @result = Array.new(3, :obj) { |i| i.to_s } + }.should complain(/block supersedes default value argument/) + @result.should == ['0', '1', '2'] + end + + it "returns the value passed to break" do + a = Array.new(3) do |i| + break if i == 2 + i.to_s + end + + a.should == nil + end +end diff --git a/spec/ruby/core/array/none_spec.rb b/spec/ruby/core/array/none_spec.rb new file mode 100644 index 0000000000..31cd8c46d6 --- /dev/null +++ b/spec/ruby/core/array/none_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#none?" do + @value_to_return = -> _ { false } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :none? + + it "ignores the block if there is an argument" do + -> { + ['bar', 'foobar'].none?(/baz/) { true }.should == true + }.should complain(/given block not used/) + end +end diff --git a/spec/ruby/core/array/one_spec.rb b/spec/ruby/core/array/one_spec.rb new file mode 100644 index 0000000000..0c61907881 --- /dev/null +++ b/spec/ruby/core/array/one_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#one?" do + @value_to_return = -> _ { false } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :one? + + it "ignores the block if there is an argument" do + -> { + ['bar', 'foobar'].one?(/foo/) { false }.should == true + }.should complain(/given block not used/) + end +end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb new file mode 100644 index 0000000000..8245cd5470 --- /dev/null +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -0,0 +1,73 @@ +# encoding: binary +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 "calls #to_str to convert an Object to a String" do + obj = mock("pack A string") + obj.should_receive(:to_str).and_return("``abcdef") + [obj].pack("A*").should == "``abcdef" + end + + it "will not implicitly convert a number to a string" do + -> { [0].pack('A') }.should raise_error(TypeError) + -> { [0].pack('a') }.should raise_error(TypeError) + end + + it "adds all the bytes to the output when passed the '*' modifier" do + ["abc"].pack("A*").should == "abc" + end + + it "pads the output with spaces when the count exceeds the size of the String" do + ["abc"].pack("A6").should == "abc " + end + + it "adds a space when the value is nil" do + [nil].pack("A").should == " " + end + + it "pads the output with spaces when the value is nil" do + [nil].pack("A3").should == " " + end + + it "does not pad with spaces when passed the '*' modifier and the value is nil" do + [nil].pack("A*").should == "" + end +end + +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" + end + + it "pads the output with NULL bytes when the count exceeds the size of the String" do + ["abc"].pack("a6").should == "abc\x00\x00\x00" + end + + it "adds a NULL byte when the value is nil" do + [nil].pack("a").should == "\x00" + end + + it "pads the output with NULL bytes when the value is nil" do + [nil].pack("a3").should == "\x00\x00\x00" + end + + it "does not pad with NULL bytes when passed the '*' modifier and the value is nil" do + [nil].pack("a*").should == "" + end +end diff --git a/spec/ruby/core/array/pack/at_spec.rb b/spec/ruby/core/array/pack/at_spec.rb new file mode 100644 index 0000000000..bb9801440a --- /dev/null +++ b/spec/ruby/core/array/pack/at_spec.rb @@ -0,0 +1,30 @@ +# encoding: binary +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' + +describe "Array#pack with format '@'" do + it_behaves_like :array_pack_basic, '@' + it_behaves_like :array_pack_basic_non_float, '@' + it_behaves_like :array_pack_no_platform, '@' + + it "moves the insertion point to the index specified by the count modifier" do + [1, 2, 3, 4, 5].pack("C4@2C").should == "\x01\x02\x05" + end + + it "does not consume any elements" do + [1, 2, 3].pack("C@3C").should == "\x01\x00\x00\x02" + end + + it "extends the string with NULL bytes if the string size is less than the count" do + [1, 2, 3].pack("@3C*").should == "\x00\x00\x00\x01\x02\x03" + end + + it "truncates the string if the string size is greater than the count" do + [1, 2, 3].pack("Cx5@2C").should == "\x01\x00\x02" + end + + it "implicitly has a count of one when no count modifier is passed" do + [1, 2, 3].pack("C*@").should == "\x01" + end +end diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb new file mode 100644 index 0000000000..247a9ca023 --- /dev/null +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -0,0 +1,113 @@ +# encoding: binary +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 B string") + obj.should_receive(:to_str).and_return("``abcdef") + [obj].pack("B*").should == "\x2a" + end + + it "will not implicitly convert a number to a string" do + -> { [0].pack('B') }.should raise_error(TypeError) + -> { [0].pack('b') }.should raise_error(TypeError) + end + + it "encodes one bit for each character starting with the most significant bit" do + [ [["0"], "\x00"], + [["1"], "\x80"] + ].should be_computed_by(:pack, "B") + end + + it "implicitly has a count of one when not passed a count modifier" do + ["1"].pack("B").should == "\x80" + end + + it "implicitly has count equal to the string length when passed the '*' modifier" do + [ [["00101010"], "\x2a"], + [["00000000"], "\x00"], + [["11111111"], "\xff"], + [["10000000"], "\x80"], + [["00000001"], "\x01"] + ].should be_computed_by(:pack, "B*") + end + + it "encodes the least significant bit of a character other than 0 or 1" do + [ [["bbababab"], "\x2a"], + [["^&#&#^#^"], "\x2a"], + [["(()()()("], "\x2a"], + [["@@%@%@%@"], "\x2a"], + [["ppqrstuv"], "\x2a"], + [["rqtvtrqp"], "\x42"] + ].should be_computed_by(:pack, "B*") + end + + it "returns a binary string" do + ["1"].pack("B").encoding.should == Encoding::BINARY + end + + it "encodes the string as a sequence of bytes" do + ["ああああああああ"].pack("B*").should == "\xdbm\xb6" + end +end + +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") + obj.should_receive(:to_str).and_return("`abcdef`") + [obj].pack("b*").should == "\x2a" + end + + it "encodes one bit for each character starting with the least significant bit" do + [ [["0"], "\x00"], + [["1"], "\x01"] + ].should be_computed_by(:pack, "b") + end + + it "implicitly has a count of one when not passed a count modifier" do + ["1"].pack("b").should == "\x01" + end + + it "implicitly has count equal to the string length when passed the '*' modifier" do + [ [["0101010"], "\x2a"], + [["00000000"], "\x00"], + [["11111111"], "\xff"], + [["10000000"], "\x01"], + [["00000001"], "\x80"] + ].should be_computed_by(:pack, "b*") + end + + it "encodes the least significant bit of a character other than 0 or 1" do + [ [["bababab"], "\x2a"], + [["&#&#^#^"], "\x2a"], + [["()()()("], "\x2a"], + [["@%@%@%@"], "\x2a"], + [["pqrstuv"], "\x2a"], + [["qrtrtvs"], "\x41"] + ].should be_computed_by(:pack, "b*") + end + + it "returns a binary string" do + ["1"].pack("b").encoding.should == Encoding::BINARY + end + + it "encodes the string as a sequence of bytes" do + ["ああああああああ"].pack("b*").should == "\xdb\xb6m" + end +end diff --git a/spec/ruby/core/array/pack/buffer_spec.rb b/spec/ruby/core/array/pack/buffer_spec.rb new file mode 100644 index 0000000000..b77b2d1efa --- /dev/null +++ b/spec/ruby/core/array/pack/buffer_spec.rb @@ -0,0 +1,60 @@ +# encoding: binary + +require_relative '../../../spec_helper' + +describe "Array#pack with :buffer option" do + it "returns specified buffer" do + n = [ 65, 66, 67 ] + buffer = " "*3 + result = n.pack("ccc", buffer: buffer) #=> "ABC" + result.should equal(buffer) + end + + it "adds result at the end of buffer content" do + n = [ 65, 66, 67 ] # result without buffer is "ABC" + + buffer = +"" + n.pack("ccc", buffer: buffer).should == "ABC" + + buffer = +"123" + n.pack("ccc", buffer: buffer).should == "123ABC" + + buffer = +"12345" + n.pack("ccc", buffer: buffer).should == "12345ABC" + end + + it "raises TypeError exception if buffer is not String" do + -> { [65].pack("ccc", buffer: []) }.should raise_error( + TypeError, "buffer must be String, not Array") + end + + it "raise FrozenError if buffer is frozen" do + -> { [65].pack("c", buffer: "frozen-string".freeze) }.should raise_error(FrozenError) + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + [65, 66, 67].pack("ccc", buffer: buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + + context "offset (@) is specified" do + it 'keeps buffer content if it is longer than offset' do + n = [ 65, 66, 67 ] + buffer = +"123456" + n.pack("@3ccc", buffer: buffer).should == "123ABC" + end + + it "fills the gap with \\0 if buffer content is shorter than offset" do + n = [ 65, 66, 67 ] + buffer = +"123" + n.pack("@6ccc", buffer: buffer).should == "123\0\0\0ABC" + end + + it 'does not keep buffer content if it is longer than offset + result' do + n = [ 65, 66, 67 ] + buffer = +"1234567890" + n.pack("@3ccc", buffer: buffer).should == "123ABC" + end + end +end diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb new file mode 100644 index 0000000000..47b71b663d --- /dev/null +++ b/spec/ruby/core/array/pack/c_spec.rb @@ -0,0 +1,87 @@ +# encoding: binary + +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' + +describe :array_pack_8bit, shared: true do + it "encodes the least significant eight bits of a positive number" do + [ [[49], "1"], + [[0b11111111], "\xFF"], + [[0b100000000], "\x00"], + [[0b100000001], "\x01"] + ].should be_computed_by(:pack, pack_format) + end + + it "encodes the least significant eight bits of a negative number" do + [ [[-1], "\xFF"], + [[-0b10000000], "\x80"], + [[-0b11111111], "\x01"], + [[-0b100000000], "\x00"], + [[-0b100000001], "\xFF"] + ].should be_computed_by(:pack, pack_format) + end + + it "encodes a Float truncated as an Integer" do + [ [[5.2], "\x05"], + [[5.8], "\x05"] + ].should be_computed_by(:pack, pack_format) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + [obj].pack(pack_format).should == "\x05" + end + + it "encodes the number of array elements specified by the count modifier" do + [ [[1, 2, 3], pack_format(3), "\x01\x02\x03"], + [[1, 2, 3], pack_format(2) + pack_format(1), "\x01\x02\x03"] + ].should be_computed_by(:pack) + end + + it "encodes all remaining elements when passed the '*' modifier" do + [1, 2, 3, 4, 5].pack(pack_format('*')).should == "\x01\x02\x03\x04\x05" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [1, 2, 3].pack(pack_format("\000", 2)).should == "\x01\x02" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [1, 2, 3].pack(pack_format(' ', 2)).should == "\x01\x02" + end +end + +describe "Array#pack with format 'C'" do + it_behaves_like :array_pack_basic, 'C' + it_behaves_like :array_pack_basic_non_float, 'C' + it_behaves_like :array_pack_8bit, 'C' + it_behaves_like :array_pack_arguments, 'C' + it_behaves_like :array_pack_numeric_basic, 'C' + it_behaves_like :array_pack_integer, 'C' + it_behaves_like :array_pack_no_platform, 'C' +end + +describe "Array#pack with format 'c'" do + it_behaves_like :array_pack_basic, 'c' + it_behaves_like :array_pack_basic_non_float, 'c' + it_behaves_like :array_pack_8bit, 'c' + it_behaves_like :array_pack_arguments, 'c' + it_behaves_like :array_pack_numeric_basic, 'c' + it_behaves_like :array_pack_integer, 'c' + it_behaves_like :array_pack_no_platform, 'c' +end diff --git a/spec/ruby/core/array/pack/comment_spec.rb b/spec/ruby/core/array/pack/comment_spec.rb new file mode 100644 index 0000000000..daf1cff06a --- /dev/null +++ b/spec/ruby/core/array/pack/comment_spec.rb @@ -0,0 +1,25 @@ +# encoding: binary +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Array#pack" do + it "ignores directives text from '#' to the first newline" do + [1, 2, 3].pack("c#this is a comment\nc").should == "\x01\x02" + end + + it "ignores directives text from '#' to the end if no newline is present" do + [1, 2, 3].pack("c#this is a comment c").should == "\x01" + end + + it "ignores comments at the start of the directives string" do + [1, 2, 3].pack("#this is a comment\nc").should == "\x01" + end + + it "ignores the entire directive string if it is a comment" do + [1, 2, 3].pack("#this is a comment").should == "" + end + + it "ignores multiple comments" do + [1, 2, 3].pack("c#comment\nc#comment\nc#c").should == "\x01\x02\x03" + end +end diff --git a/spec/ruby/core/array/pack/d_spec.rb b/spec/ruby/core/array/pack/d_spec.rb new file mode 100644 index 0000000000..8bb3654633 --- /dev/null +++ b/spec/ruby/core/array/pack/d_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/float' + +describe "Array#pack with format 'D'" do + it_behaves_like :array_pack_basic, 'D' + it_behaves_like :array_pack_basic_float, 'D' + it_behaves_like :array_pack_arguments, 'D' + it_behaves_like :array_pack_no_platform, 'D' + it_behaves_like :array_pack_numeric_basic, 'D' + it_behaves_like :array_pack_float, 'D' + + little_endian do + it_behaves_like :array_pack_double_le, 'D' + end + + big_endian do + it_behaves_like :array_pack_double_be, 'D' + end +end + +describe "Array#pack with format 'd'" do + it_behaves_like :array_pack_basic, 'd' + it_behaves_like :array_pack_basic_float, 'd' + it_behaves_like :array_pack_arguments, 'd' + it_behaves_like :array_pack_no_platform, 'd' + it_behaves_like :array_pack_numeric_basic, 'd' + it_behaves_like :array_pack_float, 'd' + + little_endian do + it_behaves_like :array_pack_double_le, 'd' + end + + big_endian do + it_behaves_like :array_pack_double_be, 'd' + end +end diff --git a/spec/ruby/core/array/pack/e_spec.rb b/spec/ruby/core/array/pack/e_spec.rb new file mode 100644 index 0000000000..ab61ef578f --- /dev/null +++ b/spec/ruby/core/array/pack/e_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/float' + +describe "Array#pack with format 'E'" do + it_behaves_like :array_pack_basic, 'E' + it_behaves_like :array_pack_basic_float, 'E' + it_behaves_like :array_pack_arguments, 'E' + it_behaves_like :array_pack_no_platform, 'E' + it_behaves_like :array_pack_numeric_basic, 'E' + it_behaves_like :array_pack_float, 'E' + it_behaves_like :array_pack_double_le, 'E' +end + +describe "Array#pack with format 'e'" do + it_behaves_like :array_pack_basic, 'e' + it_behaves_like :array_pack_basic_float, 'e' + it_behaves_like :array_pack_arguments, 'e' + it_behaves_like :array_pack_no_platform, 'e' + it_behaves_like :array_pack_numeric_basic, 'e' + it_behaves_like :array_pack_float, 'e' + it_behaves_like :array_pack_float_le, 'e' +end diff --git a/spec/ruby/core/array/pack/empty_spec.rb b/spec/ruby/core/array/pack/empty_spec.rb new file mode 100644 index 0000000000..d635d6a563 --- /dev/null +++ b/spec/ruby/core/array/pack/empty_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' + +describe "Array#pack with empty format" do + it "returns an empty String" do + [1, 2, 3].pack("").should == "" + end + + it "returns a String with US-ASCII encoding" do + [1, 2, 3].pack("").encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/ruby/core/array/pack/f_spec.rb b/spec/ruby/core/array/pack/f_spec.rb new file mode 100644 index 0000000000..d436e0787c --- /dev/null +++ b/spec/ruby/core/array/pack/f_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/float' + +describe "Array#pack with format 'F'" do + it_behaves_like :array_pack_basic, 'F' + it_behaves_like :array_pack_basic_float, 'F' + it_behaves_like :array_pack_arguments, 'F' + it_behaves_like :array_pack_no_platform, 'F' + it_behaves_like :array_pack_numeric_basic, 'F' + it_behaves_like :array_pack_float, 'F' + + little_endian do + it_behaves_like :array_pack_float_le, 'F' + end + + big_endian do + it_behaves_like :array_pack_float_be, 'F' + end +end + +describe "Array#pack with format 'f'" do + it_behaves_like :array_pack_basic, 'f' + it_behaves_like :array_pack_basic_float, 'f' + it_behaves_like :array_pack_arguments, 'f' + it_behaves_like :array_pack_no_platform, 'f' + it_behaves_like :array_pack_numeric_basic, 'f' + it_behaves_like :array_pack_float, 'f' + + little_endian do + it_behaves_like :array_pack_float_le, 'f' + end + + big_endian do + it_behaves_like :array_pack_float_be, 'f' + end +end diff --git a/spec/ruby/core/array/pack/g_spec.rb b/spec/ruby/core/array/pack/g_spec.rb new file mode 100644 index 0000000000..83b7f81acc --- /dev/null +++ b/spec/ruby/core/array/pack/g_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/float' + +describe "Array#pack with format 'G'" do + it_behaves_like :array_pack_basic, 'G' + it_behaves_like :array_pack_basic_float, 'G' + it_behaves_like :array_pack_arguments, 'G' + it_behaves_like :array_pack_no_platform, 'G' + it_behaves_like :array_pack_numeric_basic, 'G' + it_behaves_like :array_pack_float, 'G' + it_behaves_like :array_pack_double_be, 'G' +end + +describe "Array#pack with format 'g'" do + it_behaves_like :array_pack_basic, 'g' + it_behaves_like :array_pack_basic_float, 'g' + it_behaves_like :array_pack_arguments, 'g' + it_behaves_like :array_pack_no_platform, 'g' + it_behaves_like :array_pack_numeric_basic, 'g' + it_behaves_like :array_pack_float, 'g' + it_behaves_like :array_pack_float_be, 'g' +end diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb new file mode 100644 index 0000000000..ba188874ba --- /dev/null +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -0,0 +1,205 @@ +# encoding: binary +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") + obj.should_receive(:to_str).and_return("a") + [obj].pack("H").should == "\xa0" + end + + it "will not implicitly convert a number to a string" do + -> { [0].pack('H') }.should raise_error(TypeError) + -> { [0].pack('h') }.should raise_error(TypeError) + end + + it "encodes the first character as the most significant nibble when passed no count modifier" do + ["ab"].pack("H").should == "\xa0" + end + + it "implicitly has count equal to the string length when passed the '*' modifier" do + ["deadbeef"].pack("H*").should == "\xde\xad\xbe\xef" + end + + it "encodes count nibbles when passed a count modifier exceeding the string length" do + ["ab"].pack('H8').should == "\xab\x00\x00\x00" + end + + it "encodes the first character as the most significant nibble of a hex value" do + [ [["0"], "\x00"], + [["1"], "\x10"], + [["2"], "\x20"], + [["3"], "\x30"], + [["4"], "\x40"], + [["5"], "\x50"], + [["6"], "\x60"], + [["7"], "\x70"], + [["8"], "\x80"], + [["9"], "\x90"], + [["a"], "\xa0"], + [["b"], "\xb0"], + [["c"], "\xc0"], + [["d"], "\xd0"], + [["e"], "\xe0"], + [["f"], "\xf0"], + [["A"], "\xa0"], + [["B"], "\xb0"], + [["C"], "\xc0"], + [["D"], "\xd0"], + [["E"], "\xe0"], + [["F"], "\xf0"] + ].should be_computed_by(:pack, "H") + end + + it "encodes the second character as the least significant nibble of a hex value" do + [ [["00"], "\x00"], + [["01"], "\x01"], + [["02"], "\x02"], + [["03"], "\x03"], + [["04"], "\x04"], + [["05"], "\x05"], + [["06"], "\x06"], + [["07"], "\x07"], + [["08"], "\x08"], + [["09"], "\x09"], + [["0a"], "\x0a"], + [["0b"], "\x0b"], + [["0c"], "\x0c"], + [["0d"], "\x0d"], + [["0e"], "\x0e"], + [["0f"], "\x0f"], + [["0A"], "\x0a"], + [["0B"], "\x0b"], + [["0C"], "\x0c"], + [["0D"], "\x0d"], + [["0E"], "\x0e"], + [["0F"], "\x0f"] + ].should be_computed_by(:pack, "H2") + end + + it "encodes the least significant nibble of a non alphanumeric character as the most significant nibble of the hex value" do + [ [["^"], "\xe0"], + [["*"], "\xa0"], + [["#"], "\x30"], + [["["], "\xb0"], + [["]"], "\xd0"], + [["@"], "\x00"], + [["!"], "\x10"], + [["H"], "\x10"], + [["O"], "\x80"], + [["T"], "\xd0"], + [["Z"], "\x30"], + ].should be_computed_by(:pack, "H") + end + + it "returns a binary string" do + ["41"].pack("H").encoding.should == Encoding::BINARY + end +end + +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") + obj.should_receive(:to_str).and_return("a") + [obj].pack("h").should == "\x0a" + end + + it "encodes the first character as the least significant nibble when passed no count modifier" do + ["ab"].pack("h").should == "\x0a" + end + + it "implicitly has count equal to the string length when passed the '*' modifier" do + ["deadbeef"].pack("h*").should == "\xed\xda\xeb\xfe" + end + + it "encodes count nibbles when passed a count modifier exceeding the string length" do + ["ab"].pack('h8').should == "\xba\x00\x00\x00" + end + + it "encodes the first character as the least significant nibble of a hex value" do + [ [["0"], "\x00"], + [["1"], "\x01"], + [["2"], "\x02"], + [["3"], "\x03"], + [["4"], "\x04"], + [["5"], "\x05"], + [["6"], "\x06"], + [["7"], "\x07"], + [["8"], "\x08"], + [["9"], "\x09"], + [["a"], "\x0a"], + [["b"], "\x0b"], + [["c"], "\x0c"], + [["d"], "\x0d"], + [["e"], "\x0e"], + [["f"], "\x0f"], + [["A"], "\x0a"], + [["B"], "\x0b"], + [["C"], "\x0c"], + [["D"], "\x0d"], + [["E"], "\x0e"], + [["F"], "\x0f"] + ].should be_computed_by(:pack, "h") + end + + it "encodes the second character as the most significant nibble of a hex value" do + [ [["00"], "\x00"], + [["01"], "\x10"], + [["02"], "\x20"], + [["03"], "\x30"], + [["04"], "\x40"], + [["05"], "\x50"], + [["06"], "\x60"], + [["07"], "\x70"], + [["08"], "\x80"], + [["09"], "\x90"], + [["0a"], "\xa0"], + [["0b"], "\xb0"], + [["0c"], "\xc0"], + [["0d"], "\xd0"], + [["0e"], "\xe0"], + [["0f"], "\xf0"], + [["0A"], "\xa0"], + [["0B"], "\xb0"], + [["0C"], "\xc0"], + [["0D"], "\xd0"], + [["0E"], "\xe0"], + [["0F"], "\xf0"] + ].should be_computed_by(:pack, "h2") + end + + it "encodes the least significant nibble of a non alphanumeric character as the least significant nibble of the hex value" do + [ [["^"], "\x0e"], + [["*"], "\x0a"], + [["#"], "\x03"], + [["["], "\x0b"], + [["]"], "\x0d"], + [["@"], "\x00"], + [["!"], "\x01"], + [["H"], "\x01"], + [["O"], "\x08"], + [["T"], "\x0d"], + [["Z"], "\x03"], + ].should be_computed_by(:pack, "h") + end + + it "returns a binary string" do + ["41"].pack("h").encoding.should == Encoding::BINARY + end +end diff --git a/spec/ruby/core/array/pack/i_spec.rb b/spec/ruby/core/array/pack/i_spec.rb new file mode 100644 index 0000000000..a237071227 --- /dev/null +++ b/spec/ruby/core/array/pack/i_spec.rb @@ -0,0 +1,133 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'I'" do + it_behaves_like :array_pack_basic, 'I' + it_behaves_like :array_pack_basic_non_float, 'I' + it_behaves_like :array_pack_arguments, 'I' + it_behaves_like :array_pack_numeric_basic, 'I' + it_behaves_like :array_pack_integer, 'I' +end + +describe "Array#pack with format 'i'" do + it_behaves_like :array_pack_basic, 'i' + it_behaves_like :array_pack_basic_non_float, 'i' + it_behaves_like :array_pack_arguments, 'i' + it_behaves_like :array_pack_numeric_basic, 'i' + it_behaves_like :array_pack_integer, 'i' +end + +describe "Array#pack with format 'I'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_32bit_le, 'I<' + end + + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'I<_' + it_behaves_like :array_pack_32bit_le, 'I_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'I<!' + it_behaves_like :array_pack_32bit_le, 'I!<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_32bit_be, 'I>' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'I>_' + it_behaves_like :array_pack_32bit_be, 'I_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'I>!' + it_behaves_like :array_pack_32bit_be, 'I!>' + end +end + +describe "Array#pack with format 'i'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_32bit_le, 'i<' + end + + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'i<_' + it_behaves_like :array_pack_32bit_le, 'i_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'i<!' + it_behaves_like :array_pack_32bit_le, 'i!<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_32bit_be, 'i>' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'i>_' + it_behaves_like :array_pack_32bit_be, 'i_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'i>!' + it_behaves_like :array_pack_32bit_be, 'i!>' + end +end + +little_endian do + describe "Array#pack with format 'I'" do + it_behaves_like :array_pack_32bit_le, 'I' + end + + describe "Array#pack with format 'I' with modifier '_'" do + it_behaves_like :array_pack_32bit_le_platform, 'I_' + end + + describe "Array#pack with format 'I' with modifier '!'" do + it_behaves_like :array_pack_32bit_le_platform, 'I!' + end + + describe "Array#pack with format 'i'" do + it_behaves_like :array_pack_32bit_le, 'i' + end + + describe "Array#pack with format 'i' with modifier '_'" do + it_behaves_like :array_pack_32bit_le_platform, 'i_' + end + + describe "Array#pack with format 'i' with modifier '!'" do + it_behaves_like :array_pack_32bit_le_platform, 'i!' + end +end + +big_endian do + describe "Array#pack with format 'I'" do + it_behaves_like :array_pack_32bit_be, 'I' + end + + describe "Array#pack with format 'I' with modifier '_'" do + it_behaves_like :array_pack_32bit_be_platform, 'I_' + end + + describe "Array#pack with format 'I' with modifier '!'" do + it_behaves_like :array_pack_32bit_be_platform, 'I!' + end + + describe "Array#pack with format 'i'" do + it_behaves_like :array_pack_32bit_be, 'i' + end + + describe "Array#pack with format 'i' with modifier '_'" do + it_behaves_like :array_pack_32bit_be_platform, 'i_' + end + + describe "Array#pack with format 'i' with modifier '!'" do + it_behaves_like :array_pack_32bit_be_platform, 'i!' + end +end diff --git a/spec/ruby/core/array/pack/j_spec.rb b/spec/ruby/core/array/pack/j_spec.rb new file mode 100644 index 0000000000..7b62d5efdf --- /dev/null +++ b/spec/ruby/core/array/pack/j_spec.rb @@ -0,0 +1,217 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +platform_is pointer_size: 64 do + describe "Array#pack with format 'J'" do + it_behaves_like :array_pack_basic, 'J' + it_behaves_like :array_pack_basic_non_float, 'J' + it_behaves_like :array_pack_arguments, 'J' + it_behaves_like :array_pack_numeric_basic, 'J' + it_behaves_like :array_pack_integer, 'J' + end + + describe "Array#pack with format 'j'" do + it_behaves_like :array_pack_basic, 'j' + it_behaves_like :array_pack_basic_non_float, 'j' + it_behaves_like :array_pack_arguments, 'j' + it_behaves_like :array_pack_numeric_basic, 'j' + it_behaves_like :array_pack_integer, 'j' + end + + little_endian do + describe "Array#pack with format 'J'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_64bit_le, 'J_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_64bit_le, 'J!' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_64bit_le, 'j_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_64bit_le, 'j!' + end + end + end + + big_endian do + describe "Array#pack with format 'J'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_64bit_be, 'J_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_64bit_be, 'J!' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_64bit_be, 'j_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_64bit_be, 'j!' + end + end + end + + describe "Array#pack with format 'J'" do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_64bit_le, 'J<_' + it_behaves_like :array_pack_64bit_le, 'J_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_64bit_le, 'J<!' + it_behaves_like :array_pack_64bit_le, 'J!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_64bit_be, 'J>_' + it_behaves_like :array_pack_64bit_be, 'J_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_64bit_be, 'J>!' + it_behaves_like :array_pack_64bit_be, 'J!>' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_64bit_le, 'j<_' + it_behaves_like :array_pack_64bit_le, 'j_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_64bit_le, 'j<!' + it_behaves_like :array_pack_64bit_le, 'j!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_64bit_be, 'j>_' + it_behaves_like :array_pack_64bit_be, 'j_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_64bit_be, 'j>!' + it_behaves_like :array_pack_64bit_be, 'j!>' + end + end +end + +platform_is pointer_size: 32 do + describe "Array#pack with format 'J'" do + it_behaves_like :array_pack_basic, 'J' + it_behaves_like :array_pack_basic_non_float, 'J' + it_behaves_like :array_pack_arguments, 'J' + it_behaves_like :array_pack_numeric_basic, 'J' + it_behaves_like :array_pack_integer, 'J' + end + + describe "Array#pack with format 'j'" do + it_behaves_like :array_pack_basic, 'j' + it_behaves_like :array_pack_basic_non_float, 'j' + it_behaves_like :array_pack_arguments, 'j' + it_behaves_like :array_pack_numeric_basic, 'j' + it_behaves_like :array_pack_integer, 'j' + end + + big_endian do + describe "Array#pack with format 'J'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_32bit_be, 'J_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_32bit_be, 'J!' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_32bit_be, 'j_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_32bit_be, 'j!' + end + end + end + + little_endian do + describe "Array#pack with format 'J'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_32bit_le, 'J_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_32bit_le, 'J!' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '_'" do + it_behaves_like :array_pack_32bit_le, 'j_' + end + + describe "with modifier '!'" do + it_behaves_like :array_pack_32bit_le, 'j!' + end + end + end + + describe "Array#pack with format 'J'" do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'J<_' + it_behaves_like :array_pack_32bit_le, 'J_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'J<!' + it_behaves_like :array_pack_32bit_le, 'J!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'J>_' + it_behaves_like :array_pack_32bit_be, 'J_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'J>!' + it_behaves_like :array_pack_32bit_be, 'J!>' + end + end + + describe "Array#pack with format 'j'" do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'j<_' + it_behaves_like :array_pack_32bit_le, 'j_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'j<!' + it_behaves_like :array_pack_32bit_le, 'j!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'j>_' + it_behaves_like :array_pack_32bit_be, 'j_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'j>!' + it_behaves_like :array_pack_32bit_be, 'j!>' + end + end +end diff --git a/spec/ruby/core/array/pack/l_spec.rb b/spec/ruby/core/array/pack/l_spec.rb new file mode 100644 index 0000000000..f6dfb1da83 --- /dev/null +++ b/spec/ruby/core/array/pack/l_spec.rb @@ -0,0 +1,221 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'L'" do + it_behaves_like :array_pack_basic, 'L' + it_behaves_like :array_pack_basic_non_float, 'L' + it_behaves_like :array_pack_arguments, 'L' + it_behaves_like :array_pack_numeric_basic, 'L' + it_behaves_like :array_pack_integer, 'L' +end + +describe "Array#pack with format 'l'" do + it_behaves_like :array_pack_basic, 'l' + it_behaves_like :array_pack_basic_non_float, 'l' + it_behaves_like :array_pack_arguments, 'l' + it_behaves_like :array_pack_numeric_basic, 'l' + it_behaves_like :array_pack_integer, 'l' +end + +describe "Array#pack with format 'L'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_32bit_le, 'L<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_32bit_be, 'L>' + end + + platform_is c_long_size: 32 do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'L<_' + it_behaves_like :array_pack_32bit_le, 'L_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'L<!' + it_behaves_like :array_pack_32bit_le, 'L!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'L>_' + it_behaves_like :array_pack_32bit_be, 'L_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'L>!' + it_behaves_like :array_pack_32bit_be, 'L!>' + end + end + + platform_is c_long_size: 64 do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_64bit_le, 'L<_' + it_behaves_like :array_pack_64bit_le, 'L_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_64bit_le, 'L<!' + it_behaves_like :array_pack_64bit_le, 'L!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_64bit_be, 'L>_' + it_behaves_like :array_pack_64bit_be, 'L_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_64bit_be, 'L>!' + it_behaves_like :array_pack_64bit_be, 'L!>' + end + end +end + +describe "Array#pack with format 'l'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_32bit_le, 'l<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_32bit_be, 'l>' + end + + platform_is c_long_size: 32 do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_32bit_le, 'l<_' + it_behaves_like :array_pack_32bit_le, 'l_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_32bit_le, 'l<!' + it_behaves_like :array_pack_32bit_le, 'l!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_32bit_be, 'l>_' + it_behaves_like :array_pack_32bit_be, 'l_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_32bit_be, 'l>!' + it_behaves_like :array_pack_32bit_be, 'l!>' + end + end + + platform_is c_long_size: 64 do + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_64bit_le, 'l<_' + it_behaves_like :array_pack_64bit_le, 'l_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_64bit_le, 'l<!' + it_behaves_like :array_pack_64bit_le, 'l!<' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_64bit_be, 'l>_' + it_behaves_like :array_pack_64bit_be, 'l_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_64bit_be, 'l>!' + it_behaves_like :array_pack_64bit_be, 'l!>' + end + end +end + +little_endian do + describe "Array#pack with format 'L'" do + it_behaves_like :array_pack_32bit_le, 'L' + end + + describe "Array#pack with format 'l'" do + it_behaves_like :array_pack_32bit_le, 'l' + end + + platform_is c_long_size: 32 do + describe "Array#pack with format 'L' with modifier '_'" do + it_behaves_like :array_pack_32bit_le, 'L_' + end + + describe "Array#pack with format 'L' with modifier '!'" do + it_behaves_like :array_pack_32bit_le, 'L!' + end + + describe "Array#pack with format 'l' with modifier '_'" do + it_behaves_like :array_pack_32bit_le, 'l_' + end + + describe "Array#pack with format 'l' with modifier '!'" do + it_behaves_like :array_pack_32bit_le, 'l!' + end + end + + platform_is c_long_size: 64 do + describe "Array#pack with format 'L' with modifier '_'" do + it_behaves_like :array_pack_64bit_le, 'L_' + end + + describe "Array#pack with format 'L' with modifier '!'" do + it_behaves_like :array_pack_64bit_le, 'L!' + end + + describe "Array#pack with format 'l' with modifier '_'" do + it_behaves_like :array_pack_64bit_le, 'l_' + end + + describe "Array#pack with format 'l' with modifier '!'" do + it_behaves_like :array_pack_64bit_le, 'l!' + end + end +end + +big_endian do + describe "Array#pack with format 'L'" do + it_behaves_like :array_pack_32bit_be, 'L' + end + + describe "Array#pack with format 'l'" do + it_behaves_like :array_pack_32bit_be, 'l' + end + + platform_is c_long_size: 32 do + describe "Array#pack with format 'L' with modifier '_'" do + it_behaves_like :array_pack_32bit_be, 'L_' + end + + describe "Array#pack with format 'L' with modifier '!'" do + it_behaves_like :array_pack_32bit_be, 'L!' + end + + describe "Array#pack with format 'l' with modifier '_'" do + it_behaves_like :array_pack_32bit_be, 'l_' + end + + describe "Array#pack with format 'l' with modifier '!'" do + it_behaves_like :array_pack_32bit_be, 'l!' + end + end + + platform_is c_long_size: 64 do + describe "Array#pack with format 'L' with modifier '_'" do + it_behaves_like :array_pack_64bit_be, 'L_' + end + + describe "Array#pack with format 'L' with modifier '!'" do + it_behaves_like :array_pack_64bit_be, 'L!' + end + + describe "Array#pack with format 'l' with modifier '_'" do + it_behaves_like :array_pack_64bit_be, 'l_' + end + + describe "Array#pack with format 'l' with modifier '!'" do + it_behaves_like :array_pack_64bit_be, 'l!' + end + end +end diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb new file mode 100644 index 0000000000..a80f91275c --- /dev/null +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -0,0 +1,317 @@ +# encoding: binary +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 == "" + end + + it "encodes nil as an empty string" do + [nil].pack("M").should == "" + end + + it "appends a soft line break at the end of an encoded string" do + ["a"].pack("M").should == "a=\n" + end + + it "does not append a soft break if the string ends with a newline" do + ["a\n"].pack("M").should == "a\n" + end + + it "encodes one element for each directive" do + ["a", "b", "c"].pack("MM").should == "a=\nb=\n" + end + + it "encodes byte values 33..60 directly" do + [ [["!\"\#$%&'()*+,-./"], "!\"\#$%&'()*+,-./=\n"], + [["0123456789"], "0123456789=\n"], + [[":;<"], ":;<=\n"] + ].should be_computed_by(:pack, "M") + end + + it "encodes byte values 62..126 directly" do + [ [[">?@"], ">?@=\n"], + [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "ABCDEFGHIJKLMNOPQRSTUVWXYZ=\n"], + [["[\\]^_`"], "[\\]^_`=\n"], + [["abcdefghijklmnopqrstuvwxyz"], "abcdefghijklmnopqrstuvwxyz=\n"], + [["{|}~"], "{|}~=\n"] + ].should be_computed_by(:pack, "M") + end + + it "encodes an '=' character in hex format" do + ["="].pack("M").should == "=3D=\n" + end + + it "encodes an embedded space directly" do + ["a b"].pack("M").should == "a b=\n" + end + + it "encodes a space at the end of the string directly" do + ["a "].pack("M").should == "a =\n" + end + + it "encodes an embedded tab directly" do + ["a\tb"].pack("M").should == "a\tb=\n" + end + + it "encodes a tab at the end of the string directly" do + ["a\t"].pack("M").should == "a\t=\n" + end + + it "encodes an embedded newline directly" do + ["a\nb"].pack("M").should == "a\nb=\n" + end + + it "encodes 0..31 except tab and newline in hex format" do + [ [["\x00\x01\x02\x03\x04\x05\x06"], "=00=01=02=03=04=05=06=\n"], + [["\a\b\v\f\r"], "=07=08=0B=0C=0D=\n"], + [["\x0e\x0f\x10\x11\x12\x13\x14"], "=0E=0F=10=11=12=13=14=\n"], + [["\x15\x16\x17\x18\x19\x1a"], "=15=16=17=18=19=1A=\n"], + [["\e"], "=1B=\n"], + [["\x1c\x1d\x1e\x1f"], "=1C=1D=1E=1F=\n"] + ].should be_computed_by(:pack, "M") + end + + it "encodes a tab at the end of a line with an encoded newline" do + ["\t"].pack("M").should == "\t=\n" + ["\t\n"].pack("M").should == "\t=\n\n" + ["abc\t\nxyz"].pack("M").should == "abc\t=\n\nxyz=\n" + end + + it "encodes a space at the end of a line with an encoded newline" do + [" "].pack("M").should == " =\n" + [" \n"].pack("M").should == " =\n\n" + ["abc \nxyz"].pack("M").should == "abc =\n\nxyz=\n" + end + + it "encodes 127..255 in hex format" do + [ [["\x7f\x80\x81\x82\x83\x84\x85\x86"], "=7F=80=81=82=83=84=85=86=\n"], + [["\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"], "=87=88=89=8A=8B=8C=8D=8E=\n"], + [["\x8f\x90\x91\x92\x93\x94\x95\x96"], "=8F=90=91=92=93=94=95=96=\n"], + [["\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"], "=97=98=99=9A=9B=9C=9D=9E=\n"], + [["\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6"], "=9F=A0=A1=A2=A3=A4=A5=A6=\n"], + [["\xa7\xa8\xa9\xaa\xab\xac\xad\xae"], "=A7=A8=A9=AA=AB=AC=AD=AE=\n"], + [["\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"], "=AF=B0=B1=B2=B3=B4=B5=B6=\n"], + [["\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"], "=B7=B8=B9=BA=BB=BC=BD=BE=\n"], + [["\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6"], "=BF=C0=C1=C2=C3=C4=C5=C6=\n"], + [["\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"], "=C7=C8=C9=CA=CB=CC=CD=CE=\n"], + [["\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6"], "=CF=D0=D1=D2=D3=D4=D5=D6=\n"], + [["\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde"], "=D7=D8=D9=DA=DB=DC=DD=DE=\n"], + [["\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"], "=DF=E0=E1=E2=E3=E4=E5=E6=\n"], + [["\xe7\xe8\xe9\xea\xeb\xec\xed\xee"], "=E7=E8=E9=EA=EB=EC=ED=EE=\n"], + [["\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"], "=EF=F0=F1=F2=F3=F4=F5=F6=\n"], + [["\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"], "=F7=F8=F9=FA=FB=FC=FD=FE=\n"], + [["\xff"], "=FF=\n"] + ].should be_computed_by(:pack, "M") + end + + it "emits a soft line break when the output exceeds 72 characters when passed '*', 0, 1, or no count modifier" do + s1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + r1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=\n" + s2 = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19" + r2 = "=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=\n=19=\n" + s3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a" + r3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=15=\na=\n" + s4 = "\x15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a" + r4 = "=15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=15a=\n" + + [ [[s1], "M", r1], + [[s1], "M0", r1], + [[s1], "M1", r1], + [[s2], "M", r2], + [[s2], "M0", r2], + [[s2], "M1", r2], + [[s3], "M", r3], + [[s3], "M0", r3], + [[s3], "M1", r3], + [[s4], "M", r4], + [[s4], "M0", r4], + [[s4], "M1", r4] + ].should be_computed_by(:pack) + end + + it "emits a soft line break when the output exceeds count characters" do + [ [["abcdefghi"], "M2", "abc=\ndef=\nghi=\n"], + [["abcdefghi"], "M3", "abcd=\nefgh=\ni=\n"], + [["abcdefghi"], "M4", "abcde=\nfghi=\n"], + [["abcdefghi"], "M5", "abcdef=\nghi=\n"], + [["abcdefghi"], "M6", "abcdefg=\nhi=\n"], + [["\x19\x19\x19\x19"], "M2", "=19=\n=19=\n=19=\n=19=\n"], + [["\x19\x19\x19\x19"], "M3", "=19=19=\n=19=19=\n"], + [["\x19\x19\x19\x19"], "M4", "=19=19=\n=19=19=\n"], + [["\x19\x19\x19\x19"], "M5", "=19=19=\n=19=19=\n"], + [["\x19\x19\x19\x19"], "M6", "=19=19=19=\n=19=\n"], + [["\x19\x19\x19\x19"], "M7", "=19=19=19=\n=19=\n"] + ].should be_computed_by(:pack) + end + + it "encodes a recursive array" do + empty = ArraySpecs.empty_recursive_array + empty.pack('M').should be_an_instance_of(String) + + array = ArraySpecs.recursive_array + array.pack('M').should == "1=\n" + end + + it "calls #to_s to convert an object to a String" do + obj = mock("pack M string") + obj.should_receive(:to_s).and_return("packing") + + [obj].pack("M").should == "packing=\n" + end + + it "converts the object to a String representation if #to_s does not return a String" do + obj = mock("pack M non-string") + obj.should_receive(:to_s).and_return(2) + + [obj].pack("M").should be_an_instance_of(String) + end + + it "encodes a Symbol as a String" do + [:symbol].pack("M").should == "symbol=\n" + end + + it "encodes an Integer as a String" do + [ [[1], "1=\n"], + [[bignum_value], "#{bignum_value}=\n"] + ].should be_computed_by(:pack, "M") + end + + it "encodes a Float as a String" do + [1.0].pack("M").should == "1.0=\n" + end + + it "converts Floats to the minimum unique representation" do + [1.0 / 3.0].pack("M").should == "0.3333333333333333=\n" + end + + it "sets the output string to US-ASCII encoding" do + ["abcd"].pack("M").encoding.should == Encoding::US_ASCII + end +end + +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 == "" + end + + it "appends a newline to the end of the encoded string" do + ["a"].pack("m").should == "YQ==\n" + end + + it "encodes one element per directive" do + ["abc", "DEF"].pack("mm").should == "YWJj\nREVG\n" + end + + it "encodes 1, 2, or 3 characters in 4 output characters (Base64 encoding)" do + [ [["a"], "YQ==\n"], + [["ab"], "YWI=\n"], + [["abc"], "YWJj\n"], + [["abcd"], "YWJjZA==\n"], + [["abcde"], "YWJjZGU=\n"], + [["abcdef"], "YWJjZGVm\n"], + [["abcdefg"], "YWJjZGVmZw==\n"], + ].should be_computed_by(:pack, "m") + end + + it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do + ["abcdefg"].pack("m3").should == "YWJj\nZGVm\nZw==\n" + end + + it "implicitly has a count of 45 when passed '*', 1, 2 or no count modifier" do + s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\nYWFhYWE=\n" + [ [[s], "m", r], + [[s], "m*", r], + [[s], "m1", r], + [[s], "m2", r], + ].should be_computed_by(:pack) + end + + it "encodes all ascii characters" do + [ [["\x00\x01\x02\x03\x04\x05\x06"], "AAECAwQFBg==\n"], + [["\a\b\t\n\v\f\r"], "BwgJCgsMDQ==\n"], + [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], "Dg8QERITFBUW\n"], + [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], "FxgZGhscHR4f\n"], + [["!\"\#$%&'()*+,-./"], "ISIjJCUmJygpKissLS4v\n"], + [["0123456789"], "MDEyMzQ1Njc4OQ==\n"], + [[":;<=>?@"], "Ojs8PT4/QA==\n"], + [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=\n"], + [["[\\]^_`"], "W1xdXl9g\n"], + [["abcdefghijklmnopqrstuvwxyz"], "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n"], + [["{|}~"], "e3x9fg==\n"], + [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], "f8KAwoHCgsKD\n"], + [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], "woTChcKGwofC\n"], + [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], "iMKJworCi8KM\n"], + [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], "wo3CjsKPwpDC\n"], + [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], "kcKSwpPClMKV\n"], + [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], "wpbCl8KYwpnC\n"], + [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], "msKbwpzCncKe\n"], + [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], "wp/CoMKhwqLC\n"], + [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], "o8KkwqXCpsKn\n"], + [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], "wqjCqcKqwqvC\n"], + [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], "rMKtwq7Cr8Kw\n"], + [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], "wrHCssKzwrTC\n"], + [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], "tcK2wrfCuMK5\n"], + [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], "wrrCu8K8wr3C\n"], + [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], "vsK/w4DDgcOC\n"], + [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], "w4PDhMOFw4bD\n"], + [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], "h8OIw4nDisOL\n"], + [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], "w4zDjcOOw4/D\n"], + [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], "kMORw5LDk8OU\n"], + [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], "w5XDlsOXw5jD\n"], + [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], "mcOaw5vDnMOd\n"], + [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], "w57Dn8Ogw6HD\n"], + [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], "osOjw6TDpcOm\n"], + [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], "w6fDqMOpw6rD\n"], + [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], "q8Osw63DrsOv\n"], + [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], "w7DDscOyw7PD\n"], + [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], "tMO1w7bDt8O4\n"], + [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], "w7nDusO7w7zD\n"], + [["\xbd\xc3\xbe\xc3\xbf"], "vcO+w78=\n"] + ].should be_computed_by(:pack, "m") + end + + it "calls #to_str to convert an object to a String" do + obj = mock("pack m string") + obj.should_receive(:to_str).and_return("abc") + [obj].pack("m").should == "YWJj\n" + end + + it "raises a TypeError if #to_str does not return a String" do + obj = mock("pack m non-string") + -> { [obj].pack("m") }.should raise_error(TypeError) + end + + it "raises a TypeError if passed nil" do + -> { [nil].pack("m") }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Integer" do + -> { [0].pack("m") }.should raise_error(TypeError) + -> { [bignum_value].pack("m") }.should raise_error(TypeError) + end + + it "does not emit a newline if passed zero as the count modifier" do + s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=" + [s].pack("m0").should == r + end + + it "sets the output string to US-ASCII encoding" do + ["abcd"].pack("m").encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/ruby/core/array/pack/n_spec.rb b/spec/ruby/core/array/pack/n_spec.rb new file mode 100644 index 0000000000..ab9409fc1e --- /dev/null +++ b/spec/ruby/core/array/pack/n_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'N'" do + it_behaves_like :array_pack_basic, 'N' + it_behaves_like :array_pack_basic_non_float, 'N' + it_behaves_like :array_pack_arguments, 'N' + it_behaves_like :array_pack_numeric_basic, 'N' + it_behaves_like :array_pack_integer, 'N' + it_behaves_like :array_pack_no_platform, 'N' + it_behaves_like :array_pack_32bit_be, 'N' +end + +describe "Array#pack with format 'n'" do + it_behaves_like :array_pack_basic, 'n' + it_behaves_like :array_pack_basic_non_float, 'n' + it_behaves_like :array_pack_arguments, 'n' + it_behaves_like :array_pack_numeric_basic, 'n' + it_behaves_like :array_pack_integer, 'n' + it_behaves_like :array_pack_no_platform, 'n' + it_behaves_like :array_pack_16bit_be, 'n' +end diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb new file mode 100644 index 0000000000..b023bf9110 --- /dev/null +++ b/spec/ruby/core/array/pack/p_spec.rb @@ -0,0 +1,38 @@ +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 "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 "with nil gives a null pointer" do + [nil].pack("p").unpack("J").should == [0] + end +end diff --git a/spec/ruby/core/array/pack/percent_spec.rb b/spec/ruby/core/array/pack/percent_spec.rb new file mode 100644 index 0000000000..5d56dea5fe --- /dev/null +++ b/spec/ruby/core/array/pack/percent_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' + +describe "Array#pack with format '%'" do + it "raises an Argument Error" do + -> { [1].pack("%") }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/array/pack/q_spec.rb b/spec/ruby/core/array/pack/q_spec.rb new file mode 100644 index 0000000000..bd6b2a4b71 --- /dev/null +++ b/spec/ruby/core/array/pack/q_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'Q'" do + it_behaves_like :array_pack_basic, 'Q' + it_behaves_like :array_pack_basic_non_float, 'Q' + it_behaves_like :array_pack_arguments, 'Q' + it_behaves_like :array_pack_numeric_basic, 'Q' + it_behaves_like :array_pack_integer, 'Q' +end + +describe "Array#pack with format 'q'" do + it_behaves_like :array_pack_basic, 'q' + it_behaves_like :array_pack_basic_non_float, 'q' + it_behaves_like :array_pack_arguments, 'q' + it_behaves_like :array_pack_numeric_basic, 'q' + it_behaves_like :array_pack_integer, 'q' +end + +describe "Array#pack with format 'Q'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_64bit_le, 'Q<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_64bit_be, 'Q>' + end +end + +describe "Array#pack with format 'q'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_64bit_le, 'q<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_64bit_be, 'q>' + end +end + +little_endian do + describe "Array#pack with format 'Q'" do + it_behaves_like :array_pack_64bit_le, 'Q' + end + + describe "Array#pack with format 'q'" do + it_behaves_like :array_pack_64bit_le, 'q' + end +end + +big_endian do + describe "Array#pack with format 'Q'" do + it_behaves_like :array_pack_64bit_be, 'Q' + end + + describe "Array#pack with format 'q'" do + it_behaves_like :array_pack_64bit_be, 'q' + end +end diff --git a/spec/ruby/core/array/pack/s_spec.rb b/spec/ruby/core/array/pack/s_spec.rb new file mode 100644 index 0000000000..4212d6a0b1 --- /dev/null +++ b/spec/ruby/core/array/pack/s_spec.rb @@ -0,0 +1,133 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'S'" do + it_behaves_like :array_pack_basic, 'S' + it_behaves_like :array_pack_basic_non_float, 'S' + it_behaves_like :array_pack_arguments, 'S' + it_behaves_like :array_pack_numeric_basic, 'S' + it_behaves_like :array_pack_integer, 'S' +end + +describe "Array#pack with format 's'" do + it_behaves_like :array_pack_basic, 's' + it_behaves_like :array_pack_basic_non_float, 's' + it_behaves_like :array_pack_arguments, 's' + it_behaves_like :array_pack_numeric_basic, 's' + it_behaves_like :array_pack_integer, 's' +end + +describe "Array#pack with format 'S'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_16bit_le, 'S<' + end + + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_16bit_le, 'S<_' + it_behaves_like :array_pack_16bit_le, 'S_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_16bit_le, 'S<!' + it_behaves_like :array_pack_16bit_le, 'S!<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_16bit_be, 'S>' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_16bit_be, 'S>_' + it_behaves_like :array_pack_16bit_be, 'S_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_16bit_be, 'S>!' + it_behaves_like :array_pack_16bit_be, 'S!>' + end +end + +describe "Array#pack with format 's'" do + describe "with modifier '<'" do + it_behaves_like :array_pack_16bit_le, 's<' + end + + describe "with modifier '<' and '_'" do + it_behaves_like :array_pack_16bit_le, 's<_' + it_behaves_like :array_pack_16bit_le, 's_<' + end + + describe "with modifier '<' and '!'" do + it_behaves_like :array_pack_16bit_le, 's<!' + it_behaves_like :array_pack_16bit_le, 's!<' + end + + describe "with modifier '>'" do + it_behaves_like :array_pack_16bit_be, 's>' + end + + describe "with modifier '>' and '_'" do + it_behaves_like :array_pack_16bit_be, 's>_' + it_behaves_like :array_pack_16bit_be, 's_>' + end + + describe "with modifier '>' and '!'" do + it_behaves_like :array_pack_16bit_be, 's>!' + it_behaves_like :array_pack_16bit_be, 's!>' + end +end + +little_endian do + describe "Array#pack with format 'S'" do + it_behaves_like :array_pack_16bit_le, 'S' + end + + describe "Array#pack with format 'S' with modifier '_'" do + it_behaves_like :array_pack_16bit_le, 'S_' + end + + describe "Array#pack with format 'S' with modifier '!'" do + it_behaves_like :array_pack_16bit_le, 'S!' + end + + describe "Array#pack with format 's'" do + it_behaves_like :array_pack_16bit_le, 's' + end + + describe "Array#pack with format 's' with modifier '_'" do + it_behaves_like :array_pack_16bit_le, 's_' + end + + describe "Array#pack with format 's' with modifier '!'" do + it_behaves_like :array_pack_16bit_le, 's!' + end +end + +big_endian do + describe "Array#pack with format 'S'" do + it_behaves_like :array_pack_16bit_be, 'S' + end + + describe "Array#pack with format 'S' with modifier '_'" do + it_behaves_like :array_pack_16bit_be, 'S_' + end + + describe "Array#pack with format 'S' with modifier '!'" do + it_behaves_like :array_pack_16bit_be, 'S!' + end + + describe "Array#pack with format 's'" do + it_behaves_like :array_pack_16bit_be, 's' + end + + describe "Array#pack with format 's' with modifier '_'" do + it_behaves_like :array_pack_16bit_be, 's_' + end + + describe "Array#pack with format 's' with modifier '!'" do + it_behaves_like :array_pack_16bit_be, 's!' + end +end diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb new file mode 100644 index 0000000000..a63f64d296 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -0,0 +1,84 @@ +describe :array_pack_arguments, shared: true do + it "raises an ArgumentError if there are fewer elements than the format requires" do + -> { [].pack(pack_format(1)) }.should raise_error(ArgumentError) + end +end + +describe :array_pack_basic, shared: true do + before :each do + @obj = ArraySpecs.universal_pack_object + end + + it "raises a TypeError when passed nil" do + -> { [@obj].pack(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed an Integer" do + -> { [@obj].pack(1) }.should raise_error(TypeError) + end +end + +describe :array_pack_basic_non_float, shared: true do + before :each do + @obj = ArraySpecs.universal_pack_object + end + + it "ignores whitespace in the format string" do + [@obj, @obj].pack("a \t\n\v\f\r"+pack_format).should be_an_instance_of(String) + end + + it "ignores comments in the format string" do + # 2 additional directives ('a') are required for the X directive + [@obj, @obj, @obj, @obj].pack("aa #{pack_format} # some comment \n#{pack_format}").should be_an_instance_of(String) + end + + ruby_version_is ""..."3.3" do + it "warns that a directive is unknown" do + # additional directive ('a') is required for the X directive + -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K' in 'a K#{pack_format}'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0' in 'a 0#{pack_format}'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':' in 'a :#{pack_format}'/) + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError when a directive is unknown" do + # additional directive ('a') is required for the X directive + -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) + end + end + + it "calls #to_str to coerce the directives string" do + d = mock("pack directive") + d.should_receive(:to_str).and_return("x"+pack_format) + [@obj, @obj].pack(d).should be_an_instance_of(String) + end +end + +describe :array_pack_basic_float, shared: true do + it "ignores whitespace in the format string" do + [9.3, 4.7].pack(" \t\n\v\f\r"+pack_format).should be_an_instance_of(String) + end + + it "ignores comments in the format string" do + [9.3, 4.7].pack(pack_format + "# some comment \n" + pack_format).should be_an_instance_of(String) + end + + it "calls #to_str to coerce the directives string" do + d = mock("pack directive") + d.should_receive(:to_str).and_return("x"+pack_format) + [1.2, 4.7].pack(d).should be_an_instance_of(String) + end +end + +describe :array_pack_no_platform, shared: true do + it "raises ArgumentError when the format modifier is '_'" do + ->{ [1].pack(pack_format("_")) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the format modifier is '!'" do + ->{ [1].pack(pack_format("!")) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/array/pack/shared/encodings.rb b/spec/ruby/core/array/pack/shared/encodings.rb new file mode 100644 index 0000000000..6b7ffac764 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/encodings.rb @@ -0,0 +1,16 @@ +describe :array_pack_hex, shared: true do + it "encodes no bytes when passed zero as the count modifier" do + ["abc"].pack(pack_format(0)).should == "" + end + + it "raises a TypeError if the object does not respond to #to_str" do + obj = mock("pack hex non-string") + -> { [obj].pack(pack_format) }.should raise_error(TypeError) + end + + it "raises a TypeError if #to_str does not return a String" do + obj = mock("pack hex non-string") + obj.should_receive(:to_str).and_return(1) + -> { [obj].pack(pack_format) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb new file mode 100644 index 0000000000..76c800b74d --- /dev/null +++ b/spec/ruby/core/array/pack/shared/float.rb @@ -0,0 +1,295 @@ +# encoding: binary + +describe :array_pack_float_le, shared: true do + it "encodes a positive Float" do + [1.42].pack(pack_format).should == "\x8f\xc2\xb5?" + end + + it "encodes a negative Float" do + [-34.2].pack(pack_format).should == "\xcd\xcc\x08\xc2" + end + + it "converts an Integer to a Float" do + [8].pack(pack_format).should == "\x00\x00\x00A" + end + + it "raises a TypeError if passed a String representation of a floating point number" do + -> { ["13"].pack(pack_format) }.should raise_error(TypeError) + end + + it "encodes the number of array elements specified by the count modifier" do + [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "\x9a\x999@33\xb3?" + end + + it "encodes all remaining elements when passed the '*' modifier" do + [2.9, 1.4, 8.2].pack(pack_format("*")).should == "\x9a\x999@33\xb3?33\x03A" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "\x9a\x99\xa9@33\x13A" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [5.3, 9.2].pack(pack_format(" ", 2)).should == "\x9a\x99\xa9@33\x13A" + end + + it "encodes positive Infinity" do + [infinity_value].pack(pack_format).should == "\x00\x00\x80\x7f" + end + + it "encodes negative Infinity" do + [-infinity_value].pack(pack_format).should == "\x00\x00\x80\xff" + end + + it "encodes NaN" do + nans = ["\x00\x00\xc0\xff", "\x00\x00\xc0\x7f", "\xFF\xFF\xFF\x7F"] + nans.should include([nan_value].pack(pack_format)) + end + + it "encodes a positive Float outside the range of a single precision float" do + [1e150].pack(pack_format).should == "\x00\x00\x80\x7f" + end + + it "encodes a negative Float outside the range of a single precision float" do + [-1e150].pack(pack_format).should == "\x00\x00\x80\xff" + end + + it "encodes a bignum as a float" do + [2 ** 65].pack(pack_format).should == [(2 ** 65).to_f].pack(pack_format) + end + + it "encodes a rational as a float" do + [Rational(3, 4)].pack(pack_format).should == [Rational(3, 4).to_f].pack(pack_format) + end +end + +describe :array_pack_float_be, shared: true do + it "encodes a positive Float" do + [1.42].pack(pack_format).should == "?\xb5\xc2\x8f" + end + + it "encodes a negative Float" do + [-34.2].pack(pack_format).should == "\xc2\x08\xcc\xcd" + end + + it "converts an Integer to a Float" do + [8].pack(pack_format).should == "A\x00\x00\x00" + [bignum_value].pack(pack_format).should == "_\x80\x00\x00" + end + + it "converts a Rational to a Float" do + [Rational(8)].pack(pack_format).should == "A\x00\x00\x00" + end + + it "raises a TypeError if passed a String representation of a floating point number" do + -> { ["13"].pack(pack_format) }.should raise_error(TypeError) + end + + it "encodes the number of array elements specified by the count modifier" do + [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@9\x99\x9a?\xb333" + end + + it "encodes all remaining elements when passed the '*' modifier" do + [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@9\x99\x9a?\xb333A\x0333" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\xa9\x99\x9aA\x1333" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\xa9\x99\x9aA\x1333" + end + + it "encodes positive Infinity" do + [infinity_value].pack(pack_format).should == "\x7f\x80\x00\x00" + end + + it "encodes negative Infinity" do + [-infinity_value].pack(pack_format).should == "\xff\x80\x00\x00" + end + + it "encodes NaN" do + nans = ["\xff\xc0\x00\x00", "\x7f\xc0\x00\x00", "\x7F\xFF\xFF\xFF"] + nans.should include([nan_value].pack(pack_format)) + end + + it "encodes a positive Float outside the range of a single precision float" do + [1e150].pack(pack_format).should == "\x7f\x80\x00\x00" + end + + it "encodes a negative Float outside the range of a single precision float" do + [-1e150].pack(pack_format).should == "\xff\x80\x00\x00" + end +end + +describe :array_pack_double_le, shared: true do + it "encodes a positive Float" do + [1.42].pack(pack_format).should == "\xb8\x1e\x85\xebQ\xb8\xf6?" + end + + it "encodes a negative Float" do + [-34.2].pack(pack_format).should == "\x9a\x99\x99\x99\x99\x19A\xc0" + end + + it "converts an Integer to a Float" do + [8].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\x20@" + [bignum_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xF0C" + end + + it "converts a Rational to a Float" do + [Rational(8)].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00 @" + end + + it "raises a TypeError if passed a String representation of a floating point number" do + -> { ["13"].pack(pack_format) }.should raise_error(TypeError) + end + + it "encodes the number of array elements specified by the count modifier" do + [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "333333\x07@ffffff\xf6?" + end + + it "encodes all remaining elements when passed the '*' modifier" do + [2.9, 1.4, 8.2].pack(pack_format("*")).should == "333333\x07@ffffff\xf6?ffffff\x20@" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "333333\x15@ffffff\x22@" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [5.3, 9.2].pack(pack_format(" ", 2)).should == "333333\x15@ffffff\x22@" + end + + it "encodes positive Infinity" do + [infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\x7f" + end + + it "encodes negative Infinity" do + [-infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\xff" + end + + it "encodes NaN" do + nans = [ + "\x00\x00\x00\x00\x00\x00\xf8\xff", + "\x00\x00\x00\x00\x00\x00\xf8\x7f", + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" + ] + nans.should include([nan_value].pack(pack_format)) + end + + it "encodes a positive Float outside the range of a single precision float" do + [1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13_" + end + + it "encodes a negative Float outside the range of a single precision float" do + [-1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13\xdf" + end +end + +describe :array_pack_double_be, shared: true do + it "encodes a positive Float" do + [1.42].pack(pack_format).should == "?\xf6\xb8Q\xeb\x85\x1e\xb8" + end + + it "encodes a negative Float" do + [-34.2].pack(pack_format).should == "\xc0A\x19\x99\x99\x99\x99\x9a" + end + + it "converts an Integer to a Float" do + [8].pack(pack_format).should == "@\x20\x00\x00\x00\x00\x00\x00" + end + + it "raises a TypeError if passed a String representation of a floating point number" do + -> { ["13"].pack(pack_format) }.should raise_error(TypeError) + end + + it "encodes the number of array elements specified by the count modifier" do + [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@\x07333333?\xf6ffffff" + end + + it "encodes all remaining elements when passed the '*' modifier" do + [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@\x07333333?\xf6ffffff@\x20ffffff" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\x15333333@\x22ffffff" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\x15333333@\x22ffffff" + end + + it "encodes positive Infinity" do + [infinity_value].pack(pack_format).should == "\x7f\xf0\x00\x00\x00\x00\x00\x00" + end + + it "encodes negative Infinity" do + [-infinity_value].pack(pack_format).should == "\xff\xf0\x00\x00\x00\x00\x00\x00" + end + + it "encodes NaN" do + nans = [ + "\xff\xf8\x00\x00\x00\x00\x00\x00", + "\x7f\xf8\x00\x00\x00\x00\x00\x00", + "\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + ] + nans.should include([nan_value].pack(pack_format)) + end + + it "encodes a positive Float outside the range of a single precision float" do + [1e150].pack(pack_format).should == "_\x13\x8d5\x2eP\x96\xaf" + end + + it "encodes a negative Float outside the range of a single precision float" do + [-1e150].pack(pack_format).should == "\xdf\x13\x8d5\x2eP\x96\xaf" + end +end diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb new file mode 100644 index 0000000000..61f7cca184 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/integer.rb @@ -0,0 +1,453 @@ +# encoding: binary + +describe :array_pack_16bit_le, shared: true do + it "encodes the least significant 16 bits of a positive number" do + [ [[0x0000_0021], "\x21\x00"], + [[0x0000_4321], "\x21\x43"], + [[0x0065_4321], "\x21\x43"], + [[0x7865_4321], "\x21\x43"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 16 bits of a negative number" do + [ [[-0x0000_0021], "\xdf\xff"], + [[-0x0000_4321], "\xdf\xbc"], + [[-0x0065_4321], "\xdf\xbc"], + [[-0x7865_4321], "\xdf\xbc"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[2019902241.2], "\x21\x43"], + [[2019902241.8], "\x21\x43"], + [[-2019902241.2], "\xdf\xbc"], + [[-2019902241.8], "\xdf\xbc"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678) + [obj].pack(pack_format()).should == "\x78\x56" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x78\x65\xcd\xab" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x78\x65\xcd\xab\x21\x43" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x78\x65\xcd\xab" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2)) + str.should == "\x78\x65\xcd\xab" + end +end + +describe :array_pack_16bit_be, shared: true do + it "encodes the least significant 16 bits of a positive number" do + [ [[0x0000_0021], "\x00\x21"], + [[0x0000_4321], "\x43\x21"], + [[0x0065_4321], "\x43\x21"], + [[0x7865_4321], "\x43\x21"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 16 bits of a negative number" do + [ [[-0x0000_0021], "\xff\xdf"], + [[-0x0000_4321], "\xbc\xdf"], + [[-0x0065_4321], "\xbc\xdf"], + [[-0x7865_4321], "\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[2019902241.2], "\x43\x21"], + [[2019902241.8], "\x43\x21"], + [[-2019902241.2], "\xbc\xdf"], + [[-2019902241.8], "\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678) + [obj].pack(pack_format()).should == "\x56\x78" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x65\x78\xab\xcd" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x65\x78\xab\xcd\x43\x21" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x65\x78\xab\xcd" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2)) + str.should == "\x65\x78\xab\xcd" + end +end + +describe :array_pack_32bit_le, shared: true do + it "encodes the least significant 32 bits of a positive number" do + [ [[0x0000_0021], "\x21\x00\x00\x00"], + [[0x0000_4321], "\x21\x43\x00\x00"], + [[0x0065_4321], "\x21\x43\x65\x00"], + [[0x7865_4321], "\x21\x43\x65\x78"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 32 bits of a negative number" do + [ [[-0x0000_0021], "\xdf\xff\xff\xff"], + [[-0x0000_4321], "\xdf\xbc\xff\xff"], + [[-0x0065_4321], "\xdf\xbc\x9a\xff"], + [[-0x7865_4321], "\xdf\xbc\x9a\x87"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[2019902241.2], "\x21\x43\x65\x78"], + [[2019902241.8], "\x21\x43\x65\x78"], + [[-2019902241.2], "\xdf\xbc\x9a\x87"], + [[-2019902241.8], "\xdf\xbc\x9a\x87"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678) + [obj].pack(pack_format()).should == "\x78\x56\x34\x12" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2)) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + end +end + +describe :array_pack_32bit_be, shared: true do + it "encodes the least significant 32 bits of a positive number" do + [ [[0x0000_0021], "\x00\x00\x00\x21"], + [[0x0000_4321], "\x00\x00\x43\x21"], + [[0x0065_4321], "\x00\x65\x43\x21"], + [[0x7865_4321], "\x78\x65\x43\x21"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 32 bits of a negative number" do + [ [[-0x0000_0021], "\xff\xff\xff\xdf"], + [[-0x0000_4321], "\xff\xff\xbc\xdf"], + [[-0x0065_4321], "\xff\x9a\xbc\xdf"], + [[-0x7865_4321], "\x87\x9a\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[2019902241.2], "\x78\x65\x43\x21"], + [[2019902241.8], "\x78\x65\x43\x21"], + [[-2019902241.2], "\x87\x9a\xbc\xdf"], + [[-2019902241.8], "\x87\x9a\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678) + [obj].pack(pack_format()).should == "\x12\x34\x56\x78" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2)) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + end +end + +describe :array_pack_32bit_le_platform, shared: true do + it "encodes the least significant 32 bits of a number" do + [ [[0x7865_4321], "\x21\x43\x65\x78"], + [[-0x7865_4321], "\xdf\xbc\x9a\x87"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78" + end + + platform_is c_long_size: 64 do + it "encodes the least significant 32 bits of a number that is greater than 32 bits" do + [ [[0xff_7865_4321], "\x21\x43\x65\x78"], + [[-0xff_7865_4321], "\xdf\xbc\x9a\x87"] + ].should be_computed_by(:pack, pack_format()) + end + end +end + +describe :array_pack_32bit_be_platform, shared: true do + it "encodes the least significant 32 bits of a number" do + [ [[0x7865_4321], "\x78\x65\x43\x21"], + [[-0x7865_4321], "\x87\x9a\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2)) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*')) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21" + end + + platform_is c_long_size: 64 do + it "encodes the least significant 32 bits of a number that is greater than 32 bits" do + [ [[0xff_7865_4321], "\x78\x65\x43\x21"], + [[-0xff_7865_4321], "\x87\x9a\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + end +end + +describe :array_pack_64bit_le, shared: true do + it "encodes the least significant 64 bits of a positive number" do + [ [[0x0000_0000_0000_0021], "\x21\x00\x00\x00\x00\x00\x00\x00"], + [[0x0000_0000_0000_4321], "\x21\x43\x00\x00\x00\x00\x00\x00"], + [[0x0000_0000_0065_4321], "\x21\x43\x65\x00\x00\x00\x00\x00"], + [[0x0000_0000_7865_4321], "\x21\x43\x65\x78\x00\x00\x00\x00"], + [[0x0000_0090_7865_4321], "\x21\x43\x65\x78\x90\x00\x00\x00"], + [[0x0000_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\x00\x00"], + [[0x00dc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x00"], + [[0x7edc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x7e"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 64 bits of a negative number" do + [ [[-0x0000_0000_0000_0021], "\xdf\xff\xff\xff\xff\xff\xff\xff"], + [[-0x0000_0000_0000_4321], "\xdf\xbc\xff\xff\xff\xff\xff\xff"], + [[-0x0000_0000_0065_4321], "\xdf\xbc\x9a\xff\xff\xff\xff\xff"], + [[-0x0000_0000_7865_4321], "\xdf\xbc\x9a\x87\xff\xff\xff\xff"], + [[-0x0000_0090_7865_4321], "\xdf\xbc\x9a\x87\x6f\xff\xff\xff"], + [[-0x0000_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\xff\xff"], + [[-0x00dc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\xff"], + [[-0x7edc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\x81"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[9.14138647331322368e+18], "\x00\x44\x65\x78\x90\xba\xdc\x7e"], + [[-9.14138647331322368e+18], "\x00\xbc\x9a\x87\x6f\x45\x23\x81"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef) + [obj].pack(pack_format()).should == "\xef\xcd\xab\x90\x78\x56\x34\x12" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1234_5678_90ab_cdef, + 0xdef0_abcd_3412_7856, + 0x7865_4321_dcba_def0].pack(pack_format(2)) + str.should == "\xef\xcd\xab\x90\x78\x56\x34\x12\x56\x78\x12\x34\xcd\xab\xf0\xde" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*')) + str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2)) + str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" + end +end + +describe :array_pack_64bit_be, shared: true do + it "encodes the least significant 64 bits of a positive number" do + [ [[0x0000_0000_0000_0021], "\x00\x00\x00\x00\x00\x00\x00\x21"], + [[0x0000_0000_0000_4321], "\x00\x00\x00\x00\x00\x00\x43\x21"], + [[0x0000_0000_0065_4321], "\x00\x00\x00\x00\x00\x65\x43\x21"], + [[0x0000_0000_7865_4321], "\x00\x00\x00\x00\x78\x65\x43\x21"], + [[0x0000_0090_7865_4321], "\x00\x00\x00\x90\x78\x65\x43\x21"], + [[0x0000_ba90_7865_4321], "\x00\x00\xba\x90\x78\x65\x43\x21"], + [[0x00dc_ba90_7865_4321], "\x00\xdc\xba\x90\x78\x65\x43\x21"], + [[0x7edc_ba90_7865_4321], "\x7e\xdc\xba\x90\x78\x65\x43\x21"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes the least significant 64 bits of a negative number" do + [ [[-0x0000_0000_0000_0021], "\xff\xff\xff\xff\xff\xff\xff\xdf"], + [[-0x0000_0000_0000_4321], "\xff\xff\xff\xff\xff\xff\xbc\xdf"], + [[-0x0000_0000_0065_4321], "\xff\xff\xff\xff\xff\x9a\xbc\xdf"], + [[-0x0000_0000_7865_4321], "\xff\xff\xff\xff\x87\x9a\xbc\xdf"], + [[-0x0000_0090_7865_4321], "\xff\xff\xff\x6f\x87\x9a\xbc\xdf"], + [[-0x0000_ba90_7865_4321], "\xff\xff\x45\x6f\x87\x9a\xbc\xdf"], + [[-0x00dc_ba90_7865_4321], "\xff\x23\x45\x6f\x87\x9a\xbc\xdf"], + [[-0x7edc_ba90_7865_4321], "\x81\x23\x45\x6f\x87\x9a\xbc\xdf"] + ].should be_computed_by(:pack, pack_format()) + end + + it "encodes a Float truncated as an Integer" do + [ [[9.14138647331322368e+18], "\x7e\xdc\xba\x90\x78\x65\x44\x00"], + [[-9.14138647331322368e+18], "\x81\x23\x45\x6f\x87\x9a\xbc\x00"] + ].should be_computed_by(:pack, pack_format()) + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef) + [obj].pack(pack_format()).should == "\x12\x34\x56\x78\x90\xab\xcd\xef" + end + + it "encodes the number of array elements specified by the count modifier" do + str = [0x1234_5678_90ab_cdef, + 0xdef0_abcd_3412_7856, + 0x7865_4321_dcba_def0].pack(pack_format(2)) + str.should == "\x12\x34\x56\x78\x90\xab\xcd\xef\xde\xf0\xab\xcd\x34\x12\x78\x56" + end + + it "encodes all remaining elements when passed the '*' modifier" do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*')) + str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2)) + str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" + end +end diff --git a/spec/ruby/core/array/pack/shared/numeric_basic.rb b/spec/ruby/core/array/pack/shared/numeric_basic.rb new file mode 100644 index 0000000000..545e215e64 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/numeric_basic.rb @@ -0,0 +1,50 @@ +describe :array_pack_numeric_basic, shared: true do + it "returns an empty String if count is zero" do + [1].pack(pack_format(0)).should == "" + end + + it "raises a TypeError when passed nil" do + -> { [nil].pack(pack_format) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed true" do + -> { [true].pack(pack_format) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed false" do + -> { [false].pack(pack_format) }.should raise_error(TypeError) + end + + it "returns a binary string" do + [0xFF].pack(pack_format).encoding.should == Encoding::BINARY + [0xE3, 0x81, 0x82].pack(pack_format(3)).encoding.should == Encoding::BINARY + end +end + +describe :array_pack_integer, shared: true do + it "raises a TypeError when the object does not respond to #to_int" do + obj = mock('not an integer') + -> { [obj].pack(pack_format) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed a String" do + -> { ["5"].pack(pack_format) }.should raise_error(TypeError) + end +end + +describe :array_pack_float, shared: true do + it "raises a TypeError if a String does not represent a floating point number" do + -> { ["a"].pack(pack_format) }.should raise_error(TypeError) + end + + it "raises a TypeError when the object is not Numeric" do + obj = Object.new + -> { [obj].pack(pack_format) }.should raise_error(TypeError, /can't convert Object into Float/) + end + + it "raises a TypeError when the Numeric object does not respond to #to_f" do + klass = Class.new(Numeric) + obj = klass.new + -> { [obj].pack(pack_format) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb new file mode 100644 index 0000000000..805f78b53b --- /dev/null +++ b/spec/ruby/core/array/pack/shared/string.rb @@ -0,0 +1,48 @@ +# encoding: binary +describe :array_pack_string, shared: true do + it "adds count bytes of a String to the output" do + ["abc"].pack(pack_format(2)).should == "ab" + end + + it "implicitly has a count of one when no count is specified" do + ["abc"].pack(pack_format).should == "a" + end + + it "does not add any bytes when the count is zero" do + ["abc"].pack(pack_format(0)).should == "" + end + + it "is not affected by a previous count modifier" do + ["abcde", "defg"].pack(pack_format(3)+pack_format).should == "abcd" + end + + it "raises an ArgumentError when the Array is empty" do + -> { [].pack(pack_format) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when the Array has too few elements" do + -> { ["a"].pack(pack_format(nil, 2)) }.should raise_error(ArgumentError) + end + + it "calls #to_str to convert the element to a String" do + obj = mock('pack string') + obj.should_receive(:to_str).and_return("abc") + + [obj].pack(pack_format).should == "a" + end + + it "raises a TypeError when the object does not respond to #to_str" do + obj = mock("not a string") + -> { [obj].pack(pack_format) }.should raise_error(TypeError) + 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::BINARY], + [["abcde\xd1", "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], + [["a".dup.force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], + # under discussion [ruby-dev:37294] + [["\u{3042 3044 3046 3048}", 1].pack(f+"N"), Encoding::BINARY] + ].should be_computed_by(:encoding) + end +end 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..2c2b011c34 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/taint.rb @@ -0,0 +1,2 @@ +describe :array_pack_taint, shared: true do +end diff --git a/spec/ruby/core/array/pack/shared/unicode.rb b/spec/ruby/core/array/pack/shared/unicode.rb new file mode 100644 index 0000000000..4d8eaef323 --- /dev/null +++ b/spec/ruby/core/array/pack/shared/unicode.rb @@ -0,0 +1,106 @@ +# -*- encoding: utf-8 -*- + +describe :array_pack_unicode, shared: true do + it "encodes ASCII values as a Unicode codepoint" do + [ [[0], "\x00"], + [[1], "\x01"], + [[8], "\x08"], + [[15], "\x0f"], + [[24], "\x18"], + [[31], "\x1f"], + [[127], "\x7f"], + [[128], "\xc2\x80"], + [[129], "\xc2\x81"], + [[255], "\xc3\xbf"] + ].should be_computed_by(:pack, "U") + end + + it "encodes UTF-8 BMP codepoints" do + [ [[0x80], "\xc2\x80"], + [[0x7ff], "\xdf\xbf"], + [[0x800], "\xe0\xa0\x80"], + [[0xffff], "\xef\xbf\xbf"] + ].should be_computed_by(:pack, "U") + end + + it "constructs strings with valid encodings" do + str = [0x85].pack("U*") + str.should == "\xc2\x85" + str.valid_encoding?.should be_true + end + + it "encodes values larger than UTF-8 max codepoints" do + [ + [[0x00110000], [244, 144, 128, 128].pack('C*').force_encoding('utf-8')], + [[0x04000000], [252, 132, 128, 128, 128, 128].pack('C*').force_encoding('utf-8')], + [[0x7FFFFFFF], [253, 191, 191, 191, 191, 191].pack('C*').force_encoding('utf-8')] + ].should be_computed_by(:pack, "U") + end + + it "encodes UTF-8 max codepoints" do + [ [[0x10000], "\xf0\x90\x80\x80"], + [[0xfffff], "\xf3\xbf\xbf\xbf"], + [[0x100000], "\xf4\x80\x80\x80"], + [[0x10ffff], "\xf4\x8f\xbf\xbf"] + ].should be_computed_by(:pack, "U") + end + + it "encodes the number of array elements specified by the count modifier" do + [ [[0x41, 0x42, 0x43, 0x44], "U2", "\x41\x42"], + [[0x41, 0x42, 0x43, 0x44], "U2U", "\x41\x42\x43"] + ].should be_computed_by(:pack) + end + + it "encodes all remaining elements when passed the '*' modifier" do + [0x41, 0x42, 0x43, 0x44].pack("U*").should == "\x41\x42\x43\x44" + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + [obj].pack("U").should == "\x05" + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return("5") + -> { [obj].pack("U") }.should raise_error(TypeError) + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [1, 2, 3].pack("U\x00U").should == "\x01\x02" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack("U\x00U") + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [1, 2, 3].pack("U U").should == "\x01\x02" + end + + it "raises a RangeError if passed a negative number" do + -> { [-1].pack("U") }.should raise_error(RangeError) + end + + it "raises a RangeError if passed a number larger than an unsigned 32-bit integer" do + -> { [2**32].pack("U") }.should raise_error(RangeError) + end + + it "sets the output string to UTF-8 encoding" do + [ [[0x00].pack("U"), Encoding::UTF_8], + [[0x41].pack("U"), Encoding::UTF_8], + [[0x7F].pack("U"), Encoding::UTF_8], + [[0x80].pack("U"), Encoding::UTF_8], + [[0x10FFFF].pack("U"), Encoding::UTF_8] + ].should be_computed_by(:encoding) + end +end diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb new file mode 100644 index 0000000000..1f84095ac4 --- /dev/null +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -0,0 +1,140 @@ +# encoding: binary +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' + it_behaves_like :array_pack_basic_non_float, 'U' + it_behaves_like :array_pack_arguments, 'U' + it_behaves_like :array_pack_unicode, 'U' +end + +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 "calls #to_str to convert an Object to a String" do + obj = mock("pack u string") + obj.should_receive(:to_str).and_return("``abcdef") + [obj].pack("u*").should == "(8&!A8F-D968`\n" + end + + it "will not implicitly convert a number to a string" do + -> { [0].pack('u') }.should raise_error(TypeError) + end + + it "encodes an empty string as an empty string" do + [""].pack("u").should == "" + end + + it "appends a newline to the end of the encoded string" do + ["a"].pack("u").should == "!80``\n" + end + + it "encodes one element per directive" do + ["abc", "DEF"].pack("uu").should == "#86)C\n#1$5&\n" + end + + it "prepends the length of each segment of the input string as the first character (+32) in each line of the output" do + ["abcdefghijklm"].pack("u7").should == "&86)C9&5F\n&9VAI:FML\n!;0``\n" + end + + it "encodes 1, 2, or 3 characters in 4 output characters (uuencoding)" do + [ [["a"], "!80``\n"], + [["ab"], "\"86(`\n"], + [["abc"], "#86)C\n"], + [["abcd"], "$86)C9```\n"], + [["abcde"], "%86)C9&4`\n"], + [["abcdef"], "&86)C9&5F\n"], + [["abcdefg"], "'86)C9&5F9P``\n"], + ].should be_computed_by(:pack, "u") + end + + it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do + ["abcdefg"].pack("u3").should == "#86)C\n#9&5F\n!9P``\n" + end + + it "implicitly has a count of 45 when passed '*', 0, 1, 2 or no count modifier" do + s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + r = "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n%86%A86$`\n" + [ [[s], "u", r], + [[s], "u*", r], + [[s], "u0", r], + [[s], "u1", r], + [[s], "u2", r], + ].should be_computed_by(:pack) + end + + it "encodes all ascii characters" do + [ [["\x00\x01\x02\x03\x04\x05\x06"], "'``$\"`P0%!@``\n"], + [["\a\b\t\n\v\f\r"], "'!P@)\"@L,#0``\n"], + [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], ")\#@\\0$1(3%!46\n"], + [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], ")%Q@9&AL<'1X?\n"], + [["!\"\#$%&'()*+,-./"], "/(2(C)\"4F)R@I*BLL+2XO\n"], + [["0123456789"], "*,\#$R,S0U-C<X.0``\n"], + [[":;<=>?@"], "'.CL\\/3X_0```\n"], + [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], ":04)#1$5&1TA)2DM,34Y/4%%24U155E=865H`\n"], + [["[\\]^_`"], "&6UQ=7E]@\n"], + [["abcdefghijklmnopqrstuvwxyz"], ":86)C9&5F9VAI:FML;6YO<'%R<W1U=G=X>7H`\n"], + [["{|}~"], "$>WQ]?@``\n"], + [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], ")?\\*`PH'\"@L*#\n"], + [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], ")PH3\"A<*&PH?\"\n"], + [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], ")B,*)PHK\"B\\*,\n"], + [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], ")PHW\"CL*/PI#\"\n"], + [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], ")D<*2PI/\"E,*5\n"], + [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], ")PI;\"E\\*8PIG\"\n"], + [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], ")FL*;PIS\"G<*>\n"], + [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], ")PI_\"H,*APJ+\"\n"], + [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], ")H\\*DPJ7\"IL*G\n"], + [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], ")PJC\"J<*JPJO\"\n"], + [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], ")K,*MPJ[\"K\\*P\n"], + [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], ")PK'\"LL*SPK3\"\n"], + [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], ")M<*VPK?\"N,*Y\n"], + [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], ")PKK\"N\\*\\PKW\"\n"], + [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], ")OL*_PX#\#@<.\"\n"], + [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], ")PX/#A,.%PX;#\n"], + [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], ")A\\.(PXG#BL.+\n"], + [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], ")PXS#C<..PX_#\n"], + [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], ")D,.1PY+#D\\.4\n"], + [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], ")PY7#EL.7PYC#\n"], + [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], ")F<.:PYO#G,.=\n"], + [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], ")PY[#G\\.@PZ'#\n"], + [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], ")HL.CPZ3#I<.F\n"], + [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], ")PZ?#J,.IPZK#\n"], + [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], ")J\\.LPZW#KL.O\n"], + [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], ")P[##L<.RP[/#\n"], + [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], ")M,.UP[;#M\\.X\n"], + [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], ")P[G#NL.[P[S#\n"], + [["\xbd\xc3\xbe\xc3\xbf"], "%O<.^P[\\`\n"] + ].should be_computed_by(:pack, "u") + end + + it "calls #to_str to convert an object to a String" do + obj = mock("pack m string") + obj.should_receive(:to_str).and_return("abc") + [obj].pack("u").should == "#86)C\n" + end + + it "raises a TypeError if #to_str does not return a String" do + obj = mock("pack m non-string") + -> { [obj].pack("u") }.should raise_error(TypeError) + end + + it "raises a TypeError if passed nil" do + -> { [nil].pack("u") }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Integer" do + -> { [0].pack("u") }.should raise_error(TypeError) + -> { [bignum_value].pack("u") }.should raise_error(TypeError) + end + + it "sets the output string to US-ASCII encoding" do + ["abcd"].pack("u").encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/ruby/core/array/pack/v_spec.rb b/spec/ruby/core/array/pack/v_spec.rb new file mode 100644 index 0000000000..d3932c84af --- /dev/null +++ b/spec/ruby/core/array/pack/v_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' +require_relative 'shared/integer' + +describe "Array#pack with format 'V'" do + it_behaves_like :array_pack_basic, 'V' + it_behaves_like :array_pack_basic_non_float, 'V' + it_behaves_like :array_pack_arguments, 'V' + it_behaves_like :array_pack_numeric_basic, 'V' + it_behaves_like :array_pack_integer, 'V' + it_behaves_like :array_pack_no_platform, 'V' + it_behaves_like :array_pack_32bit_le, 'V' +end + +describe "Array#pack with format 'v'" do + it_behaves_like :array_pack_basic, 'v' + it_behaves_like :array_pack_basic_non_float, 'v' + it_behaves_like :array_pack_arguments, 'v' + it_behaves_like :array_pack_numeric_basic, 'v' + it_behaves_like :array_pack_integer, 'v' + it_behaves_like :array_pack_no_platform, 'v' + it_behaves_like :array_pack_16bit_le, 'v' +end diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb new file mode 100644 index 0000000000..e770288d67 --- /dev/null +++ b/spec/ruby/core/array/pack/w_spec.rb @@ -0,0 +1,54 @@ +# encoding: binary +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' +require_relative 'shared/numeric_basic' + +describe "Array#pack with format 'w'" do + it_behaves_like :array_pack_basic, 'w' + it_behaves_like :array_pack_basic_non_float, 'w' + it_behaves_like :array_pack_arguments, 'w' + it_behaves_like :array_pack_numeric_basic, 'w' + + it "encodes a BER-compressed integer" do + [ [[0], "\x00"], + [[1], "\x01"], + [[9999], "\xce\x0f"], + [[2**65], "\x84\x80\x80\x80\x80\x80\x80\x80\x80\x00"] + ].should be_computed_by(:pack, "w") + end + + it "calls #to_int to convert the pack argument to an Integer" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + [obj].pack("w").should == "\x05" + end + + ruby_version_is ""..."3.3" do + it "ignores NULL bytes between directives" do + suppress_warning do + [1, 2, 3].pack("w\x00w").should == "\x01\x02" + end + end + end + + ruby_version_is "3.3" do + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack("w\x00w") + }.should raise_error(ArgumentError, /unknown pack directive/) + end + end + + it "ignores spaces between directives" do + [1, 2, 3].pack("w w").should == "\x01\x02" + end + + it "raises an ArgumentError when passed a negative value" do + -> { [-1].pack("w") }.should raise_error(ArgumentError) + end + + it "returns a binary string" do + [1].pack('w').encoding.should == Encoding::BINARY + end +end diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb new file mode 100644 index 0000000000..012fe4567f --- /dev/null +++ b/spec/ruby/core/array/pack/x_spec.rb @@ -0,0 +1,65 @@ +# encoding: binary +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/basic' + +describe "Array#pack with format 'x'" do + it_behaves_like :array_pack_basic, 'x' + it_behaves_like :array_pack_basic_non_float, 'x' + it_behaves_like :array_pack_no_platform, 'x' + + it "adds a NULL byte with an empty array" do + [].pack("x").should == "\x00" + end + + it "adds a NULL byte without consuming an element" do + [1, 2].pack("CxC").should == "\x01\x00\x02" + end + + it "is not affected by a previous count modifier" do + [].pack("x3x").should == "\x00\x00\x00\x00" + end + + it "adds multiple NULL bytes when passed a count modifier" do + [].pack("x3").should == "\x00\x00\x00" + end + + it "does not add a NULL byte if the count modifier is zero" do + [].pack("x0").should == "" + end + + it "does not add a NULL byte when passed the '*' modifier" do + [].pack("x*").should == "" + [1, 2].pack("Cx*C").should == "\x01\x02" + end +end + +describe "Array#pack with format 'X'" do + it_behaves_like :array_pack_basic, 'X' + it_behaves_like :array_pack_basic_non_float, 'X' + it_behaves_like :array_pack_no_platform, 'X' + + it "reduces the output string by one byte at the point it is encountered" do + [1, 2, 3].pack("C2XC").should == "\x01\x03" + end + + it "does not consume any elements" do + [1, 2, 3].pack("CXC").should == "\x02" + end + + it "reduces the output string by multiple bytes when passed a count modifier" do + [1, 2, 3, 4, 5].pack("C2X2C").should == "\x03" + end + + it "has no affect when passed the '*' modifier" do + [1, 2, 3].pack("C2X*C").should == "\x01\x02\x03" + end + + it "raises an ArgumentError if the output string is empty" do + -> { [1, 2, 3].pack("XC") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the count modifier is greater than the bytes in the string" do + -> { [1, 2, 3].pack("C2X3") }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb new file mode 100644 index 0000000000..60f8f7bf10 --- /dev/null +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -0,0 +1,44 @@ +# encoding: binary +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 "calls #to_str to convert an Object to a String" do + obj = mock("pack Z string") + obj.should_receive(:to_str).and_return("``abcdef") + [obj].pack("Z*").should == "``abcdef\x00" + end + + it "will not implicitly convert a number to a string" do + -> { [0].pack('Z') }.should raise_error(TypeError) + end + + it "adds all the bytes and appends a NULL byte when passed the '*' modifier" do + ["abc"].pack("Z*").should == "abc\x00" + end + + it "pads the output with NULL bytes when the count exceeds the size of the String" do + ["abc"].pack("Z6").should == "abc\x00\x00\x00" + end + + it "adds a NULL byte when the value is nil" do + [nil].pack("Z").should == "\x00" + end + + it "pads the output with NULL bytes when the value is nil" do + [nil].pack("Z3").should == "\x00\x00\x00" + end + + it "does not append a NULL byte when passed the '*' modifier and the value is nil" do + [nil].pack("Z*").should == "\x00" + end +end diff --git a/spec/ruby/core/array/partition_spec.rb b/spec/ruby/core/array/partition_spec.rb new file mode 100644 index 0000000000..be36fffcab --- /dev/null +++ b/spec/ruby/core/array/partition_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#partition" do + it "returns two arrays" do + [].partition {}.should == [[], []] + end + + it "returns in the left array values for which the block evaluates to true" do + ary = [0, 1, 2, 3, 4, 5] + + ary.partition { |i| true }.should == [ary, []] + ary.partition { |i| 5 }.should == [ary, []] + ary.partition { |i| false }.should == [[], ary] + ary.partition { |i| nil }.should == [[], ary] + ary.partition { |i| i % 2 == 0 }.should == [[0, 2, 4], [1, 3, 5]] + ary.partition { |i| i / 3 == 0 }.should == [[0, 1, 2], [3, 4, 5]] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.partition { true }.should == [[empty], []] + empty.partition { false }.should == [[], [empty]] + + array = ArraySpecs.recursive_array + array.partition { true }.should == [ + [1, 'two', 3.0, array, array, array, array, array], + [] + ] + condition = true + array.partition { condition = !condition }.should == [ + ['two', array, array, array], + [1, 3.0, array, array] + ] + end + + it "does not return subclass instances on Array subclasses" do + result = ArraySpecs::MyArray[1, 2, 3].partition { |x| x % 2 == 0 } + result.should be_an_instance_of(Array) + result[0].should be_an_instance_of(Array) + result[1].should be_an_instance_of(Array) + end +end diff --git a/spec/ruby/core/array/permutation_spec.rb b/spec/ruby/core/array/permutation_spec.rb new file mode 100644 index 0000000000..f15bd76639 --- /dev/null +++ b/spec/ruby/core/array/permutation_spec.rb @@ -0,0 +1,138 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + + +describe "Array#permutation" do + + before :each do + @numbers = (1..3).to_a + @yielded = [] + end + + it "returns an Enumerator of all permutations when called without a block or arguments" do + enum = @numbers.permutation + enum.should be_an_instance_of(Enumerator) + enum.to_a.sort.should == [ + [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] + ].sort + end + + it "returns an Enumerator of permutations of given length when called with an argument but no block" do + enum = @numbers.permutation(1) + enum.should be_an_instance_of(Enumerator) + enum.to_a.sort.should == [[1],[2],[3]] + end + + it "yields all permutations to the block then returns self when called with block but no arguments" do + array = @numbers.permutation {|n| @yielded << n} + array.should be_an_instance_of(Array) + array.sort.should == @numbers.sort + @yielded.sort.should == [ + [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] + ].sort + end + + it "yields all permutations of given length to the block then returns self when called with block and argument" do + array = @numbers.permutation(2) {|n| @yielded << n} + array.should be_an_instance_of(Array) + array.sort.should == @numbers.sort + @yielded.sort.should == [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]].sort + end + + it "returns the empty permutation ([[]]) when the given length is 0" do + @numbers.permutation(0).to_a.should == [[]] + @numbers.permutation(0) { |n| @yielded << n } + @yielded.should == [[]] + end + + it "returns the empty permutation([]) when called on an empty Array" do + [].permutation.to_a.should == [[]] + [].permutation { |n| @yielded << n } + @yielded.should == [[]] + end + + it "returns no permutations when the given length has no permutations" do + @numbers.permutation(9).entries.size.should == 0 + @numbers.permutation(9) { |n| @yielded << n } + @yielded.should == [] + end + + it "handles duplicate elements correctly" do + @numbers << 1 + @numbers.permutation(2).sort.should == [ + [1,1],[1,1],[1,2],[1,2],[1,3],[1,3], + [2,1],[2,1],[2,3], + [3,1],[3,1],[3,2] + ].sort + end + + it "handles nested Arrays correctly" do + # The ugliness is due to the order of permutations returned by + # permutation being undefined combined with #sort croaking on Arrays of + # Arrays. + @numbers << [4,5] + got = @numbers.permutation(2).to_a + expected = [ + [1, 2], [1, 3], [1, [4, 5]], + [2, 1], [2, 3], [2, [4, 5]], + [3, 1], [3, 2], [3, [4, 5]], + [[4, 5], 1], [[4, 5], 2], [[4, 5], 3] + ] + expected.each {|e| got.include?(e).should be_true} + got.size.should == expected.size + end + + it "truncates Float arguments" do + @numbers.permutation(3.7).to_a.sort.should == + @numbers.permutation(3).to_a.sort + end + + it "returns an Enumerator which works as expected even when the array was modified" do + @numbers = [1, 2] + enum = @numbers.permutation + @numbers << 3 + enum.to_a.sort.should == [ + [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] + ].sort + end + + it "generates from a defensive copy, ignoring mutations" do + accum = [] + ary = [1,2,3] + ary.permutation(3) do |x| + accum << x + ary[0] = 5 + end + + accum.should == [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + end + + describe "when no block is given" do + describe "returned Enumerator" do + describe "size" do + describe "with an array size greater than 0" do + it "returns the descending factorial of array size and given length" do + @numbers.permutation(4).size.should == 0 + @numbers.permutation(3).size.should == 6 + @numbers.permutation(2).size.should == 6 + @numbers.permutation(1).size.should == 3 + @numbers.permutation(0).size.should == 1 + end + it "returns the descending factorial of array size with array size when there's no param" do + @numbers.permutation.size.should == 6 + [1,2,3,4].permutation.size.should == 24 + [1].permutation.size.should == 1 + end + end + describe "with an empty array" do + it "returns 1 when the given length is 0" do + [].permutation(0).size.should == 1 + end + it "returns 1 when there's param" do + [].permutation.size.should == 1 + end + end + end + end + end +end diff --git a/spec/ruby/core/array/plus_spec.rb b/spec/ruby/core/array/plus_spec.rb new file mode 100644 index 0000000000..b7153fd3ef --- /dev/null +++ b/spec/ruby/core/array/plus_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#+" do + it "concatenates two arrays" do + ([ 1, 2, 3 ] + [ 3, 4, 5 ]).should == [1, 2, 3, 3, 4, 5] + ([ 1, 2, 3 ] + []).should == [1, 2, 3] + ([] + [ 1, 2, 3 ]).should == [1, 2, 3] + ([] + []).should == [] + end + + it "can concatenate an array with itself" do + ary = [1, 2, 3] + (ary + ary).should == [1, 2, 3, 1, 2, 3] + end + + describe "converts the passed argument to an Array using #to_ary" do + it "successfully concatenates the resulting array from the #to_ary call" do + obj = mock('["x", "y"]') + obj.should_receive(:to_ary).and_return(["x", "y"]) + ([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"] + end + + it "raises a TypeError if the given argument can't be converted to an array" do + -> { [1, 2, 3] + nil }.should raise_error(TypeError) + -> { [1, 2, 3] + "abc" }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do + obj = mock("hello") + obj.should_receive(:to_ary).and_raise(NoMethodError) + -> { [1, 2, 3] + obj }.should raise_error(NoMethodError) + end + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + (empty + empty).should == [empty, empty] + + array = ArraySpecs.recursive_array + (empty + array).should == [empty, 1, 'two', 3.0, array, array, array, array, array] + (array + array).should == [ + 1, 'two', 3.0, array, array, array, array, array, + 1, 'two', 3.0, array, array, array, array, array] + end + + it "does return subclass instances with Array subclasses" do + (ArraySpecs::MyArray[1, 2, 3] + []).should be_an_instance_of(Array) + (ArraySpecs::MyArray[1, 2, 3] + ArraySpecs::MyArray[]).should be_an_instance_of(Array) + ([1, 2, 3] + ArraySpecs::MyArray[]).should be_an_instance_of(Array) + end + + it "does not call to_ary on array subclasses" do + ([5, 6] + ArraySpecs::ToAryArray[1, 2]).should == [5, 6, 1, 2] + end +end diff --git a/spec/ruby/core/array/pop_spec.rb b/spec/ruby/core/array/pop_spec.rb new file mode 100644 index 0000000000..2a19408660 --- /dev/null +++ b/spec/ruby/core/array/pop_spec.rb @@ -0,0 +1,124 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#pop" do + it "removes and returns the last element of the array" do + a = ["a", 1, nil, true] + + a.pop.should == true + a.should == ["a", 1, nil] + + a.pop.should == nil + a.should == ["a", 1] + + a.pop.should == 1 + a.should == ["a"] + + a.pop.should == "a" + a.should == [] + end + + it "returns nil if there are no more elements" do + [].pop.should == nil + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.pop.should == [] + + array = ArraySpecs.recursive_array + array.pop.should == [1, 'two', 3.0, array, array, array, array] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.pop }.should raise_error(FrozenError) + end + + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.pop }.should raise_error(FrozenError) + end + + describe "passed a number n as an argument" do + it "removes and returns an array with the last n elements of the array" do + a = [1, 2, 3, 4, 5, 6] + + a.pop(0).should == [] + a.should == [1, 2, 3, 4, 5, 6] + + a.pop(1).should == [6] + a.should == [1, 2, 3, 4, 5] + + a.pop(2).should == [4, 5] + a.should == [1, 2, 3] + + a.pop(3).should == [1, 2, 3] + a.should == [] + end + + it "returns an array with the last n elements even if shift was invoked" do + a = [1, 2, 3, 4] + a.shift + a.pop(3).should == [2, 3, 4] + end + + it "returns a new empty array if there are no more elements" do + a = [] + popped1 = a.pop(1) + popped1.should == [] + a.should == [] + + popped2 = a.pop(2) + popped2.should == [] + a.should == [] + + popped1.should_not equal(popped2) + end + + it "returns whole elements if n exceeds size of the array" do + a = [1, 2, 3, 4, 5] + a.pop(6).should == [1, 2, 3, 4, 5] + a.should == [] + end + + it "does not return self even when it returns whole elements" do + a = [1, 2, 3, 4, 5] + a.pop(5).should_not equal(a) + + a = [1, 2, 3, 4, 5] + a.pop(6).should_not equal(a) + end + + it "raises an ArgumentError if n is negative" do + ->{ [1, 2, 3].pop(-1) }.should raise_error(ArgumentError) + end + + it "tries to convert n to an Integer using #to_int" do + a = [1, 2, 3, 4] + a.pop(2.3).should == [3, 4] + + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + a.should == [1, 2] + a.pop(obj).should == [1, 2] + a.should == [] + end + + it "raises a TypeError when the passed n cannot be coerced to Integer" do + ->{ [1, 2].pop("cat") }.should raise_error(TypeError) + ->{ [1, 2].pop(nil) }.should raise_error(TypeError) + end + + it "raises an ArgumentError if more arguments are passed" do + ->{ [1, 2].pop(1, 2) }.should raise_error(ArgumentError) + end + + it "does not return subclass instances with Array subclass" do + ArraySpecs::MyArray[1, 2, 3].pop(2).should be_an_instance_of(Array) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.pop(2) }.should raise_error(FrozenError) + -> { ArraySpecs.frozen_array.pop(0) }.should raise_error(FrozenError) + end + end +end diff --git a/spec/ruby/core/array/prepend_spec.rb b/spec/ruby/core/array/prepend_spec.rb new file mode 100644 index 0000000000..368b8dcfcd --- /dev/null +++ b/spec/ruby/core/array/prepend_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/unshift' + +describe "Array#prepend" do + it_behaves_like :array_unshift, :prepend +end diff --git a/spec/ruby/core/array/product_spec.rb b/spec/ruby/core/array/product_spec.rb new file mode 100644 index 0000000000..6fb3818508 --- /dev/null +++ b/spec/ruby/core/array/product_spec.rb @@ -0,0 +1,73 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#product" do + it "returns converted arguments using :to_ary" do + ->{ [1].product(2..3) }.should raise_error(TypeError) + ar = ArraySpecs::ArrayConvertible.new(2,3) + [1].product(ar).should == [[1,2],[1,3]] + ar.called.should == :to_ary + end + + it "returns converted arguments using :method_missing" do + ar = ArraySpecs::ArrayMethodMissing.new(2,3) + [1].product(ar).should == [[1,2],[1,3]] + end + + it "returns the expected result" do + [1,2].product([3,4,5],[6,8]).should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], + [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]] + end + + it "has no required argument" do + [1,2].product.should == [[1],[2]] + end + + it "returns an empty array when the argument is an empty array" do + [1, 2].product([]).should == [] + end + + it "does not attempt to produce an unreasonable number of products" do + a = (0..100).to_a + -> do + a.product(a, a, a, a, a, a, a, a, a, a) + end.should raise_error(RangeError) + end + + describe "when given a block" do + it "yields all combinations in turn" do + acc = [] + [1,2].product([3,4,5],[6,8]){|array| acc << array} + acc.should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], + [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]] + + acc = [] + [1,2].product([3,4,5],[],[6,8]){|array| acc << array} + acc.should be_empty + end + + it "returns self" do + a = [1, 2, 3].freeze + + a.product([1, 2]) { |p| p.first }.should == a + end + + it "will ignore unreasonable numbers of products and yield anyway" do + a = (0..100).to_a + -> do + a.product(a, a, a, a, a, a, a, a, a, a) + end.should raise_error(RangeError) + end + end + + describe "when given an empty block" do + it "returns self" do + arr = [1,2] + arr.product([3,4,5],[6,8]){}.should equal(arr) + arr = [] + arr.product([3,4,5],[6,8]){}.should equal(arr) + arr = [1,2] + arr.product([]){}.should equal(arr) + end + end +end diff --git a/spec/ruby/core/array/push_spec.rb b/spec/ruby/core/array/push_spec.rb new file mode 100644 index 0000000000..607cbc7b4d --- /dev/null +++ b/spec/ruby/core/array/push_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/push' + +describe "Array#push" do + it_behaves_like :array_push, :push +end diff --git a/spec/ruby/core/array/rassoc_spec.rb b/spec/ruby/core/array/rassoc_spec.rb new file mode 100644 index 0000000000..632a05e8b3 --- /dev/null +++ b/spec/ruby/core/array/rassoc_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#rassoc" do + it "returns the first contained array whose second element is == object" do + ary = [[1, "a", 0.5], [2, "b"], [3, "b"], [4, "c"], [], [5], [6, "d"]] + ary.rassoc("a").should == [1, "a", 0.5] + ary.rassoc("b").should == [2, "b"] + ary.rassoc("d").should == [6, "d"] + ary.rassoc("z").should == nil + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.rassoc([]).should be_nil + [[empty, empty]].rassoc(empty).should == [empty, empty] + + array = ArraySpecs.recursive_array + array.rassoc(array).should be_nil + [[empty, array]].rassoc(array).should == [empty, array] + end + + it "calls elem == obj on the second element of each contained array" do + key = 'foobar' + o = mock('foobar') + def o.==(other); other == 'foobar'; end + + [[1, :foobar], [2, o], [3, mock('foo')]].rassoc(key).should == [2, o] + end + + it "does not check the last element in each contained but specifically the second" do + key = 'foobar' + o = mock('foobar') + def o.==(other); other == 'foobar'; end + + [[1, :foobar, o], [2, o, 1], [3, mock('foo')]].rassoc(key).should == [2, o, 1] + end + + ruby_version_is "3.3" do + it "calls to_ary on non-array elements" do + s1 = [1, 2] + s2 = ArraySpecs::ArrayConvertible.new(2, 3) + a = [s1, s2] + + s1.should_not_receive(:to_ary) + a.rassoc(2).should equal(s1) + + a.rassoc(3).should == [2, 3] + s2.called.should equal(:to_ary) + end + end +end diff --git a/spec/ruby/core/array/reject_spec.rb b/spec/ruby/core/array/reject_spec.rb new file mode 100644 index 0000000000..81a467e364 --- /dev/null +++ b/spec/ruby/core/array/reject_spec.rb @@ -0,0 +1,158 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/enumeratorize' +require_relative 'shared/delete_if' +require_relative 'shared/iterable_and_tolerating_size_increasing' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#reject" do + it "returns a new array without elements for which block is true" do + ary = [1, 2, 3, 4, 5] + ary.reject { true }.should == [] + ary.reject { false }.should == ary + ary.reject { false }.should_not equal ary + ary.reject { nil }.should == ary + ary.reject { nil }.should_not equal ary + ary.reject { 5 }.should == [] + ary.reject { |i| i < 3 }.should == [3, 4, 5] + ary.reject { |i| i % 2 == 0 }.should == [1, 3, 5] + end + + it "returns self when called on an Array emptied with #shift" do + array = [1] + array.shift + array.reject { |x| true }.should == [] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.reject { false }.should == [empty] + empty.reject { true }.should == [] + + array = ArraySpecs.recursive_array + array.reject { false }.should == [1, 'two', 3.0, array, array, array, array, array] + array.reject { true }.should == [] + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].reject { |x| x % 2 == 0 }.should be_an_instance_of(Array) + end + + it "does not retain instance variables" do + array = [] + array.instance_variable_set("@variable", "value") + array.reject { false }.instance_variable_get("@variable").should == nil + end + + it_behaves_like :enumeratorize, :reject + it_behaves_like :enumeratorized_with_origin_size, :reject, [1,2,3] +end + +describe "Array#reject" do + it_behaves_like :array_iterable_and_tolerating_size_increasing, :reject +end + +describe "Array#reject!" do + it "removes elements for which block is true" do + a = [3, 4, 5, 6, 7, 8, 9, 10, 11] + a.reject! { |i| i % 2 == 0 }.should equal(a) + a.should == [3, 5, 7, 9, 11] + a.reject! { |i| i > 8 } + a.should == [3, 5, 7] + a.reject! { |i| i < 4 } + a.should == [5, 7] + a.reject! { |i| i == 5 } + a.should == [7] + a.reject! { true } + a.should == [] + a.reject! { true } + a.should == [] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty_dup = empty.dup + empty.reject! { false }.should == nil + empty.should == empty_dup + + empty = ArraySpecs.empty_recursive_array + empty.reject! { true }.should == [] + empty.should == [] + + array = ArraySpecs.recursive_array + array_dup = array.dup + array.reject! { false }.should == nil + array.should == array_dup + + array = ArraySpecs.recursive_array + array.reject! { true }.should == [] + array.should == [] + end + + it "returns nil when called on an Array emptied with #shift" do + array = [1] + array.shift + array.reject! { |x| true }.should == nil + end + + it "returns nil if no changes are made" do + a = [1, 2, 3] + + a.reject! { |i| i < 0 }.should == nil + + a.reject! { true } + a.reject! { true }.should == nil + end + + it "returns an Enumerator if no block given, and the array is frozen" do + ArraySpecs.frozen_array.reject!.should be_an_instance_of(Enumerator) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.reject! {} }.should raise_error(FrozenError) + end + + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.reject! {} }.should raise_error(FrozenError) + end + + it "raises a FrozenError on a frozen array only during iteration if called without a block" do + enum = ArraySpecs.frozen_array.reject! + -> { enum.each {} }.should raise_error(FrozenError) + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.reject! { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "only removes elements for which the block returns true, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.reject! do |x| + case x + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + a.should == [1, 3, 4] + end + + it_behaves_like :enumeratorize, :reject! + it_behaves_like :enumeratorized_with_origin_size, :reject!, [1,2,3] + it_behaves_like :delete_if, :reject! +end + +describe "Array#reject!" do + @value_to_return = -> _ { false } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :reject! +end diff --git a/spec/ruby/core/array/repeated_combination_spec.rb b/spec/ruby/core/array/repeated_combination_spec.rb new file mode 100644 index 0000000000..b62382024a --- /dev/null +++ b/spec/ruby/core/array/repeated_combination_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' + +describe "Array#repeated_combination" do + before :each do + @array = [10, 11, 12] + end + + it "returns an enumerator when no block is provided" do + @array.repeated_combination(2).should be_an_instance_of(Enumerator) + end + + it "returns self when a block is given" do + @array.repeated_combination(2){}.should equal(@array) + end + + it "yields nothing for negative length and return self" do + @array.repeated_combination(-1){ fail }.should equal(@array) + @array.repeated_combination(-10){ fail }.should equal(@array) + end + + it "yields the expected repeated_combinations" do + @array.repeated_combination(2).to_a.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]] + @array.repeated_combination(3).to_a.sort.should == [[10, 10, 10], [10, 10, 11], [10, 10, 12], [10, 11, 11], [10, 11, 12], + [10, 12, 12], [11, 11, 11], [11, 11, 12], [11, 12, 12], [12, 12, 12]] + end + + it "yields [] when length is 0" do + @array.repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0 + [].repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0 + end + + it "yields nothing when the array is empty and num is non zero" do + [].repeated_combination(5).to_a.should == [] # one repeated_combination of length 0 + end + + it "yields a partition consisting of only singletons" do + @array.repeated_combination(1).sort.to_a.should == [[10],[11],[12]] + end + + it "accepts sizes larger than the original array" do + @array.repeated_combination(4).to_a.sort.should == + [[10, 10, 10, 10], [10, 10, 10, 11], [10, 10, 10, 12], + [10, 10, 11, 11], [10, 10, 11, 12], [10, 10, 12, 12], + [10, 11, 11, 11], [10, 11, 11, 12], [10, 11, 12, 12], + [10, 12, 12, 12], [11, 11, 11, 11], [11, 11, 11, 12], + [11, 11, 12, 12], [11, 12, 12, 12], [12, 12, 12, 12]] + end + + it "generates from a defensive copy, ignoring mutations" do + accum = [] + @array.repeated_combination(2) do |x| + accum << x + @array[0] = 1 + end + accum.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]] + end + + describe "when no block is given" do + describe "returned Enumerator" do + describe "size" do + it "returns 0 when the combination_size is < 0" do + @array.repeated_combination(-1).size.should == 0 + [].repeated_combination(-2).size.should == 0 + end + + it "returns 1 when the combination_size is 0" do + @array.repeated_combination(0).size.should == 1 + [].repeated_combination(0).size.should == 1 + end + + it "returns the binomial coefficient between combination_size and array size + combination_size -1" do + @array.repeated_combination(5).size.should == 21 + @array.repeated_combination(4).size.should == 15 + @array.repeated_combination(3).size.should == 10 + @array.repeated_combination(2).size.should == 6 + @array.repeated_combination(1).size.should == 3 + @array.repeated_combination(0).size.should == 1 + [].repeated_combination(0).size.should == 1 + [].repeated_combination(1).size.should == 0 + end + end + end + end +end diff --git a/spec/ruby/core/array/repeated_permutation_spec.rb b/spec/ruby/core/array/repeated_permutation_spec.rb new file mode 100644 index 0000000000..a165fda09e --- /dev/null +++ b/spec/ruby/core/array/repeated_permutation_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' + + +describe "Array#repeated_permutation" do + + before :each do + @numbers = [10, 11, 12] + @permutations = [[10, 10], [10, 11], [10, 12], [11, 10], [11, 11], [11, 12], [12, 10], [12, 11], [12, 12]] + end + + it "returns an Enumerator of all repeated permutations of given length when called without a block" do + enum = @numbers.repeated_permutation(2) + enum.should be_an_instance_of(Enumerator) + enum.to_a.sort.should == @permutations + end + + it "yields all repeated_permutations to the block then returns self when called with block but no arguments" do + yielded = [] + @numbers.repeated_permutation(2) {|n| yielded << n}.should equal(@numbers) + yielded.sort.should == @permutations + end + + it "yields the empty repeated_permutation ([[]]) when the given length is 0" do + @numbers.repeated_permutation(0).to_a.should == [[]] + [].repeated_permutation(0).to_a.should == [[]] + end + + it "does not yield when called on an empty Array with a nonzero argument" do + [].repeated_permutation(10).to_a.should == [] + end + + it "handles duplicate elements correctly" do + @numbers[-1] = 10 + @numbers.repeated_permutation(2).sort.should == + [[10, 10], [10, 10], [10, 10], [10, 10], [10, 11], [10, 11], [11, 10], [11, 10], [11, 11]] + end + + it "truncates Float arguments" do + @numbers.repeated_permutation(3.7).to_a.sort.should == + @numbers.repeated_permutation(3).to_a.sort + end + + it "returns an Enumerator which works as expected even when the array was modified" do + @numbers.shift + enum = @numbers.repeated_permutation(2) + @numbers.unshift 10 + enum.to_a.sort.should == @permutations + end + + it "allows permutations larger than the number of elements" do + [1,2].repeated_permutation(3).sort.should == + [[1, 1, 1], [1, 1, 2], [1, 2, 1], + [1, 2, 2], [2, 1, 1], [2, 1, 2], + [2, 2, 1], [2, 2, 2]] + end + + it "generates from a defensive copy, ignoring mutations" do + accum = [] + ary = [1,2] + ary.repeated_permutation(3) do |x| + accum << x + ary[0] = 5 + end + + accum.sort.should == + [[1, 1, 1], [1, 1, 2], [1, 2, 1], + [1, 2, 2], [2, 1, 1], [2, 1, 2], + [2, 2, 1], [2, 2, 2]] + end + + describe "when no block is given" do + describe "returned Enumerator" do + describe "size" do + it "returns 0 when combination_size is < 0" do + @numbers.repeated_permutation(-1).size.should == 0 + [].repeated_permutation(-1).size.should == 0 + end + + it "returns array size ** combination_size" do + @numbers.repeated_permutation(4).size.should == 81 + @numbers.repeated_permutation(3).size.should == 27 + @numbers.repeated_permutation(2).size.should == 9 + @numbers.repeated_permutation(1).size.should == 3 + @numbers.repeated_permutation(0).size.should == 1 + [].repeated_permutation(4).size.should == 0 + [].repeated_permutation(3).size.should == 0 + [].repeated_permutation(2).size.should == 0 + [].repeated_permutation(1).size.should == 0 + [].repeated_permutation(0).size.should == 1 + end + end + end + end +end diff --git a/spec/ruby/core/array/replace_spec.rb b/spec/ruby/core/array/replace_spec.rb new file mode 100644 index 0000000000..2f53338f5e --- /dev/null +++ b/spec/ruby/core/array/replace_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/replace' + +describe "Array#replace" do + it_behaves_like :array_replace, :replace +end diff --git a/spec/ruby/core/array/reverse_each_spec.rb b/spec/ruby/core/array/reverse_each_spec.rb new file mode 100644 index 0000000000..59dabcd33d --- /dev/null +++ b/spec/ruby/core/array/reverse_each_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/enumeratorize' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +describe "Array#reverse_each" do + before :each do + ScratchPad.record [] + end + + it "traverses array in reverse order and pass each element to block" do + [1, 3, 4, 6].reverse_each { |i| ScratchPad << i } + ScratchPad.recorded.should == [6, 4, 3, 1] + end + + it "returns self" do + a = [:a, :b, :c] + a.reverse_each { |x| }.should equal(a) + end + + it "yields only the top level element of an empty recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.reverse_each { |i| ScratchPad << i } + ScratchPad.recorded.should == [empty] + end + + it "yields only the top level element of a recursive array" do + array = ArraySpecs.recursive_array + array.reverse_each { |i| ScratchPad << i } + ScratchPad.recorded.should == [array, array, array, array, array, 3.0, 'two', 1] + end + + it "returns the correct size when no block is given" do + [1, 2, 3].reverse_each.size.should == 3 + end + + it "tolerates increasing an array size during iteration" do + array = [:a, :b, :c] + ScratchPad.record [] + i = 0 + + array.reverse_each do |e| + ScratchPad << e + array.prepend i if i < 100 + i += 1 + end + + ScratchPad.recorded.should == [:c, :a, 1] + end + + it_behaves_like :enumeratorize, :reverse_each + it_behaves_like :enumeratorized_with_origin_size, :reverse_each, [1,2,3] +end diff --git a/spec/ruby/core/array/reverse_spec.rb b/spec/ruby/core/array/reverse_spec.rb new file mode 100644 index 0000000000..05dbd2efcf --- /dev/null +++ b/spec/ruby/core/array/reverse_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#reverse" do + it "returns a new array with the elements in reverse order" do + [].reverse.should == [] + [1, 3, 5, 2].reverse.should == [2, 5, 3, 1] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.reverse.should == empty + + array = ArraySpecs.recursive_array + array.reverse.should == [array, array, array, array, array, 3.0, 'two', 1] + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].reverse.should be_an_instance_of(Array) + end +end + +describe "Array#reverse!" do + it "reverses the elements in place" do + a = [6, 3, 4, 2, 1] + a.reverse!.should equal(a) + a.should == [1, 2, 4, 3, 6] + [].reverse!.should == [] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.reverse!.should == [empty] + + array = ArraySpecs.recursive_array + array.reverse!.should == [array, array, array, array, array, 3.0, 'two', 1] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.reverse! }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/rindex_spec.rb b/spec/ruby/core/array/rindex_spec.rb new file mode 100644 index 0000000000..13de88818c --- /dev/null +++ b/spec/ruby/core/array/rindex_spec.rb @@ -0,0 +1,95 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +describe "Array#rindex" do + it "returns the first index backwards from the end where element == to object" do + key = 3 + uno = mock('one') + dos = mock('two') + tres = mock('three') + tres.should_receive(:==).any_number_of_times.and_return(false) + dos.should_receive(:==).any_number_of_times.and_return(true) + uno.should_not_receive(:==) + ary = [uno, dos, tres] + + ary.rindex(key).should == 1 + end + + it "returns size-1 if last element == to object" do + [2, 1, 3, 2, 5].rindex(5).should == 4 + end + + it "returns 0 if only first element == to object" do + [2, 1, 3, 1, 5].rindex(2).should == 0 + end + + it "returns nil if no element == to object" do + [1, 1, 3, 2, 1, 3].rindex(4).should == nil + end + + it "returns correct index even after delete_at" do + array = ["fish", "bird", "lion", "cat"] + array.delete_at(0) + array.rindex("lion").should == 1 + end + + it "properly handles empty recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.rindex(empty).should == 0 + empty.rindex(1).should be_nil + end + + it "properly handles recursive arrays" do + array = ArraySpecs.recursive_array + array.rindex(1).should == 0 + array.rindex(array).should == 7 + end + + it "accepts a block instead of an argument" do + [4, 2, 1, 5, 1, 3].rindex { |x| x < 2 }.should == 4 + end + + it "ignores the block if there is an argument" do + -> { + [4, 2, 1, 5, 1, 3].rindex(5) { |x| x < 2 }.should == 3 + }.should complain(/given block not used/) + end + + it "rechecks the array size during iteration" do + ary = [4, 2, 1, 5, 1, 3] + seen = [] + ary.rindex { |x| seen << x; ary.clear; false } + + seen.should == [3] + end + + it "tolerates increasing an array size during iteration" do + array = [:a, :b, :c] + ScratchPad.record [] + i = 0 + + array.rindex do |e| + ScratchPad << e + array.prepend i if i < 100 + i += 1 + false + end + + ScratchPad.recorded.should == [:c, :a, 1] + end + + describe "given no argument and no block" do + it "produces an Enumerator" do + enum = [4, 2, 1, 5, 1, 3].rindex + enum.should be_an_instance_of(Enumerator) + enum.each { |x| x < 2 }.should == 4 + end + end + + it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3] +end diff --git a/spec/ruby/core/array/rotate_spec.rb b/spec/ruby/core/array/rotate_spec.rb new file mode 100644 index 0000000000..60dcc8b113 --- /dev/null +++ b/spec/ruby/core/array/rotate_spec.rb @@ -0,0 +1,129 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#rotate" do + describe "when passed no argument" do + it "returns a copy of the array with the first element moved at the end" do + [1, 2, 3, 4, 5].rotate.should == [2, 3, 4, 5, 1] + end + end + + describe "with an argument n" do + it "returns a copy of the array with the first (n % size) elements moved at the end" do + a = [1, 2, 3, 4, 5] + a.rotate( 2).should == [3, 4, 5, 1, 2] + a.rotate( -1).should == [5, 1, 2, 3, 4] + a.rotate(-21).should == [5, 1, 2, 3, 4] + a.rotate( 13).should == [4, 5, 1, 2, 3] + a.rotate( 0).should == a + end + + it "coerces the argument using to_int" do + [1, 2, 3].rotate(2.6).should == [3, 1, 2] + + obj = mock('integer_like') + obj.should_receive(:to_int).and_return(2) + [1, 2, 3].rotate(obj).should == [3, 1, 2] + end + + it "raises a TypeError if not passed an integer-like argument" do + -> { + [1, 2].rotate(nil) + }.should raise_error(TypeError) + -> { + [1, 2].rotate("4") + }.should raise_error(TypeError) + end + end + + it "returns a copy of the array when its length is one or zero" do + [1].rotate.should == [1] + [1].rotate(2).should == [1] + [1].rotate(-42).should == [1] + [ ].rotate.should == [] + [ ].rotate(2).should == [] + [ ].rotate(-42).should == [] + end + + it "does not mutate the receiver" do + -> { + [].freeze.rotate + [2].freeze.rotate(2) + [1,2,3].freeze.rotate(-3) + }.should_not raise_error + end + + it "does not return self" do + a = [1, 2, 3] + a.rotate.should_not equal(a) + a = [] + a.rotate(0).should_not equal(a) + end + + it "does not return subclass instance for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].rotate.should be_an_instance_of(Array) + end +end + +describe "Array#rotate!" do + describe "when passed no argument" do + it "moves the first element to the end and returns self" do + a = [1, 2, 3, 4, 5] + a.rotate!.should equal(a) + a.should == [2, 3, 4, 5, 1] + end + end + + describe "with an argument n" do + it "moves the first (n % size) elements at the end and returns self" do + a = [1, 2, 3, 4, 5] + a.rotate!(2).should equal(a) + a.should == [3, 4, 5, 1, 2] + a.rotate!(-12).should equal(a) + a.should == [1, 2, 3, 4, 5] + a.rotate!(13).should equal(a) + a.should == [4, 5, 1, 2, 3] + end + + it "coerces the argument using to_int" do + [1, 2, 3].rotate!(2.6).should == [3, 1, 2] + + obj = mock('integer_like') + obj.should_receive(:to_int).and_return(2) + [1, 2, 3].rotate!(obj).should == [3, 1, 2] + end + + it "raises a TypeError if not passed an integer-like argument" do + -> { + [1, 2].rotate!(nil) + }.should raise_error(TypeError) + -> { + [1, 2].rotate!("4") + }.should raise_error(TypeError) + end + end + + it "does nothing and returns self when the length is zero or one" do + a = [1] + a.rotate!.should equal(a) + a.should == [1] + a.rotate!(2).should equal(a) + a.should == [1] + a.rotate!(-21).should equal(a) + a.should == [1] + + a = [] + a.rotate!.should equal(a) + a.should == [] + a.rotate!(2).should equal(a) + a.should == [] + a.rotate!(-21).should equal(a) + a.should == [] + end + + it "raises a FrozenError on a frozen array" do + -> { [1, 2, 3].freeze.rotate!(0) }.should raise_error(FrozenError) + -> { [1].freeze.rotate!(42) }.should raise_error(FrozenError) + -> { [].freeze.rotate! }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/sample_spec.rb b/spec/ruby/core/array/sample_spec.rb new file mode 100644 index 0000000000..d4e945152d --- /dev/null +++ b/spec/ruby/core/array/sample_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#sample" do + it "samples evenly" do + ArraySpecs.measure_sample_fairness(4, 1, 400) + ArraySpecs.measure_sample_fairness(4, 2, 400) + ArraySpecs.measure_sample_fairness(4, 3, 400) + ArraySpecs.measure_sample_fairness(40, 3, 400) + ArraySpecs.measure_sample_fairness(40, 4, 400) + ArraySpecs.measure_sample_fairness(40, 8, 400) + ArraySpecs.measure_sample_fairness(40, 16, 400) + ArraySpecs.measure_sample_fairness_large_sample_size(100, 80, 4000) + end + + it "returns nil for an empty Array" do + [].sample.should be_nil + end + + it "returns nil for an empty array when called without n and a Random is given" do + [].sample(random: Random.new(42)).should be_nil + end + + it "returns a single value when not passed a count" do + [4].sample.should equal(4) + end + + it "returns a single value when not passed a count and a Random is given" do + [4].sample(random: Random.new(42)).should equal(4) + end + + it "returns a single value when not passed a count and a Random class is given" do + [4].sample(random: Random).should equal(4) + end + + it "returns an empty Array when passed zero" do + [4].sample(0).should == [] + end + + it "returns an Array of elements when passed a count" do + [1, 2, 3, 4].sample(3).should be_an_instance_of(Array) + end + + it "returns elements from the Array" do + array = [1, 2, 3, 4] + array.sample(3).all? { |x| array.should include(x) } + end + + it "returns at most the number of elements in the Array" do + array = [1, 2, 3, 4] + result = array.sample(20) + result.size.should == 4 + end + + it "does not return the same value if the Array has unique values" do + array = [1, 2, 3, 4] + result = array.sample(20) + result.sort.should == array + end + + it "may return the same value if the array is not unique" do + [4, 4].sample(2).should == [4,4] + end + + it "calls #to_int to convert the count when passed an Object" do + [1, 2, 3, 4].sample(mock_int(2)).size.should == 2 + end + + it "raises ArgumentError when passed a negative count" do + -> { [1, 2].sample(-1) }.should raise_error(ArgumentError) + end + + it "does not return subclass instances with Array subclass" do + ArraySpecs::MyArray[1, 2, 3].sample(2).should be_an_instance_of(Array) + end + + describe "with options" do + it "calls #rand on the Object passed by the :random key in the arguments Hash" do + obj = mock("array_sample_random") + obj.should_receive(:rand).and_return(0.5) + + [1, 2].sample(random: obj).should be_an_instance_of(Integer) + end + + it "raises a NoMethodError if an object passed for the RNG does not define #rand" do + obj = BasicObject.new + + -> { [1, 2].sample(random: obj) }.should raise_error(NoMethodError) + end + + describe "when the object returned by #rand is an Integer" do + it "uses the integer as index" do + random = mock("array_sample_random_ret") + random.should_receive(:rand).and_return(0) + + [1, 2].sample(random: random).should == 1 + + random = mock("array_sample_random_ret") + random.should_receive(:rand).and_return(1) + + [1, 2].sample(random: random).should == 2 + end + + it "raises a RangeError if the value is less than zero" do + random = mock("array_sample_random") + random.should_receive(:rand).and_return(-1) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is equal to the Array size" do + random = mock("array_sample_random") + random.should_receive(:rand).and_return(2) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is greater than the Array size" do + random = mock("array_sample_random") + random.should_receive(:rand).and_return(3) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end + end + end + + describe "when the object returned by #rand is not an Integer but responds to #to_int" do + it "calls #to_int on the Object" do + value = mock("array_sample_random_value") + value.should_receive(:to_int).and_return(1) + random = mock("array_sample_random") + random.should_receive(:rand).and_return(value) + + [1, 2].sample(random: random).should == 2 + end + + it "raises a RangeError if the value is less than zero" do + value = mock("array_sample_random_value") + value.should_receive(:to_int).and_return(-1) + random = mock("array_sample_random") + random.should_receive(:rand).and_return(value) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is equal to the Array size" do + value = mock("array_sample_random_value") + value.should_receive(:to_int).and_return(2) + random = mock("array_sample_random") + random.should_receive(:rand).and_return(value) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end + end +end diff --git a/spec/ruby/core/array/select_spec.rb b/spec/ruby/core/array/select_spec.rb new file mode 100644 index 0000000000..298b591744 --- /dev/null +++ b/spec/ruby/core/array/select_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require_relative 'shared/select' + +describe "Array#select" do + it_behaves_like :array_select, :select +end + +describe "Array#select!" do + it "returns nil if no changes were made in the array" do + [1, 2, 3].select! { true }.should be_nil + end + + it_behaves_like :keep_if, :select! +end diff --git a/spec/ruby/core/array/shared/clone.rb b/spec/ruby/core/array/shared/clone.rb new file mode 100644 index 0000000000..035b45ec99 --- /dev/null +++ b/spec/ruby/core/array/shared/clone.rb @@ -0,0 +1,20 @@ +describe :array_clone, shared: true do + it "returns an Array or a subclass instance" do + [].send(@method).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2].send(@method).should be_an_instance_of(ArraySpecs::MyArray) + end + + it "produces a shallow copy where the references are directly copied" do + a = [mock('1'), mock('2')] + b = a.send @method + b.first.should equal a.first + b.last.should equal a.last + end + + it "creates a new array containing all elements or the original" do + a = [1, 2, 3, 4] + b = a.send @method + b.should == a + b.__id__.should_not == a.__id__ + end +end diff --git a/spec/ruby/core/array/shared/collect.rb b/spec/ruby/core/array/shared/collect.rb new file mode 100644 index 0000000000..030302ced6 --- /dev/null +++ b/spec/ruby/core/array/shared/collect.rb @@ -0,0 +1,141 @@ +require_relative '../../enumerable/shared/enumeratorized' +require_relative '../shared/iterable_and_tolerating_size_increasing' + +describe :array_collect, shared: true do + it "returns a copy of array with each element replaced by the value returned by block" do + a = ['a', 'b', 'c', 'd'] + b = a.send(@method) { |i| i + '!' } + b.should == ["a!", "b!", "c!", "d!"] + b.should_not equal a + end + + it "does not return subclass instance" do + ArraySpecs::MyArray[1, 2, 3].send(@method) { |x| x + 1 }.should be_an_instance_of(Array) + end + + it "does not change self" do + a = ['a', 'b', 'c', 'd'] + a.send(@method) { |i| i + '!' } + a.should == ['a', 'b', 'c', 'd'] + end + + it "returns the evaluated value of block if it broke in the block" do + a = ['a', 'b', 'c', 'd'] + b = a.send(@method) {|i| + if i == 'c' + break 0 + else + i + '!' + end + } + b.should == 0 + end + + it "returns an Enumerator when no block given" do + a = [1, 2, 3] + a.send(@method).should be_an_instance_of(Enumerator) + end + + it "raises an ArgumentError when no block and with arguments" do + a = [1, 2, 3] + -> { + a.send(@method, :foo) + }.should raise_error(ArgumentError) + end + + before :all do + @object = [1, 2, 3, 4] + end + it_should_behave_like :enumeratorized_with_origin_size + + it_should_behave_like :array_iterable_and_tolerating_size_increasing +end + +describe :array_collect_b, shared: true do + it "replaces each element with the value returned by block" do + a = [7, 9, 3, 5] + a.send(@method) { |i| i - 1 }.should equal(a) + a.should == [6, 8, 2, 4] + end + + it "returns self" do + a = [1, 2, 3, 4, 5] + b = a.send(@method) {|i| i+1 } + a.should equal b + end + + it "returns the evaluated value of block but its contents is partially modified, if it broke in the block" do + a = ['a', 'b', 'c', 'd'] + b = a.send(@method) {|i| + if i == 'c' + break 0 + else + i + '!' + end + } + b.should == 0 + a.should == ['a!', 'b!', 'c', 'd'] + end + + it "returns an Enumerator when no block given, and the enumerator can modify the original array" do + a = [1, 2, 3] + enum = a.send(@method) + enum.should be_an_instance_of(Enumerator) + enum.each{|i| "#{i}!" } + a.should == ["1!", "2!", "3!"] + end + + describe "when frozen" do + it "raises a FrozenError" do + -> { ArraySpecs.frozen_array.send(@method) {} }.should raise_error(FrozenError) + end + + it "raises a FrozenError when empty" do + -> { ArraySpecs.empty_frozen_array.send(@method) {} }.should raise_error(FrozenError) + end + + it "raises a FrozenError when calling #each on the returned Enumerator" do + enumerator = ArraySpecs.frozen_array.send(@method) + -> { enumerator.each {|x| x } }.should raise_error(FrozenError) + end + + it "raises a FrozenError when calling #each on the returned Enumerator when empty" do + enumerator = ArraySpecs.empty_frozen_array.send(@method) + -> { enumerator.each {|x| x } }.should raise_error(FrozenError) + end + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.send(@method) { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "only changes elements before error is raised, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.send(@method) do |e| + case e + when 1 then -1 + when 2 then -2 + when 3 then raise StandardError, 'Oops' + else 0 + end + end + rescue StandardError + end + + a.should == [-1, -2, 3, 4] + end + + before :all do + @object = [1, 2, 3, 4] + end + it_should_behave_like :enumeratorized_with_origin_size + + it_should_behave_like :array_iterable_and_tolerating_size_increasing +end diff --git a/spec/ruby/core/array/shared/delete_if.rb b/spec/ruby/core/array/shared/delete_if.rb new file mode 100644 index 0000000000..a3fdcf4fac --- /dev/null +++ b/spec/ruby/core/array/shared/delete_if.rb @@ -0,0 +1,13 @@ +describe :delete_if, shared: true do + before :each do + @object = [1,2,3] + end + + it "updates the receiver after all blocks" do + @object.send(@method) do |e| + @object.length.should == 3 + true + end + @object.length.should == 0 + end +end diff --git a/spec/ruby/core/array/shared/difference.rb b/spec/ruby/core/array/shared/difference.rb new file mode 100644 index 0000000000..3e69050d82 --- /dev/null +++ b/spec/ruby/core/array/shared/difference.rb @@ -0,0 +1,78 @@ +describe :array_binary_difference, shared: true do + it "creates an array minus any items from other array" do + [].send(@method, [ 1, 2, 4 ]).should == [] + [1, 2, 4].send(@method, []).should == [1, 2, 4] + [ 1, 2, 3, 4, 5 ].send(@method, [ 1, 2, 4 ]).should == [3, 5] + end + + it "removes multiple items on the lhs equal to one on the rhs" do + [1, 1, 2, 2, 3, 3, 4, 5].send(@method, [1, 2, 4]).should == [3, 3, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method, empty).should == [] + + [].send(@method, ArraySpecs.recursive_array).should == [] + + array = ArraySpecs.recursive_array + array.send(@method, array).should == [] + end + + it "tries to convert the passed arguments to Arrays using #to_ary" do + obj = mock('[2,3,3,4]') + obj.should_receive(:to_ary).and_return([2, 3, 3, 4]) + [1, 1, 2, 2, 3, 4].send(@method, obj).should == [1, 1] + end + + it "raises a TypeError if the argument cannot be coerced to an Array by calling #to_ary" do + obj = mock('not an array') + -> { [1, 2, 3].send(@method, obj) }.should raise_error(TypeError) + end + + it "does not return subclass instance for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[]).should be_an_instance_of(Array) + [1, 2, 3].send(@method, ArraySpecs::MyArray[]).should be_an_instance_of(Array) + end + + it "does not call to_ary on array subclasses" do + [5, 6, 7].send(@method, ArraySpecs::ToAryArray[7]).should == [5, 6] + end + + it "removes an item identified as equivalent via #hash and #eql?" do + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.should_receive(:eql?).at_least(1).and_return(true) + + [obj1].send(@method, [obj2]).should == [] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [] + end + + it "doesn't remove an item with the same hash but not #eql?" do + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.should_receive(:eql?).at_least(1).and_return(false) + + [obj1].send(@method, [obj2]).should == [obj1] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj1] + end + + it "removes an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.stub!(:hash).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + [x].send(@method, [x]).should == [] + end + + it "is not destructive" do + a = [1, 2, 3] + a.send(@method, [1]) + a.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/shared/enumeratorize.rb b/spec/ruby/core/array/shared/enumeratorize.rb new file mode 100644 index 0000000000..a19a5d3b9b --- /dev/null +++ b/spec/ruby/core/array/shared/enumeratorize.rb @@ -0,0 +1,5 @@ +describe :enumeratorize, shared: true do + it "returns an Enumerator if no block given" do + [1,2].send(@method).should be_an_instance_of(Enumerator) + end +end diff --git a/spec/ruby/core/array/shared/eql.rb b/spec/ruby/core/array/shared/eql.rb new file mode 100644 index 0000000000..b5d9128434 --- /dev/null +++ b/spec/ruby/core/array/shared/eql.rb @@ -0,0 +1,92 @@ +describe :array_eql, shared: true do + it "returns true if other is the same array" do + a = [1] + a.send(@method, a).should be_true + end + + it "returns true if corresponding elements are #eql?" do + [].send(@method, []).should be_true + [1, 2, 3, 4].send(@method, [1, 2, 3, 4]).should be_true + end + + it "returns false if other is shorter than self" do + [1, 2, 3, 4].send(@method, [1, 2, 3]).should be_false + end + + it "returns false if other is longer than self" do + [1, 2, 3, 4].send(@method, [1, 2, 3, 4, 5]).should be_false + end + + it "returns false immediately when sizes of the arrays differ" do + obj = mock('1') + obj.should_not_receive(@method) + + [] .send(@method, [obj] ).should be_false + [obj] .send(@method, [] ).should be_false + end + + it "handles well recursive arrays" do + a = ArraySpecs.empty_recursive_array + a .send(@method, [a] ).should be_true + a .send(@method, [[a]] ).should be_true + [a] .send(@method, a ).should be_true + [[a]] .send(@method, a ).should be_true + # These may be surprising, but no difference can be + # found between these arrays, so they are ==. + # There is no "path" that will lead to a difference + # (contrary to other examples below) + + a2 = ArraySpecs.empty_recursive_array + a .send(@method, a2 ).should be_true + a .send(@method, [a2] ).should be_true + a .send(@method, [[a2]] ).should be_true + [a] .send(@method, a2 ).should be_true + [[a]] .send(@method, a2 ).should be_true + + back = [] + forth = [back]; back << forth; + back .send(@method, a ).should be_true + + x = []; x << x << x + x .send(@method, a ).should be_false # since x.size != a.size + x .send(@method, [a, a] ).should be_false # since x[0].size != [a, a][0].size + x .send(@method, [x, a] ).should be_false # since x[1].size != [x, a][1].size + [x, a] .send(@method, [a, x] ).should be_false # etc... + x .send(@method, [x, x] ).should be_true + x .send(@method, [[x, x], [x, x]] ).should be_true + + tree = []; + branch = []; branch << tree << tree; tree << branch + tree2 = []; + branch2 = []; branch2 << tree2 << tree2; tree2 << branch2 + forest = [tree, branch, :bird, a]; forest << forest + forest2 = [tree2, branch2, :bird, a2]; forest2 << forest2 + + forest .send(@method, forest2 ).should be_true + forest .send(@method, [tree2, branch, :bird, a, forest2]).should be_true + + diffforest = [branch2, tree2, :bird, a2]; diffforest << forest2 + forest .send(@method, diffforest ).should be_false # since forest[0].size == 1 != 3 == diffforest[0] + forest .send(@method, [nil] ).should be_false + forest .send(@method, [forest] ).should be_false + end + + it "does not call #to_ary on its argument" do + obj = mock('to_ary') + obj.should_not_receive(:to_ary) + + [1, 2, 3].send(@method, obj).should be_false + end + + it "does not call #to_ary on Array subclasses" do + ary = ArraySpecs::ToAryArray[5, 6, 7] + ary.should_not_receive(:to_ary) + [5, 6, 7].send(@method, ary).should be_true + end + + it "ignores array class differences" do + ArraySpecs::MyArray[1, 2, 3].send(@method, [1, 2, 3]).should be_true + ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_true + [1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_true + end +end diff --git a/spec/ruby/core/array/shared/index.rb b/spec/ruby/core/array/shared/index.rb new file mode 100644 index 0000000000..a4a0adbab6 --- /dev/null +++ b/spec/ruby/core/array/shared/index.rb @@ -0,0 +1,41 @@ +require_relative '../shared/iterable_and_tolerating_size_increasing' + +describe :array_index, shared: true do + it "returns the index of the first element == to object" do + x = mock('3') + def x.==(obj) 3 == obj; end + + [2, x, 3, 1, 3, 1].send(@method, 3).should == 1 + [2, 3.0, 3, x, 1, 3, 1].send(@method, x).should == 1 + end + + it "returns 0 if first element == to object" do + [2, 1, 3, 2, 5].send(@method, 2).should == 0 + end + + it "returns size-1 if only last element == to object" do + [2, 1, 3, 1, 5].send(@method, 5).should == 4 + end + + it "returns nil if no element == to object" do + [2, 1, 1, 1, 1].send(@method, 3).should == nil + end + + it "accepts a block instead of an argument" do + [4, 2, 1, 5, 1, 3].send(@method) {|x| x < 2}.should == 2 + end + + it "ignores the block if there is an argument" do + -> { + [4, 2, 1, 5, 1, 3].send(@method, 5) {|x| x < 2}.should == 3 + }.should complain(/given block not used/) + end + + describe "given no argument and no block" do + it "produces an Enumerator" do + [].send(@method).should be_an_instance_of(Enumerator) + end + end + + it_should_behave_like :array_iterable_and_tolerating_size_increasing +end diff --git a/spec/ruby/core/array/shared/inspect.rb b/spec/ruby/core/array/shared/inspect.rb new file mode 100644 index 0000000000..af5128c645 --- /dev/null +++ b/spec/ruby/core/array/shared/inspect.rb @@ -0,0 +1,107 @@ +require_relative '../fixtures/encoded_strings' + +describe :array_inspect, shared: true do + it "returns a string" do + [1, 2, 3].send(@method).should be_an_instance_of(String) + end + + it "returns '[]' for an empty Array" do + [].send(@method).should == "[]" + end + + it "calls inspect on its elements and joins the results with commas" do + items = Array.new(3) do |i| + obj = mock(i.to_s) + obj.should_receive(:inspect).and_return(i.to_s) + obj + end + items.send(@method).should == "[0, 1, 2]" + end + + it "does not call #to_s on a String returned from #inspect" do + str = +"abc" + str.should_not_receive(:to_s) + + [str].send(@method).should == '["abc"]' + end + + it "calls #to_s on the object returned from #inspect if the Object isn't a String" do + obj = mock("Array#inspect/to_s calls #to_s") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_return("abc") + + [obj].send(@method).should == "[abc]" + end + + it "does not call #to_str on the object returned from #inspect when it is not a String" do + obj = mock("Array#inspect/to_s does not call #to_str") + obj.should_receive(:inspect).and_return(obj) + obj.should_not_receive(:to_str) + + [obj].send(@method).should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/ + end + + it "does not call #to_str on the object returned from #to_s when it is not a String" do + obj = mock("Array#inspect/to_s does not call #to_str on #to_s result") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_return(obj) + obj.should_not_receive(:to_str) + + [obj].send(@method).should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/ + end + + it "does not swallow exceptions raised by #to_s" do + obj = mock("Array#inspect/to_s does not swallow #to_s exceptions") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_raise(Exception) + + -> { [obj].send(@method) }.should raise_error(Exception) + end + + it "represents a recursive element with '[...]'" do + ArraySpecs.recursive_array.send(@method).should == "[1, \"two\", 3.0, [...], [...], [...], [...], [...]]" + ArraySpecs.head_recursive_array.send(@method).should == "[[...], [...], [...], [...], [...], 1, \"two\", 3.0]" + ArraySpecs.empty_recursive_array.send(@method).should == "[[...]]" + end + + describe "with encoding" do + before :each do + @default_external_encoding = Encoding.default_external + end + + after :each do + Encoding.default_external = @default_external_encoding + end + + it "returns a US-ASCII string for an empty Array" do + [].send(@method).encoding.should == Encoding::US_ASCII + end + + it "use the default external encoding if it is ascii compatible" do + Encoding.default_external = Encoding.find('UTF-8') + + utf8 = "utf8".encode("UTF-8") + jp = "jp".encode("EUC-JP") + array = [jp, utf8] + + array.send(@method).encoding.name.should == "UTF-8" + end + + it "use US-ASCII encoding if the default external encoding is not ascii compatible" do + Encoding.default_external = Encoding.find('UTF-32') + + utf8 = "utf8".encode("UTF-8") + jp = "jp".encode("EUC-JP") + array = [jp, utf8] + + array.send(@method).encoding.name.should == "US-ASCII" + end + + it "does not raise if inspected result is not default external encoding" do + utf_16be = mock(+"utf_16be") + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) + + [utf_16be].send(@method).should == '["utf_16be \u3042"]' + end + end +end diff --git a/spec/ruby/core/array/shared/intersection.rb b/spec/ruby/core/array/shared/intersection.rb new file mode 100644 index 0000000000..0b4166ab63 --- /dev/null +++ b/spec/ruby/core/array/shared/intersection.rb @@ -0,0 +1,85 @@ +describe :array_intersection, shared: true do + it "creates an array with elements common to both arrays (intersection)" do + [].send(@method, []).should == [] + [1, 2].send(@method, []).should == [] + [].send(@method, [1, 2]).should == [] + [ 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).should == [1, 3] + end + + it "creates an array with no duplicates" do + [ 1, 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).uniq!.should == nil + end + + it "creates an array with elements in order they are first encountered" do + [ 1, 2, 3, 2, 5, 6, 7, 8 ].send(@method, [ 5, 2, 3, 4 ]).should == [2, 3, 5] # array > other + [ 5, 2, 3, 4 ].send(@method, [ 1, 2, 3, 2, 5, 6, 7, 8 ]).should == [5, 2, 3] # array < other + end + + it "does not modify the original Array" do + a = [1, 1, 3, 5] + a.send(@method, [1, 2, 3]).should == [1, 3] + a.should == [1, 1, 3, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method, empty).should == empty + + ArraySpecs.recursive_array.send(@method, []).should == [] + [].send(@method, ArraySpecs.recursive_array).should == [] + + ArraySpecs.recursive_array.send(@method, ArraySpecs.recursive_array).should == [1, 'two', 3.0, ArraySpecs.recursive_array] + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('[1,2,3]') + obj.should_receive(:to_ary).and_return([1, 2, 3]) + [1, 2].send(@method, obj).should == ([1, 2]) + end + + it "determines equivalence between elements in the sense of eql?" do + not_supported_on :opal do + [5.0, 4.0].send(@method, [5, 4]).should == [] + end + + str = "x" + [str].send(@method, [str.dup]).should == [str] + + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.should_receive(:eql?).at_least(1).and_return(true) + obj2.stub!(:eql?).and_return(true) + + [obj1].send(@method, [obj2]).should == [obj1] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1] + + obj1 = mock('3') + obj2 = mock('4') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.should_receive(:eql?).at_least(1).and_return(false) + + [obj1].send(@method, [obj2]).should == [] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj2] + end + + it "does return subclass instances for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array) + [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array) + end + + it "does not call to_ary on array subclasses" do + [5, 6].send(@method, ArraySpecs::ToAryArray[1, 2, 5, 6]).should == [5, 6] + end + + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.stub!(:hash).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + [x].send(@method, [x]).should == [x] + end +end diff --git a/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb b/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb new file mode 100644 index 0000000000..3e73bad44b --- /dev/null +++ b/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb @@ -0,0 +1,25 @@ +describe :array_iterable_and_tolerating_size_increasing, shared: true do + before do + @value_to_return ||= -> _ { nil } + end + + it "tolerates increasing an array size during iteration" do + # The goal is to trigger potential reallocation of internal array storage, so we: + # - use elements of different types, starting with the less generic (Integer) + # - add reasonably big number of new elements (~ 100) + array = [1, 2, 3] # to test some methods we need several uniq elements + array_to_join = [:a, :b, :c] + (4..100).to_a + + ScratchPad.record [] + i = 0 + + array.send(@method) do |e| + ScratchPad << e + array << array_to_join[i] if i < array_to_join.size + i += 1 + @value_to_return.call(e) + end + + ScratchPad.recorded.should == [1, 2, 3] + array_to_join + end +end diff --git a/spec/ruby/core/array/shared/join.rb b/spec/ruby/core/array/shared/join.rb new file mode 100644 index 0000000000..507b13e3c8 --- /dev/null +++ b/spec/ruby/core/array/shared/join.rb @@ -0,0 +1,112 @@ +require_relative '../fixtures/classes' +require_relative '../fixtures/encoded_strings' + +describe :array_join_with_default_separator, shared: true do + before :each do + @separator = $, + end + + after :each do + $, = @separator + end + + it "returns an empty string if the Array is empty" do + [].send(@method).should == '' + end + + it "returns a US-ASCII string for an empty Array" do + [].send(@method).encoding.should == Encoding::US_ASCII + end + + it "returns a string formed by concatenating each String element separated by $," do + suppress_warning { + $, = " | " + ["1", "2", "3"].send(@method).should == "1 | 2 | 3" + } + end + + it "attempts coercion via #to_str first" do + obj = mock('foo') + obj.should_receive(:to_str).any_number_of_times.and_return("foo") + [obj].send(@method).should == "foo" + end + + it "attempts coercion via #to_ary second" do + obj = mock('foo') + obj.should_receive(:to_str).any_number_of_times.and_return(nil) + obj.should_receive(:to_ary).any_number_of_times.and_return(["foo"]) + [obj].send(@method).should == "foo" + end + + it "attempts coercion via #to_s third" do + obj = mock('foo') + obj.should_receive(:to_str).any_number_of_times.and_return(nil) + obj.should_receive(:to_ary).any_number_of_times.and_return(nil) + obj.should_receive(:to_s).any_number_of_times.and_return("foo") + [obj].send(@method).should == "foo" + end + + it "raises a NoMethodError if an element does not respond to #to_str, #to_ary, or #to_s" do + obj = mock('o') + class << obj; undef :to_s; end + -> { [1, obj].send(@method) }.should raise_error(NoMethodError) + end + + it "raises an ArgumentError when the Array is recursive" do + -> { ArraySpecs.recursive_array.send(@method) }.should raise_error(ArgumentError) + -> { ArraySpecs.head_recursive_array.send(@method) }.should raise_error(ArgumentError) + -> { ArraySpecs.empty_recursive_array.send(@method) }.should raise_error(ArgumentError) + end + + it "uses the first encoding when other strings are compatible" do + ary1 = ArraySpecs.array_with_7bit_utf8_and_usascii_strings + ary2 = ArraySpecs.array_with_usascii_and_7bit_utf8_strings + ary3 = ArraySpecs.array_with_utf8_and_7bit_binary_strings + ary4 = ArraySpecs.array_with_usascii_and_7bit_binary_strings + + ary1.send(@method).encoding.should == Encoding::UTF_8 + ary2.send(@method).encoding.should == Encoding::US_ASCII + ary3.send(@method).encoding.should == Encoding::UTF_8 + ary4.send(@method).encoding.should == Encoding::US_ASCII + end + + it "uses the widest common encoding when other strings are incompatible" do + ary1 = ArraySpecs.array_with_utf8_and_usascii_strings + ary2 = ArraySpecs.array_with_usascii_and_utf8_strings + + ary1.send(@method).encoding.should == Encoding::UTF_8 + ary2.send(@method).encoding.should == Encoding::UTF_8 + end + + it "fails for arrays with incompatibly-encoded strings" do + ary_utf8_bad_binary = ArraySpecs.array_with_utf8_and_binary_strings + + -> { ary_utf8_bad_binary.send(@method) }.should raise_error(EncodingError) + end + + context "when $, is not nil" do + before do + suppress_warning do + $, = '*' + end + end + + it "warns" do + -> { [].join }.should complain(/warning: \$, is set to non-nil value/) + -> { [].join(nil) }.should complain(/warning: \$, is set to non-nil value/) + end + end +end + +describe :array_join_with_string_separator, shared: true do + it "returns a string formed by concatenating each element.to_str separated by separator" do + obj = mock('foo') + obj.should_receive(:to_str).and_return("foo") + [1, 2, 3, 4, obj].send(@method, ' | ').should == '1 | 2 | 3 | 4 | foo' + end + + it "uses the same separator with nested arrays" do + [1, [2, [3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6" + [1, [2, ArraySpecs::MyArray[3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6" + end +end diff --git a/spec/ruby/core/array/shared/keep_if.rb b/spec/ruby/core/array/shared/keep_if.rb new file mode 100644 index 0000000000..43a047c0a7 --- /dev/null +++ b/spec/ruby/core/array/shared/keep_if.rb @@ -0,0 +1,95 @@ +require_relative '../../enumerable/shared/enumeratorized' +require_relative '../shared/iterable_and_tolerating_size_increasing' + +describe :keep_if, shared: true do + it "deletes elements for which the block returns a false value" do + array = [1, 2, 3, 4, 5] + array.send(@method) {|item| item > 3 }.should equal(array) + array.should == [4, 5] + end + + it "returns an enumerator if no block is given" do + [1, 2, 3].send(@method).should be_an_instance_of(Enumerator) + end + + it "updates the receiver after all blocks" do + a = [1, 2, 3] + a.send(@method) do |e| + a.length.should == 3 + false + end + a.length.should == 0 + end + + before :all do + @object = [1,2,3] + end + it_should_behave_like :enumeratorized_with_origin_size + + describe "on frozen objects" do + before :each do + @origin = [true, false] + @frozen = @origin.dup.freeze + end + + it "returns an Enumerator if no block is given" do + @frozen.send(@method).should be_an_instance_of(Enumerator) + end + + describe "with truthy block" do + it "keeps elements after any exception" do + -> { @frozen.send(@method) { true } }.should raise_error(Exception) + @frozen.should == @origin + end + + it "raises a FrozenError" do + -> { @frozen.send(@method) { true } }.should raise_error(FrozenError) + end + end + + describe "with falsy block" do + it "keeps elements after any exception" do + -> { @frozen.send(@method) { false } }.should raise_error(Exception) + @frozen.should == @origin + end + + it "raises a FrozenError" do + -> { @frozen.send(@method) { false } }.should raise_error(FrozenError) + end + end + + it "raises a FrozenError on a frozen array only during iteration if called without a block" do + enum = @frozen.send(@method) + -> { enum.each {} }.should raise_error(FrozenError) + end + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.send(@method) { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "only changes elements before error is raised, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.send(@method) do |e| + case e + when 2 then false + when 3 then raise StandardError, 'Oops' + else true + end + end + rescue StandardError + end + + a.should == [1, 3, 4] + end + + @value_to_return = -> _ { true } + it_should_behave_like :array_iterable_and_tolerating_size_increasing +end diff --git a/spec/ruby/core/array/shared/length.rb b/spec/ruby/core/array/shared/length.rb new file mode 100644 index 0000000000..f84966d0ba --- /dev/null +++ b/spec/ruby/core/array/shared/length.rb @@ -0,0 +1,11 @@ +describe :array_length, shared: true do + it "returns the number of elements" do + [].send(@method).should == 0 + [1, 2, 3].send(@method).should == 3 + end + + it "properly handles recursive arrays" do + ArraySpecs.empty_recursive_array.send(@method).should == 1 + ArraySpecs.recursive_array.send(@method).should == 8 + end +end diff --git a/spec/ruby/core/array/shared/push.rb b/spec/ruby/core/array/shared/push.rb new file mode 100644 index 0000000000..ac790fb6a4 --- /dev/null +++ b/spec/ruby/core/array/shared/push.rb @@ -0,0 +1,33 @@ +describe :array_push, shared: true do + it "appends the arguments to the array" do + a = [ "a", "b", "c" ] + a.send(@method, "d", "e", "f").should equal(a) + a.send(@method).should == ["a", "b", "c", "d", "e", "f"] + a.send(@method, 5) + a.should == ["a", "b", "c", "d", "e", "f", 5] + + a = [0, 1] + a.send(@method, 2) + a.should == [0, 1, 2] + end + + it "isn't confused by previous shift" do + a = [ "a", "b", "c" ] + a.shift + a.send(@method, "foo") + a.should == ["b", "c", "foo"] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method, :last).should == [empty, :last] + + array = ArraySpecs.recursive_array + array.send(@method, :last).should == [1, 'two', 3.0, array, array, array, array, array, :last] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.send(@method, 1) }.should raise_error(FrozenError) + -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/shared/replace.rb b/spec/ruby/core/array/shared/replace.rb new file mode 100644 index 0000000000..9a6e60c1b0 --- /dev/null +++ b/spec/ruby/core/array/shared/replace.rb @@ -0,0 +1,60 @@ +describe :array_replace, shared: true do + it "replaces the elements with elements from other array" do + a = [1, 2, 3, 4, 5] + b = ['a', 'b', 'c'] + a.send(@method, b).should equal(a) + a.should == b + a.should_not equal(b) + + a.send(@method, [4] * 10) + a.should == [4] * 10 + + a.send(@method, []) + a.should == [] + end + + it "properly handles recursive arrays" do + orig = [1, 2, 3] + empty = ArraySpecs.empty_recursive_array + orig.send(@method, empty) + orig.should == empty + + array = ArraySpecs.recursive_array + orig.send(@method, array) + orig.should == array + end + + it "returns self" do + ary = [1, 2, 3] + other = [:a, :b, :c] + ary.send(@method, other).should equal(ary) + end + + it "does not make self dependent to the original array" do + ary = [1, 2, 3] + other = [:a, :b, :c] + ary.send(@method, other) + ary.should == [:a, :b, :c] + ary << :d + ary.should == [:a, :b, :c, :d] + other.should == [:a, :b, :c] + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('to_ary') + obj.stub!(:to_ary).and_return([1, 2, 3]) + [].send(@method, obj).should == [1, 2, 3] + end + + it "does not call #to_ary on Array subclasses" do + obj = ArraySpecs::ToAryArray[5, 6, 7] + obj.should_not_receive(:to_ary) + [].send(@method, ArraySpecs::ToAryArray[5, 6, 7]).should == [5, 6, 7] + end + + it "raises a FrozenError on a frozen array" do + -> { + ArraySpecs.frozen_array.send(@method, ArraySpecs.frozen_array) + }.should raise_error(FrozenError) + end +end diff --git a/spec/ruby/core/array/shared/select.rb b/spec/ruby/core/array/shared/select.rb new file mode 100644 index 0000000000..9c2cbf76c4 --- /dev/null +++ b/spec/ruby/core/array/shared/select.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/enumeratorize' +require_relative '../shared/keep_if' +require_relative '../shared/iterable_and_tolerating_size_increasing' +require_relative '../../enumerable/shared/enumeratorized' + +describe :array_select, shared: true do + it_should_behave_like :enumeratorize + + it_should_behave_like :array_iterable_and_tolerating_size_increasing + + before :each do + @object = [1,2,3] + end + it_should_behave_like :enumeratorized_with_origin_size + + it "returns a new array of elements for which block is true" do + [1, 3, 4, 5, 6, 9].send(@method) { |i| i % ((i + 1) / 2) == 0}.should == [1, 4, 6] + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].send(@method) { true }.should be_an_instance_of(Array) + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method) { true }.should == empty + empty.send(@method) { false }.should == [] + + array = ArraySpecs.recursive_array + array.send(@method) { true }.should == [1, 'two', 3.0, array, array, array, array, array] + array.send(@method) { false }.should == [] + end +end diff --git a/spec/ruby/core/array/shared/slice.rb b/spec/ruby/core/array/shared/slice.rb new file mode 100644 index 0000000000..b80261d32f --- /dev/null +++ b/spec/ruby/core/array/shared/slice.rb @@ -0,0 +1,857 @@ +describe :array_slice, shared: true do + it "returns the element at index with [index]" do + [ "a", "b", "c", "d", "e" ].send(@method, 1).should == "b" + + a = [1, 2, 3, 4] + + a.send(@method, 0).should == 1 + a.send(@method, 1).should == 2 + a.send(@method, 2).should == 3 + a.send(@method, 3).should == 4 + a.send(@method, 4).should == nil + a.send(@method, 10).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns the element at index from the end of the array with [-index]" do + [ "a", "b", "c", "d", "e" ].send(@method, -2).should == "d" + + a = [1, 2, 3, 4] + + a.send(@method, -1).should == 4 + a.send(@method, -2).should == 3 + a.send(@method, -3).should == 2 + a.send(@method, -4).should == 1 + a.send(@method, -5).should == nil + a.send(@method, -10).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns count elements starting from index with [index, count]" do + [ "a", "b", "c", "d", "e" ].send(@method, 2, 3).should == ["c", "d", "e"] + + a = [1, 2, 3, 4] + + a.send(@method, 0, 0).should == [] + a.send(@method, 0, 1).should == [1] + a.send(@method, 0, 2).should == [1, 2] + a.send(@method, 0, 4).should == [1, 2, 3, 4] + a.send(@method, 0, 6).should == [1, 2, 3, 4] + a.send(@method, 0, -1).should == nil + a.send(@method, 0, -2).should == nil + a.send(@method, 0, -4).should == nil + + a.send(@method, 2, 0).should == [] + a.send(@method, 2, 1).should == [3] + a.send(@method, 2, 2).should == [3, 4] + a.send(@method, 2, 4).should == [3, 4] + a.send(@method, 2, -1).should == nil + + a.send(@method, 4, 0).should == [] + a.send(@method, 4, 2).should == [] + a.send(@method, 4, -1).should == nil + + a.send(@method, 5, 0).should == nil + a.send(@method, 5, 2).should == nil + a.send(@method, 5, -1).should == nil + + a.send(@method, 6, 0).should == nil + a.send(@method, 6, 2).should == nil + a.send(@method, 6, -1).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns count elements starting at index from the end of array with [-index, count]" do + [ "a", "b", "c", "d", "e" ].send(@method, -2, 2).should == ["d", "e"] + + a = [1, 2, 3, 4] + + a.send(@method, -1, 0).should == [] + a.send(@method, -1, 1).should == [4] + a.send(@method, -1, 2).should == [4] + a.send(@method, -1, -1).should == nil + + a.send(@method, -2, 0).should == [] + a.send(@method, -2, 1).should == [3] + a.send(@method, -2, 2).should == [3, 4] + a.send(@method, -2, 4).should == [3, 4] + a.send(@method, -2, -1).should == nil + + a.send(@method, -4, 0).should == [] + a.send(@method, -4, 1).should == [1] + a.send(@method, -4, 2).should == [1, 2] + a.send(@method, -4, 4).should == [1, 2, 3, 4] + a.send(@method, -4, 6).should == [1, 2, 3, 4] + a.send(@method, -4, -1).should == nil + + a.send(@method, -5, 0).should == nil + a.send(@method, -5, 1).should == nil + a.send(@method, -5, 10).should == nil + a.send(@method, -5, -1).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns the first count elements with [0, count]" do + [ "a", "b", "c", "d", "e" ].send(@method, 0, 3).should == ["a", "b", "c"] + end + + it "returns the subarray which is independent to self with [index,count]" do + a = [1, 2, 3] + sub = a.send(@method, 1,2) + sub.replace([:a, :b]) + a.should == [1, 2, 3] + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.stub!(:to_int).and_return(2) + + a = [1, 2, 3, 4] + a.send(@method, obj).should == 3 + a.send(@method, obj, 1).should == [3] + a.send(@method, obj, obj).should == [3, 4] + a.send(@method, 0, obj).should == [1, 2] + end + + it "raises TypeError if to_int returns non-integer" do + from = mock('from') + to = mock('to') + + # So we can construct a range out of them... + def from.<=>(o) 0 end + def to.<=>(o) 0 end + + a = [1, 2, 3, 4, 5] + + def from.to_int() 'cat' end + def to.to_int() -2 end + + -> { a.send(@method, from..to) }.should raise_error(TypeError) + + def from.to_int() 1 end + def to.to_int() 'cat' end + + -> { a.send(@method, from..to) }.should raise_error(TypeError) + end + + it "returns the elements specified by Range indexes with [m..n]" do + [ "a", "b", "c", "d", "e" ].send(@method, 1..3).should == ["b", "c", "d"] + [ "a", "b", "c", "d", "e" ].send(@method, 4..-1).should == ['e'] + [ "a", "b", "c", "d", "e" ].send(@method, 3..3).should == ['d'] + [ "a", "b", "c", "d", "e" ].send(@method, 3..-2).should == ['d'] + ['a'].send(@method, 0..-1).should == ['a'] + + a = [1, 2, 3, 4] + + a.send(@method, 0..-10).should == [] + a.send(@method, 0..0).should == [1] + a.send(@method, 0..1).should == [1, 2] + a.send(@method, 0..2).should == [1, 2, 3] + a.send(@method, 0..3).should == [1, 2, 3, 4] + a.send(@method, 0..4).should == [1, 2, 3, 4] + a.send(@method, 0..10).should == [1, 2, 3, 4] + + a.send(@method, 2..-10).should == [] + a.send(@method, 2..0).should == [] + a.send(@method, 2..2).should == [3] + a.send(@method, 2..3).should == [3, 4] + a.send(@method, 2..4).should == [3, 4] + + a.send(@method, 3..0).should == [] + a.send(@method, 3..3).should == [4] + a.send(@method, 3..4).should == [4] + + a.send(@method, 4..0).should == [] + a.send(@method, 4..4).should == [] + a.send(@method, 4..5).should == [] + + a.send(@method, 5..0).should == nil + a.send(@method, 5..5).should == nil + a.send(@method, 5..6).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns elements specified by Range indexes except the element at index n with [m...n]" do + [ "a", "b", "c", "d", "e" ].send(@method, 1...3).should == ["b", "c"] + + a = [1, 2, 3, 4] + + a.send(@method, 0...-10).should == [] + a.send(@method, 0...0).should == [] + a.send(@method, 0...1).should == [1] + a.send(@method, 0...2).should == [1, 2] + a.send(@method, 0...3).should == [1, 2, 3] + a.send(@method, 0...4).should == [1, 2, 3, 4] + a.send(@method, 0...10).should == [1, 2, 3, 4] + + a.send(@method, 2...-10).should == [] + a.send(@method, 2...0).should == [] + a.send(@method, 2...2).should == [] + a.send(@method, 2...3).should == [3] + a.send(@method, 2...4).should == [3, 4] + + a.send(@method, 3...0).should == [] + a.send(@method, 3...3).should == [] + a.send(@method, 3...4).should == [4] + + a.send(@method, 4...0).should == [] + a.send(@method, 4...4).should == [] + a.send(@method, 4...5).should == [] + + a.send(@method, 5...0).should == nil + a.send(@method, 5...5).should == nil + a.send(@method, 5...6).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns elements that exist if range start is in the array but range end is not with [m..n]" do + [ "a", "b", "c", "d", "e" ].send(@method, 4..7).should == ["e"] + end + + it "accepts Range instances having a negative m and both signs for n with [m..n] and [m...n]" do + a = [1, 2, 3, 4] + + a.send(@method, -1..-1).should == [4] + a.send(@method, -1...-1).should == [] + a.send(@method, -1..3).should == [4] + a.send(@method, -1...3).should == [] + a.send(@method, -1..4).should == [4] + a.send(@method, -1...4).should == [4] + a.send(@method, -1..10).should == [4] + a.send(@method, -1...10).should == [4] + a.send(@method, -1..0).should == [] + a.send(@method, -1..-4).should == [] + a.send(@method, -1...-4).should == [] + a.send(@method, -1..-6).should == [] + a.send(@method, -1...-6).should == [] + + a.send(@method, -2..-2).should == [3] + a.send(@method, -2...-2).should == [] + a.send(@method, -2..-1).should == [3, 4] + a.send(@method, -2...-1).should == [3] + a.send(@method, -2..10).should == [3, 4] + a.send(@method, -2...10).should == [3, 4] + + a.send(@method, -4..-4).should == [1] + a.send(@method, -4..-2).should == [1, 2, 3] + a.send(@method, -4...-2).should == [1, 2] + a.send(@method, -4..-1).should == [1, 2, 3, 4] + a.send(@method, -4...-1).should == [1, 2, 3] + a.send(@method, -4..3).should == [1, 2, 3, 4] + a.send(@method, -4...3).should == [1, 2, 3] + a.send(@method, -4..4).should == [1, 2, 3, 4] + a.send(@method, -4...4).should == [1, 2, 3, 4] + a.send(@method, -4...4).should == [1, 2, 3, 4] + a.send(@method, -4..0).should == [1] + a.send(@method, -4...0).should == [] + a.send(@method, -4..1).should == [1, 2] + a.send(@method, -4...1).should == [1] + + a.send(@method, -5..-5).should == nil + a.send(@method, -5...-5).should == nil + a.send(@method, -5..-4).should == nil + a.send(@method, -5..-1).should == nil + a.send(@method, -5..10).should == nil + + a.should == [1, 2, 3, 4] + end + + it "returns the subarray which is independent to self with [m..n]" do + a = [1, 2, 3] + sub = a.send(@method, 1..2) + sub.replace([:a, :b]) + a.should == [1, 2, 3] + end + + it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do + from = mock('from') + to = mock('to') + + # So we can construct a range out of them... + def from.<=>(o) 0 end + def to.<=>(o) 0 end + + def from.to_int() 1 end + def to.to_int() -2 end + + a = [1, 2, 3, 4] + + a.send(@method, from..to).should == [2, 3] + a.send(@method, from...to).should == [2] + a.send(@method, 1..0).should == [] + a.send(@method, 1...0).should == [] + + -> { a.send(@method, "a" .. "b") }.should raise_error(TypeError) + -> { a.send(@method, "a" ... "b") }.should raise_error(TypeError) + -> { a.send(@method, from .. "b") }.should raise_error(TypeError) + -> { a.send(@method, from ... "b") }.should raise_error(TypeError) + end + + it "returns the same elements as [m..n] and [m...n] with Range subclasses" do + a = [1, 2, 3, 4] + range_incl = ArraySpecs::MyRange.new(1, 2) + range_excl = ArraySpecs::MyRange.new(-3, -1, true) + + a.send(@method, range_incl).should == [2, 3] + a.send(@method, range_excl).should == [2, 3] + end + + it "returns nil for a requested index not in the array with [index]" do + [ "a", "b", "c", "d", "e" ].send(@method, 5).should == nil + end + + it "returns [] if the index is valid but length is zero with [index, length]" do + [ "a", "b", "c", "d", "e" ].send(@method, 0, 0).should == [] + [ "a", "b", "c", "d", "e" ].send(@method, 2, 0).should == [] + end + + it "returns nil if length is zero but index is invalid with [index, length]" do + [ "a", "b", "c", "d", "e" ].send(@method, 100, 0).should == nil + [ "a", "b", "c", "d", "e" ].send(@method, -50, 0).should == nil + end + + # This is by design. It is in the official documentation. + it "returns [] if index == array.size with [index, length]" do + %w|a b c d e|.send(@method, 5, 2).should == [] + end + + it "returns nil if index > array.size with [index, length]" do + %w|a b c d e|.send(@method, 6, 2).should == nil + end + + it "returns nil if length is negative with [index, length]" do + %w|a b c d e|.send(@method, 3, -1).should == nil + %w|a b c d e|.send(@method, 2, -2).should == nil + %w|a b c d e|.send(@method, 1, -100).should == nil + end + + it "returns nil if no requested index is in the array with [m..n]" do + [ "a", "b", "c", "d", "e" ].send(@method, 6..10).should == nil + end + + it "returns nil if range start is not in the array with [m..n]" do + [ "a", "b", "c", "d", "e" ].send(@method, -10..2).should == nil + [ "a", "b", "c", "d", "e" ].send(@method, 10..12).should == nil + end + + it "returns an empty array when m == n with [m...n]" do + [1, 2, 3, 4, 5].send(@method, 1...1).should == [] + end + + it "returns an empty array with [0...0]" do + [1, 2, 3, 4, 5].send(@method, 0...0).should == [] + end + + it "returns a subarray where m, n negatives and m < n with [m..n]" do + [ "a", "b", "c", "d", "e" ].send(@method, -3..-2).should == ["c", "d"] + end + + it "returns an array containing the first element with [0..0]" do + [1, 2, 3, 4, 5].send(@method, 0..0).should == [1] + end + + it "returns the entire array with [0..-1]" do + [1, 2, 3, 4, 5].send(@method, 0..-1).should == [1, 2, 3, 4, 5] + end + + it "returns all but the last element with [0...-1]" do + [1, 2, 3, 4, 5].send(@method, 0...-1).should == [1, 2, 3, 4] + end + + it "returns [3] for [2..-1] out of [1, 2, 3]" do + [1,2,3].send(@method, 2..-1).should == [3] + end + + it "returns an empty array when m > n and m, n are positive with [m..n]" do + [1, 2, 3, 4, 5].send(@method, 3..2).should == [] + end + + it "returns an empty array when m > n and m, n are negative with [m..n]" do + [1, 2, 3, 4, 5].send(@method, -2..-3).should == [] + end + + it "does not expand array when the indices are outside of the array bounds" do + a = [1, 2] + a.send(@method, 4).should == nil + a.should == [1, 2] + a.send(@method, 4, 0).should == nil + a.should == [1, 2] + a.send(@method, 6, 1).should == nil + a.should == [1, 2] + a.send(@method, 8...8).should == nil + a.should == [1, 2] + a.send(@method, 10..10).should == nil + a.should == [1, 2] + end + + describe "with a subclass of Array" do + before :each do + ScratchPad.clear + + @array = ArraySpecs::MyArray[1, 2, 3, 4, 5] + end + + it "returns a Array instance with [n, m]" do + @array.send(@method, 0, 2).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n, m]" do + @array.send(@method, -3, 2).should be_an_instance_of(Array) + end + + it "returns a Array instance with [n..m]" do + @array.send(@method, 1..3).should be_an_instance_of(Array) + end + + it "returns a Array instance with [n...m]" do + @array.send(@method, 1...3).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n..-m]" do + @array.send(@method, -3..-1).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n...-m]" do + @array.send(@method, -3...-1).should be_an_instance_of(Array) + end + + it "returns an empty array when m == n with [m...n]" do + @array.send(@method, 1...1).should == [] + ScratchPad.recorded.should be_nil + end + + it "returns an empty array with [0...0]" do + @array.send(@method, 0...0).should == [] + ScratchPad.recorded.should be_nil + end + + it "returns an empty array when m > n and m, n are positive with [m..n]" do + @array.send(@method, 3..2).should == [] + ScratchPad.recorded.should be_nil + end + + it "returns an empty array when m > n and m, n are negative with [m..n]" do + @array.send(@method, -2..-3).should == [] + ScratchPad.recorded.should be_nil + end + + it "returns [] if index == array.size with [index, length]" do + @array.send(@method, 5, 2).should == [] + ScratchPad.recorded.should be_nil + end + + it "returns [] if the index is valid but length is zero with [index, length]" do + @array.send(@method, 0, 0).should == [] + @array.send(@method, 2, 0).should == [] + ScratchPad.recorded.should be_nil + end + + it "does not call #initialize on the subclass instance" do + @array.send(@method, 0, 3).should == [1, 2, 3] + ScratchPad.recorded.should be_nil + end + end + + it "raises a RangeError when the start index is out of range of Fixnum" do + array = [1, 2, 3, 4, 5, 6] + obj = mock('large value') + obj.should_receive(:to_int).and_return(bignum_value) + -> { array.send(@method, obj) }.should raise_error(RangeError) + + obj = 8e19 + -> { array.send(@method, obj) }.should raise_error(RangeError) + + # boundary value when longs are 64 bits + -> { array.send(@method, 2.0**63) }.should raise_error(RangeError) + + # just under the boundary value when longs are 64 bits + array.send(@method, max_long.to_f.prev_float).should == nil + end + + it "raises a RangeError when the length is out of range of Fixnum" do + array = [1, 2, 3, 4, 5, 6] + obj = mock('large value') + obj.should_receive(:to_int).and_return(bignum_value) + -> { array.send(@method, 1, obj) }.should raise_error(RangeError) + + obj = 8e19 + -> { array.send(@method, 1, obj) }.should raise_error(RangeError) + end + + it "raises a type error if a range is passed with a length" do + ->{ [1, 2, 3].send(@method, 1..2, 1) }.should raise_error(TypeError) + end + + it "raises a RangeError if passed a range with a bound that is too large" do + array = [1, 2, 3, 4, 5, 6] + -> { array.send(@method, bignum_value..(bignum_value + 1)) }.should raise_error(RangeError) + -> { array.send(@method, 0..bignum_value) }.should raise_error(RangeError) + end + + it "can accept endless ranges" do + a = [0, 1, 2, 3, 4, 5] + a.send(@method, eval("(2..)")).should == [2, 3, 4, 5] + a.send(@method, eval("(2...)")).should == [2, 3, 4, 5] + a.send(@method, eval("(-2..)")).should == [4, 5] + a.send(@method, eval("(-2...)")).should == [4, 5] + a.send(@method, eval("(9..)")).should == nil + a.send(@method, eval("(9...)")).should == nil + a.send(@method, eval("(-9..)")).should == nil + a.send(@method, eval("(-9...)")).should == nil + end + + describe "can be sliced with Enumerator::ArithmeticSequence" do + before :each do + @array = [0, 1, 2, 3, 4, 5] + end + + it "has endless range and positive steps" do + @array.send(@method, eval("(0..).step(1)")).should == [0, 1, 2, 3, 4, 5] + @array.send(@method, eval("(0..).step(2)")).should == [0, 2, 4] + @array.send(@method, eval("(0..).step(10)")).should == [0] + + @array.send(@method, eval("(2..).step(1)")).should == [2, 3, 4, 5] + @array.send(@method, eval("(2..).step(2)")).should == [2, 4] + @array.send(@method, eval("(2..).step(10)")).should == [2] + + @array.send(@method, eval("(-3..).step(1)")).should == [3, 4, 5] + @array.send(@method, eval("(-3..).step(2)")).should == [3, 5] + @array.send(@method, eval("(-3..).step(10)")).should == [3] + end + + it "has beginless range and positive steps" do + # end with zero index + @array.send(@method, (..0).step(1)).should == [0] + @array.send(@method, (...0).step(1)).should == [] + + @array.send(@method, (..0).step(2)).should == [0] + @array.send(@method, (...0).step(2)).should == [] + + @array.send(@method, (..0).step(10)).should == [0] + @array.send(@method, (...0).step(10)).should == [] + + # end with positive index + @array.send(@method, (..3).step(1)).should == [0, 1, 2, 3] + @array.send(@method, (...3).step(1)).should == [0, 1, 2] + + @array.send(@method, (..3).step(2)).should == [0, 2] + @array.send(@method, (...3).step(2)).should == [0, 2] + + @array.send(@method, (..3).step(10)).should == [0] + @array.send(@method, (...3).step(10)).should == [0] + + # end with negative index + @array.send(@method, (..-2).step(1)).should == [0, 1, 2, 3, 4,] + @array.send(@method, (...-2).step(1)).should == [0, 1, 2, 3] + + @array.send(@method, (..-2).step(2)).should == [0, 2, 4] + @array.send(@method, (...-2).step(2)).should == [0, 2] + + @array.send(@method, (..-2).step(10)).should == [0] + @array.send(@method, (...-2).step(10)).should == [0] + end + + it "has endless range and negative steps" do + @array.send(@method, eval("(0..).step(-1)")).should == [0] + @array.send(@method, eval("(0..).step(-2)")).should == [0] + @array.send(@method, eval("(0..).step(-10)")).should == [0] + + @array.send(@method, eval("(2..).step(-1)")).should == [2, 1, 0] + @array.send(@method, eval("(2..).step(-2)")).should == [2, 0] + + @array.send(@method, eval("(-3..).step(-1)")).should == [3, 2, 1, 0] + @array.send(@method, eval("(-3..).step(-2)")).should == [3, 1] + end + + it "has closed range and positive steps" do + # start and end with 0 + @array.send(@method, eval("(0..0).step(1)")).should == [0] + @array.send(@method, eval("(0...0).step(1)")).should == [] + + @array.send(@method, eval("(0..0).step(2)")).should == [0] + @array.send(@method, eval("(0...0).step(2)")).should == [] + + @array.send(@method, eval("(0..0).step(10)")).should == [0] + @array.send(@method, eval("(0...0).step(10)")).should == [] + + # start and end with positive index + @array.send(@method, eval("(1..3).step(1)")).should == [1, 2, 3] + @array.send(@method, eval("(1...3).step(1)")).should == [1, 2] + + @array.send(@method, eval("(1..3).step(2)")).should == [1, 3] + @array.send(@method, eval("(1...3).step(2)")).should == [1] + + @array.send(@method, eval("(1..3).step(10)")).should == [1] + @array.send(@method, eval("(1...3).step(10)")).should == [1] + + # start with positive index, end with negative index + @array.send(@method, eval("(1..-2).step(1)")).should == [1, 2, 3, 4] + @array.send(@method, eval("(1...-2).step(1)")).should == [1, 2, 3] + + @array.send(@method, eval("(1..-2).step(2)")).should == [1, 3] + @array.send(@method, eval("(1...-2).step(2)")).should == [1, 3] + + @array.send(@method, eval("(1..-2).step(10)")).should == [1] + @array.send(@method, eval("(1...-2).step(10)")).should == [1] + + # start with negative index, end with positive index + @array.send(@method, eval("(-4..4).step(1)")).should == [2, 3, 4] + @array.send(@method, eval("(-4...4).step(1)")).should == [2, 3] + + @array.send(@method, eval("(-4..4).step(2)")).should == [2, 4] + @array.send(@method, eval("(-4...4).step(2)")).should == [2] + + @array.send(@method, eval("(-4..4).step(10)")).should == [2] + @array.send(@method, eval("(-4...4).step(10)")).should == [2] + + # start with negative index, end with negative index + @array.send(@method, eval("(-4..-2).step(1)")).should == [2, 3, 4] + @array.send(@method, eval("(-4...-2).step(1)")).should == [2, 3] + + @array.send(@method, eval("(-4..-2).step(2)")).should == [2, 4] + @array.send(@method, eval("(-4...-2).step(2)")).should == [2] + + @array.send(@method, eval("(-4..-2).step(10)")).should == [2] + @array.send(@method, eval("(-4...-2).step(10)")).should == [2] + end + + it "has closed range and negative steps" do + # start and end with 0 + @array.send(@method, eval("(0..0).step(-1)")).should == [0] + @array.send(@method, eval("(0...0).step(-1)")).should == [] + + @array.send(@method, eval("(0..0).step(-2)")).should == [0] + @array.send(@method, eval("(0...0).step(-2)")).should == [] + + @array.send(@method, eval("(0..0).step(-10)")).should == [0] + @array.send(@method, eval("(0...0).step(-10)")).should == [] + + # start and end with positive index + @array.send(@method, eval("(1..3).step(-1)")).should == [] + @array.send(@method, eval("(1...3).step(-1)")).should == [] + + @array.send(@method, eval("(1..3).step(-2)")).should == [] + @array.send(@method, eval("(1...3).step(-2)")).should == [] + + @array.send(@method, eval("(1..3).step(-10)")).should == [] + @array.send(@method, eval("(1...3).step(-10)")).should == [] + + # start with positive index, end with negative index + @array.send(@method, eval("(1..-2).step(-1)")).should == [] + @array.send(@method, eval("(1...-2).step(-1)")).should == [] + + @array.send(@method, eval("(1..-2).step(-2)")).should == [] + @array.send(@method, eval("(1...-2).step(-2)")).should == [] + + @array.send(@method, eval("(1..-2).step(-10)")).should == [] + @array.send(@method, eval("(1...-2).step(-10)")).should == [] + + # start with negative index, end with positive index + @array.send(@method, eval("(-4..4).step(-1)")).should == [] + @array.send(@method, eval("(-4...4).step(-1)")).should == [] + + @array.send(@method, eval("(-4..4).step(-2)")).should == [] + @array.send(@method, eval("(-4...4).step(-2)")).should == [] + + @array.send(@method, eval("(-4..4).step(-10)")).should == [] + @array.send(@method, eval("(-4...4).step(-10)")).should == [] + + # start with negative index, end with negative index + @array.send(@method, eval("(-4..-2).step(-1)")).should == [] + @array.send(@method, eval("(-4...-2).step(-1)")).should == [] + + @array.send(@method, eval("(-4..-2).step(-2)")).should == [] + @array.send(@method, eval("(-4...-2).step(-2)")).should == [] + + @array.send(@method, eval("(-4..-2).step(-10)")).should == [] + @array.send(@method, eval("(-4...-2).step(-10)")).should == [] + end + + it "has inverted closed range and positive steps" do + # start and end with positive index + @array.send(@method, eval("(3..1).step(1)")).should == [] + @array.send(@method, eval("(3...1).step(1)")).should == [] + + @array.send(@method, eval("(3..1).step(2)")).should == [] + @array.send(@method, eval("(3...1).step(2)")).should == [] + + @array.send(@method, eval("(3..1).step(10)")).should == [] + @array.send(@method, eval("(3...1).step(10)")).should == [] + + # start with negative index, end with positive index + @array.send(@method, eval("(-2..1).step(1)")).should == [] + @array.send(@method, eval("(-2...1).step(1)")).should == [] + + @array.send(@method, eval("(-2..1).step(2)")).should == [] + @array.send(@method, eval("(-2...1).step(2)")).should == [] + + @array.send(@method, eval("(-2..1).step(10)")).should == [] + @array.send(@method, eval("(-2...1).step(10)")).should == [] + + # start with positive index, end with negative index + @array.send(@method, eval("(4..-4).step(1)")).should == [] + @array.send(@method, eval("(4...-4).step(1)")).should == [] + + @array.send(@method, eval("(4..-4).step(2)")).should == [] + @array.send(@method, eval("(4...-4).step(2)")).should == [] + + @array.send(@method, eval("(4..-4).step(10)")).should == [] + @array.send(@method, eval("(4...-4).step(10)")).should == [] + + # start with negative index, end with negative index + @array.send(@method, eval("(-2..-4).step(1)")).should == [] + @array.send(@method, eval("(-2...-4).step(1)")).should == [] + + @array.send(@method, eval("(-2..-4).step(2)")).should == [] + @array.send(@method, eval("(-2...-4).step(2)")).should == [] + + @array.send(@method, eval("(-2..-4).step(10)")).should == [] + @array.send(@method, eval("(-2...-4).step(10)")).should == [] + end + + it "has range with bounds outside of array" do + # end is equal to array's length + @array.send(@method, (0..6).step(1)).should == [0, 1, 2, 3, 4, 5] + -> { @array.send(@method, (0..6).step(2)) }.should raise_error(RangeError) + + # end is greater than length with positive steps + @array.send(@method, (1..6).step(2)).should == [1, 3, 5] + @array.send(@method, (2..7).step(2)).should == [2, 4] + -> { @array.send(@method, (2..8).step(2)) }.should raise_error(RangeError) + + # begin is greater than length with negative steps + @array.send(@method, (6..1).step(-2)).should == [5, 3, 1] + @array.send(@method, (7..2).step(-2)).should == [5, 3] + -> { @array.send(@method, (8..2).step(-2)) }.should raise_error(RangeError) + end + + it "has endless range with start outside of array's bounds" do + @array.send(@method, eval("(6..).step(1)")).should == [] + @array.send(@method, eval("(7..).step(1)")).should == nil + + @array.send(@method, eval("(6..).step(2)")).should == [] + -> { @array.send(@method, eval("(7..).step(2)")) }.should raise_error(RangeError) + end + end + + it "can accept beginless ranges" do + a = [0, 1, 2, 3, 4, 5] + a.send(@method, (..3)).should == [0, 1, 2, 3] + a.send(@method, (...3)).should == [0, 1, 2] + a.send(@method, (..-3)).should == [0, 1, 2, 3] + a.send(@method, (...-3)).should == [0, 1, 2] + a.send(@method, (..0)).should == [0] + a.send(@method, (...0)).should == [] + a.send(@method, (..9)).should == [0, 1, 2, 3, 4, 5] + a.send(@method, (...9)).should == [0, 1, 2, 3, 4, 5] + a.send(@method, (..-9)).should == [] + a.send(@method, (...-9)).should == [] + end + + describe "can be sliced with Enumerator::ArithmeticSequence" do + it "with infinite/inverted ranges and negative steps" do + @array = [0, 1, 2, 3, 4, 5] + @array.send(@method, (2..).step(-1)).should == [2, 1, 0] + @array.send(@method, (2..).step(-2)).should == [2, 0] + @array.send(@method, (2..).step(-3)).should == [2] + @array.send(@method, (2..).step(-4)).should == [2] + + @array.send(@method, (-3..).step(-1)).should == [3, 2, 1, 0] + @array.send(@method, (-3..).step(-2)).should == [3, 1] + @array.send(@method, (-3..).step(-3)).should == [3, 0] + @array.send(@method, (-3..).step(-4)).should == [3] + @array.send(@method, (-3..).step(-5)).should == [3] + + @array.send(@method, (..0).step(-1)).should == [5, 4, 3, 2, 1, 0] + @array.send(@method, (..0).step(-2)).should == [5, 3, 1] + @array.send(@method, (..0).step(-3)).should == [5, 2] + @array.send(@method, (..0).step(-4)).should == [5, 1] + @array.send(@method, (..0).step(-5)).should == [5, 0] + @array.send(@method, (..0).step(-6)).should == [5] + @array.send(@method, (..0).step(-7)).should == [5] + + @array.send(@method, (...0).step(-1)).should == [5, 4, 3, 2, 1] + @array.send(@method, (...0).step(-2)).should == [5, 3, 1] + @array.send(@method, (...0).step(-3)).should == [5, 2] + @array.send(@method, (...0).step(-4)).should == [5, 1] + @array.send(@method, (...0).step(-5)).should == [5] + @array.send(@method, (...0).step(-6)).should == [5] + + @array.send(@method, (...1).step(-1)).should == [5, 4, 3, 2] + @array.send(@method, (...1).step(-2)).should == [5, 3] + @array.send(@method, (...1).step(-3)).should == [5, 2] + @array.send(@method, (...1).step(-4)).should == [5] + @array.send(@method, (...1).step(-5)).should == [5] + + @array.send(@method, (..-5).step(-1)).should == [5, 4, 3, 2, 1] + @array.send(@method, (..-5).step(-2)).should == [5, 3, 1] + @array.send(@method, (..-5).step(-3)).should == [5, 2] + @array.send(@method, (..-5).step(-4)).should == [5, 1] + @array.send(@method, (..-5).step(-5)).should == [5] + @array.send(@method, (..-5).step(-6)).should == [5] + + @array.send(@method, (...-5).step(-1)).should == [5, 4, 3, 2] + @array.send(@method, (...-5).step(-2)).should == [5, 3] + @array.send(@method, (...-5).step(-3)).should == [5, 2] + @array.send(@method, (...-5).step(-4)).should == [5] + @array.send(@method, (...-5).step(-5)).should == [5] + + @array.send(@method, (4..1).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (4..1).step(-2)).should == [4, 2] + @array.send(@method, (4..1).step(-3)).should == [4, 1] + @array.send(@method, (4..1).step(-4)).should == [4] + @array.send(@method, (4..1).step(-5)).should == [4] + + @array.send(@method, (4...1).step(-1)).should == [4, 3, 2] + @array.send(@method, (4...1).step(-2)).should == [4, 2] + @array.send(@method, (4...1).step(-3)).should == [4] + @array.send(@method, (4...1).step(-4)).should == [4] + + @array.send(@method, (-2..1).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (-2..1).step(-2)).should == [4, 2] + @array.send(@method, (-2..1).step(-3)).should == [4, 1] + @array.send(@method, (-2..1).step(-4)).should == [4] + @array.send(@method, (-2..1).step(-5)).should == [4] + + @array.send(@method, (-2...1).step(-1)).should == [4, 3, 2] + @array.send(@method, (-2...1).step(-2)).should == [4, 2] + @array.send(@method, (-2...1).step(-3)).should == [4] + @array.send(@method, (-2...1).step(-4)).should == [4] + + @array.send(@method, (4..-5).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (4..-5).step(-2)).should == [4, 2] + @array.send(@method, (4..-5).step(-3)).should == [4, 1] + @array.send(@method, (4..-5).step(-4)).should == [4] + @array.send(@method, (4..-5).step(-5)).should == [4] + + @array.send(@method, (4...-5).step(-1)).should == [4, 3, 2] + @array.send(@method, (4...-5).step(-2)).should == [4, 2] + @array.send(@method, (4...-5).step(-3)).should == [4] + @array.send(@method, (4...-5).step(-4)).should == [4] + + @array.send(@method, (-2..-5).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (-2..-5).step(-2)).should == [4, 2] + @array.send(@method, (-2..-5).step(-3)).should == [4, 1] + @array.send(@method, (-2..-5).step(-4)).should == [4] + @array.send(@method, (-2..-5).step(-5)).should == [4] + + @array.send(@method, (-2...-5).step(-1)).should == [4, 3, 2] + @array.send(@method, (-2...-5).step(-2)).should == [4, 2] + @array.send(@method, (-2...-5).step(-3)).should == [4] + @array.send(@method, (-2...-5).step(-4)).should == [4] + end + end + + it "can accept nil...nil ranges" do + a = [0, 1, 2, 3, 4, 5] + a.send(@method, eval("(nil...nil)")).should == a + a.send(@method, (...nil)).should == a + a.send(@method, eval("(nil..)")).should == a + end +end diff --git a/spec/ruby/core/array/shared/union.rb b/spec/ruby/core/array/shared/union.rb new file mode 100644 index 0000000000..0b60df9ca4 --- /dev/null +++ b/spec/ruby/core/array/shared/union.rb @@ -0,0 +1,79 @@ +describe :array_binary_union, shared: true do + it "returns an array of elements that appear in either array (union)" do + [].send(@method, []).should == [] + [1, 2].send(@method, []).should == [1, 2] + [].send(@method, [1, 2]).should == [1, 2] + [ 1, 2, 3, 4 ].send(@method, [ 3, 4, 5 ]).should == [1, 2, 3, 4, 5] + end + + it "creates an array with no duplicates" do + [ 1, 2, 3, 1, 4, 5 ].send(@method, [ 1, 3, 4, 5, 3, 6 ]).should == [1, 2, 3, 4, 5, 6] + end + + it "creates an array with elements in order they are first encountered" do + [ 1, 2, 3, 1 ].send(@method, [ 1, 3, 4, 5 ]).should == [1, 2, 3, 4, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method, empty).should == empty + + array = ArraySpecs.recursive_array + array.send(@method, []).should == [1, 'two', 3.0, array] + [].send(@method, array).should == [1, 'two', 3.0, array] + array.send(@method, array).should == [1, 'two', 3.0, array] + array.send(@method, empty).should == [1, 'two', 3.0, array, empty] + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('[1,2,3]') + obj.should_receive(:to_ary).and_return([1, 2, 3]) + [0].send(@method, obj).should == ([0] | [1, 2, 3]) + end + + # MRI follows hashing semantics here, so doesn't actually call eql?/hash for Integer/Symbol + it "acts as if using an intermediate hash to collect values" do + not_supported_on :opal do + [5.0, 4.0].send(@method, [5, 4]).should == [5.0, 4.0, 5, 4] + end + + str = "x" + [str].send(@method, [str.dup]).should == [str] + + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj2.should_receive(:eql?).at_least(1).and_return(true) + + [obj1].send(@method, [obj2]).should == [obj1] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1] + + obj1 = mock('3') + obj2 = mock('4') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj2.should_receive(:eql?).at_least(1).and_return(false) + + [obj1].send(@method, [obj2]).should == [obj1, obj2] + [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj2] + end + + it "does not return subclass instances for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].send(@method, []).should be_an_instance_of(Array) + ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array) + [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should be_an_instance_of(Array) + end + + it "does not call to_ary on array subclasses" do + [1, 2].send(@method, ArraySpecs::ToAryArray[5, 6]).should == [1, 2, 5, 6] + end + + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.stub!(:hash).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + [x].send(@method, [x]).should == [x] + end +end diff --git a/spec/ruby/core/array/shared/unshift.rb b/spec/ruby/core/array/shared/unshift.rb new file mode 100644 index 0000000000..9e0fe7556a --- /dev/null +++ b/spec/ruby/core/array/shared/unshift.rb @@ -0,0 +1,64 @@ +describe :array_unshift, shared: true do + it "prepends object to the original array" do + a = [1, 2, 3] + a.send(@method, "a").should equal(a) + a.should == ['a', 1, 2, 3] + a.send(@method).should equal(a) + a.should == ['a', 1, 2, 3] + a.send(@method, 5, 4, 3) + a.should == [5, 4, 3, 'a', 1, 2, 3] + + # shift all but one element + a = [1, 2] + a.shift + a.send(@method, 3, 4) + a.should == [3, 4, 2] + + # now shift all elements + a.shift + a.shift + a.shift + a.send(@method, 3, 4) + a.should == [3, 4] + end + + it "returns self" do + a = [1, 2, 3] + a.send(@method, "a").should.equal?(a) + end + + it "quietly ignores unshifting nothing" do + [].send(@method).should == [] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.send(@method, :new).should == [:new, empty] + + array = ArraySpecs.recursive_array + array.send(@method, :new) + array[0..5].should == [:new, 1, 'two', 3.0, array, array] + end + + it "raises a FrozenError on a frozen array when the array is modified" do + -> { ArraySpecs.frozen_array.send(@method, 1) }.should raise_error(FrozenError) + end + + # see [ruby-core:23666] + it "raises a FrozenError on a frozen array when the array would not be modified" do + -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError) + end + + # https://github.com/truffleruby/truffleruby/issues/2772 + it "doesn't rely on Array#[]= so it can be overridden" do + subclass = Class.new(Array) do + def []=(*) + raise "[]= is called" + end + end + + array = subclass.new + array.send(@method, 1) + array.should == [1] + end +end diff --git a/spec/ruby/core/array/shift_spec.rb b/spec/ruby/core/array/shift_spec.rb new file mode 100644 index 0000000000..6b4ef39f77 --- /dev/null +++ b/spec/ruby/core/array/shift_spec.rb @@ -0,0 +1,120 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#shift" do + it "removes and returns the first element" do + a = [5, 1, 1, 5, 4] + a.shift.should == 5 + a.should == [1, 1, 5, 4] + a.shift.should == 1 + a.should == [1, 5, 4] + a.shift.should == 1 + a.should == [5, 4] + a.shift.should == 5 + a.should == [4] + a.shift.should == 4 + a.should == [] + end + + it "returns nil when the array is empty" do + [].shift.should == nil + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.shift.should == [] + empty.should == [] + + array = ArraySpecs.recursive_array + array.shift.should == 1 + array[0..2].should == ['two', 3.0, array] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.shift }.should raise_error(FrozenError) + end + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.shift }.should raise_error(FrozenError) + end + + describe "passed a number n as an argument" do + it "removes and returns an array with the first n element of the array" do + a = [1, 2, 3, 4, 5, 6] + + a.shift(0).should == [] + a.should == [1, 2, 3, 4, 5, 6] + + a.shift(1).should == [1] + a.should == [2, 3, 4, 5, 6] + + a.shift(2).should == [2, 3] + a.should == [4, 5, 6] + + a.shift(3).should == [4, 5, 6] + a.should == [] + end + + it "does not corrupt the array when shift without arguments is followed by shift with an argument" do + a = [1, 2, 3, 4, 5] + + a.shift.should == 1 + a.shift(3).should == [2, 3, 4] + a.should == [5] + end + + it "returns a new empty array if there are no more elements" do + a = [] + popped1 = a.shift(1) + popped1.should == [] + a.should == [] + + popped2 = a.shift(2) + popped2.should == [] + a.should == [] + + popped1.should_not equal(popped2) + end + + it "returns whole elements if n exceeds size of the array" do + a = [1, 2, 3, 4, 5] + a.shift(6).should == [1, 2, 3, 4, 5] + a.should == [] + end + + it "does not return self even when it returns whole elements" do + a = [1, 2, 3, 4, 5] + a.shift(5).should_not equal(a) + + a = [1, 2, 3, 4, 5] + a.shift(6).should_not equal(a) + end + + it "raises an ArgumentError if n is negative" do + ->{ [1, 2, 3].shift(-1) }.should raise_error(ArgumentError) + end + + it "tries to convert n to an Integer using #to_int" do + a = [1, 2, 3, 4] + a.shift(2.3).should == [1, 2] + + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + a.should == [3, 4] + a.shift(obj).should == [3, 4] + a.should == [] + end + + it "raises a TypeError when the passed n cannot be coerced to Integer" do + ->{ [1, 2].shift("cat") }.should raise_error(TypeError) + ->{ [1, 2].shift(nil) }.should raise_error(TypeError) + end + + it "raises an ArgumentError if more arguments are passed" do + ->{ [1, 2].shift(1, 2) }.should raise_error(ArgumentError) + end + + it "does not return subclass instances with Array subclass" do + ArraySpecs::MyArray[1, 2, 3].shift(2).should be_an_instance_of(Array) + end + end +end diff --git a/spec/ruby/core/array/shuffle_spec.rb b/spec/ruby/core/array/shuffle_spec.rb new file mode 100644 index 0000000000..b84394bcb5 --- /dev/null +++ b/spec/ruby/core/array/shuffle_spec.rb @@ -0,0 +1,119 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#shuffle" do + it "returns the same values, in a usually different order" do + a = [1, 2, 3, 4] + different = false + 10.times do + s = a.shuffle + s.sort.should == a + different ||= (a != s) + end + different.should be_true # Will fail once in a blue moon (4!^10) + end + + it "is not destructive" do + a = [1, 2, 3] + 10.times do + a.shuffle + a.should == [1, 2, 3] + end + end + + it "does not return subclass instances with Array subclass" do + ArraySpecs::MyArray[1, 2, 3].shuffle.should be_an_instance_of(Array) + end + + it "calls #rand on the Object passed by the :random key in the arguments Hash" do + obj = mock("array_shuffle_random") + obj.should_receive(:rand).at_least(1).times.and_return(0.5) + + result = [1, 2].shuffle(random: obj) + result.size.should == 2 + result.should include(1, 2) + end + + it "raises a NoMethodError if an object passed for the RNG does not define #rand" do + obj = BasicObject.new + + -> { [1, 2].shuffle(random: obj) }.should raise_error(NoMethodError) + end + + it "accepts a Float for the value returned by #rand" do + random = mock("array_shuffle_random") + random.should_receive(:rand).at_least(1).times.and_return(0.3) + + [1, 2].shuffle(random: random).should be_an_instance_of(Array) + end + + it "accepts a Random class for the value for random: argument" do + [1, 2].shuffle(random: Random).should be_an_instance_of(Array) + end + + it "calls #to_int on the Object returned by #rand" do + value = mock("array_shuffle_random_value") + value.should_receive(:to_int).at_least(1).times.and_return(0) + random = mock("array_shuffle_random") + random.should_receive(:rand).at_least(1).times.and_return(value) + + [1, 2].shuffle(random: random).should be_an_instance_of(Array) + end + + it "raises a RangeError if the value is less than zero" do + value = mock("array_shuffle_random_value") + value.should_receive(:to_int).and_return(-1) + random = mock("array_shuffle_random") + random.should_receive(:rand).and_return(value) + + -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is equal to the Array size" do + value = mock("array_shuffle_random_value") + value.should_receive(:to_int).at_least(1).times.and_return(2) + random = mock("array_shuffle_random") + random.should_receive(:rand).at_least(1).times.and_return(value) + + -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is greater than the Array size" do + value = mock("array_shuffle_random_value") + value.should_receive(:to_int).at_least(1).times.and_return(3) + random = mock("array_shuffle_random") + random.should_receive(:rand).at_least(1).times.and_return(value) + + -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError) + end +end + +describe "Array#shuffle!" do + it "returns the same values, in a usually different order" do + a = [1, 2, 3, 4] + original = a + different = false + 10.times do + a = a.shuffle! + a.sort.should == [1, 2, 3, 4] + different ||= (a != [1, 2, 3, 4]) + end + different.should be_true # Will fail once in a blue moon (4!^10) + a.should equal(original) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.shuffle! }.should raise_error(FrozenError) + -> { ArraySpecs.empty_frozen_array.shuffle! }.should raise_error(FrozenError) + end + + it "matches CRuby with random:" do + %w[a b c].shuffle(random: Random.new(1)).should == %w[a c b] + (0..10).to_a.shuffle(random: Random.new(10)).should == [2, 6, 8, 5, 7, 10, 3, 1, 0, 4, 9] + end + + it "matches CRuby with srand" do + srand(123) + %w[a b c d e f g h i j k].shuffle.should == %w[a e f h i j d b g k c] + end +end diff --git a/spec/ruby/core/array/size_spec.rb b/spec/ruby/core/array/size_spec.rb new file mode 100644 index 0000000000..d68f956a83 --- /dev/null +++ b/spec/ruby/core/array/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/length' + +describe "Array#size" do + it_behaves_like :array_length, :size +end diff --git a/spec/ruby/core/array/slice_spec.rb b/spec/ruby/core/array/slice_spec.rb new file mode 100644 index 0000000000..731c129251 --- /dev/null +++ b/spec/ruby/core/array/slice_spec.rb @@ -0,0 +1,218 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/slice' + +describe "Array#slice!" do + it "removes and return the element at index" do + a = [1, 2, 3, 4] + a.slice!(10).should == nil + a.should == [1, 2, 3, 4] + a.slice!(-10).should == nil + a.should == [1, 2, 3, 4] + a.slice!(2).should == 3 + a.should == [1, 2, 4] + a.slice!(-1).should == 4 + a.should == [1, 2] + a.slice!(1).should == 2 + a.should == [1] + a.slice!(-1).should == 1 + a.should == [] + a.slice!(-1).should == nil + a.should == [] + a.slice!(0).should == nil + a.should == [] + end + + it "removes and returns length elements beginning at start" do + a = [1, 2, 3, 4, 5, 6] + a.slice!(2, 3).should == [3, 4, 5] + a.should == [1, 2, 6] + a.slice!(1, 1).should == [2] + a.should == [1, 6] + a.slice!(1, 0).should == [] + a.should == [1, 6] + a.slice!(2, 0).should == [] + a.should == [1, 6] + a.slice!(0, 4).should == [1, 6] + a.should == [] + a.slice!(0, 4).should == [] + a.should == [] + + a = [1] + a.slice!(0, 1).should == [1] + a.should == [] + a[-1].should == nil + + a = [1, 2, 3] + a.slice!(0,1).should == [1] + a.should == [2, 3] + end + + it "returns nil if length is negative" do + a = [1, 2, 3] + a.slice!(2, -1).should == nil + a.should == [1, 2, 3] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.slice(0).should == empty + + array = ArraySpecs.recursive_array + array.slice(4).should == array + array.slice(0..3).should == [1, 'two', 3.0, array] + end + + it "calls to_int on start and length arguments" do + obj = mock('2') + def obj.to_int() 2 end + + a = [1, 2, 3, 4, 5] + a.slice!(obj).should == 3 + a.should == [1, 2, 4, 5] + a.slice!(obj, obj).should == [4, 5] + a.should == [1, 2] + a.slice!(0, obj).should == [1, 2] + a.should == [] + end + + it "removes and return elements in range" do + a = [1, 2, 3, 4, 5, 6, 7, 8] + a.slice!(1..4).should == [2, 3, 4, 5] + a.should == [1, 6, 7, 8] + a.slice!(1...3).should == [6, 7] + a.should == [1, 8] + a.slice!(-1..-1).should == [8] + a.should == [1] + a.slice!(0...0).should == [] + a.should == [1] + a.slice!(0..0).should == [1] + a.should == [] + + a = [1,2,3] + a.slice!(0..3).should == [1,2,3] + a.should == [] + end + + it "removes and returns elements in end-exclusive ranges" do + a = [1, 2, 3, 4, 5, 6, 7, 8] + a.slice!(4...a.length).should == [5, 6, 7, 8] + a.should == [1, 2, 3, 4] + end + + it "calls to_int on range arguments" do + from = mock('from') + to = mock('to') + + # So we can construct a range out of them... + def from.<=>(o) 0 end + def to.<=>(o) 0 end + + def from.to_int() 1 end + def to.to_int() -2 end + + a = [1, 2, 3, 4, 5] + + a.slice!(from .. to).should == [2, 3, 4] + a.should == [1, 5] + + -> { a.slice!("a" .. "b") }.should raise_error(TypeError) + -> { a.slice!(from .. "b") }.should raise_error(TypeError) + end + + it "returns last element for consecutive calls at zero index" do + a = [ 1, 2, 3 ] + a.slice!(0).should == 1 + a.slice!(0).should == 2 + a.slice!(0).should == 3 + a.should == [] + end + + it "does not expand array with indices out of bounds" do + a = [1, 2] + a.slice!(4).should == nil + a.should == [1, 2] + a.slice!(4, 0).should == nil + a.should == [1, 2] + a.slice!(6, 1).should == nil + a.should == [1, 2] + a.slice!(8...8).should == nil + a.should == [1, 2] + a.slice!(10..10).should == nil + a.should == [1, 2] + end + + it "does not expand array with negative indices out of bounds" do + a = [1, 2] + a.slice!(-3, 1).should == nil + a.should == [1, 2] + a.slice!(-3..2).should == nil + a.should == [1, 2] + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.slice!(0, 0) }.should raise_error(FrozenError) + end + + it "works with endless ranges" do + a = [1, 2, 3] + a.slice!(eval("(1..)")).should == [2, 3] + a.should == [1] + + a = [1, 2, 3] + a.slice!(eval("(2...)")).should == [3] + a.should == [1, 2] + + a = [1, 2, 3] + a.slice!(eval("(-2..)")).should == [2, 3] + a.should == [1] + + a = [1, 2, 3] + a.slice!(eval("(-1...)")).should == [3] + a.should == [1, 2] + end + + it "works with beginless ranges" do + a = [0,1,2,3,4] + a.slice!((..3)).should == [0, 1, 2, 3] + a.should == [4] + + a = [0,1,2,3,4] + a.slice!((...-2)).should == [0, 1, 2] + a.should == [3, 4] + end + + describe "with a subclass of Array" do + before :each do + @array = ArraySpecs::MyArray[1, 2, 3, 4, 5] + end + + it "returns a Array instance with [n, m]" do + @array.slice!(0, 2).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n, m]" do + @array.slice!(-3, 2).should be_an_instance_of(Array) + end + + it "returns a Array instance with [n..m]" do + @array.slice!(1..3).should be_an_instance_of(Array) + end + + it "returns a Array instance with [n...m]" do + @array.slice!(1...3).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n..-m]" do + @array.slice!(-3..-1).should be_an_instance_of(Array) + end + + it "returns a Array instance with [-n...-m]" do + @array.slice!(-3...-1).should be_an_instance_of(Array) + end + end +end + +describe "Array#slice" do + it_behaves_like :array_slice, :slice +end diff --git a/spec/ruby/core/array/sort_by_spec.rb b/spec/ruby/core/array/sort_by_spec.rb new file mode 100644 index 0000000000..0334f953f6 --- /dev/null +++ b/spec/ruby/core/array/sort_by_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iterable_and_tolerating_size_increasing' +require_relative '../enumerable/shared/enumeratorized' + +describe "Array#sort_by!" do + it "sorts array in place by passing each element to the given block" do + a = [-100, -2, 1, 200, 30000] + a.sort_by!{ |e| e.to_s.size } + a.should == [1, -2, 200, -100, 30000] + end + + it "returns an Enumerator if not given a block" do + (1..10).to_a.sort_by!.should be_an_instance_of(Enumerator) + end + + it "completes when supplied a block that always returns the same result" do + a = [2, 3, 5, 1, 4] + a.sort_by!{ 1 } + a.should be_an_instance_of(Array) + a.sort_by!{ 0 } + a.should be_an_instance_of(Array) + a.sort_by!{ -1 } + a.should be_an_instance_of(Array) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.sort_by! {}}.should raise_error(FrozenError) + end + + it "raises a FrozenError on an empty frozen array" do + -> { ArraySpecs.empty_frozen_array.sort_by! {}}.should raise_error(FrozenError) + end + + it "raises a FrozenError on a frozen array only during iteration if called without a block" do + enum = ArraySpecs.frozen_array.sort_by! + -> { enum.each {} }.should raise_error(FrozenError) + end + + it "returns the specified value when it would break in the given block" do + [1, 2, 3].sort_by!{ break :a }.should == :a + end + + it "makes some modification even if finished sorting when it would break in the given block" do + partially_sorted = (1..5).map{|i| + ary = [5, 4, 3, 2, 1] + ary.sort_by!{|x,y| break if x==i; x<=>y} + ary + } + partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should be_true + end + + it "changes nothing when called on a single element array" do + [1].sort_by!(&:to_s).should == [1] + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.sort_by! { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "doesn't change array if error is raised" do + a = [4, 3, 2, 1] + begin + a.sort_by! do |e| + raise StandardError, 'Oops' if e == 1 + e + end + rescue StandardError + end + + a.should == [4, 3, 2, 1] + end + + it_behaves_like :enumeratorized_with_origin_size, :sort_by!, [1,2,3] +end + +describe "Array#sort_by!" do + it_behaves_like :array_iterable_and_tolerating_size_increasing, :sort_by! +end diff --git a/spec/ruby/core/array/sort_spec.rb b/spec/ruby/core/array/sort_spec.rb new file mode 100644 index 0000000000..e20b650516 --- /dev/null +++ b/spec/ruby/core/array/sort_spec.rb @@ -0,0 +1,252 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#sort" do + it "returns a new array sorted based on comparing elements with <=>" do + a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0] + a.sort.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000] + end + + it "does not affect the original Array" do + a = [3, 1, 2] + a.sort.should == [1, 2, 3] + a.should == [3, 1, 2] + + a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13] + b = a.sort + a.should == [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13] + b.should == (0..15).to_a + end + + it "sorts already-sorted Arrays" do + (0..15).to_a.sort.should == (0..15).to_a + end + + it "sorts reverse-sorted Arrays" do + (0..15).to_a.reverse.sort.should == (0..15).to_a + end + + it "sorts Arrays that consist entirely of equal elements" do + a = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + a.sort.should == a + b = Array.new(15).map { ArraySpecs::SortSame.new } + b.sort.should == b + end + + it "sorts Arrays that consist mostly of equal elements" do + a = [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1] + a.sort.should == [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + end + + it "does not return self even if the array would be already sorted" do + a = [1, 2, 3] + sorted = a.sort + sorted.should == a + sorted.should_not equal(a) + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.sort.should == empty + + array = [[]]; array << array + array.sort.should == [[], array] + end + + it "uses #<=> of elements in order to sort" do + a = ArraySpecs::MockForCompared.new + b = ArraySpecs::MockForCompared.new + c = ArraySpecs::MockForCompared.new + + ArraySpecs::MockForCompared.should_not.compared? + [a, b, c].sort.should == [c, b, a] + ArraySpecs::MockForCompared.should.compared? + end + + it "does not deal with exceptions raised by unimplemented or incorrect #<=>" do + o = Object.new + + -> { + [o, 1].sort + }.should raise_error(ArgumentError) + end + + it "may take a block which is used to determine the order of objects a and b described as -1, 0 or +1" do + a = [5, 1, 4, 3, 2] + a.sort.should == [1, 2, 3, 4, 5] + a.sort {|x, y| y <=> x}.should == [5, 4, 3, 2, 1] + end + + it "raises an error when a given block returns nil" do + -> { [1, 2].sort {} }.should raise_error(ArgumentError) + end + + it "does not call #<=> on contained objects when invoked with a block" do + a = Array.new(25) + (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new } + + a.sort { -1 }.should be_an_instance_of(Array) + end + + it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do + a = Array.new(1500) + (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new } + + a.sort { -1 }.should be_an_instance_of(Array) + end + + it "completes when supplied a block that always returns the same result" do + a = [2, 3, 5, 1, 4] + a.sort { 1 }.should be_an_instance_of(Array) + a.sort { 0 }.should be_an_instance_of(Array) + a.sort { -1 }.should be_an_instance_of(Array) + end + + it "does not freezes self during being sorted" do + a = [1, 2, 3] + a.sort { |x,y| a.should_not.frozen?; x <=> y } + end + + it "returns the specified value when it would break in the given block" do + [1, 2, 3].sort{ break :a }.should == :a + end + + it "uses the sign of Integer block results as the sort result" do + a = [1, 2, 5, 10, 7, -4, 12] + begin + class Integer + alias old_spaceship <=> + def <=>(other) + raise + end + end + a.sort {|n, m| (n - m) * (2 ** 200)}.should == [-4, 1, 2, 5, 7, 10, 12] + ensure + class Integer + alias <=> old_spaceship + end + end + end + + it "compares values returned by block with 0" do + a = [1, 2, 5, 10, 7, -4, 12] + a.sort { |n, m| n - m }.should == [-4, 1, 2, 5, 7, 10, 12] + a.sort { |n, m| + ArraySpecs::ComparableWithInteger.new(n-m) + }.should == [-4, 1, 2, 5, 7, 10, 12] + -> { + a.sort { |n, m| (n - m).to_s } + }.should raise_error(ArgumentError) + end + + it "sorts an array that has a value shifted off without a block" do + a = Array.new(20, 1) + a.shift + a[0] = 2 + a.sort.last.should == 2 + end + + it "sorts an array that has a value shifted off with a block" do + a = Array.new(20, 1) + a.shift + a[0] = 2 + a.sort {|x, y| x <=> y }.last.should == 2 + end + + it "raises an error if objects can't be compared" do + a=[ArraySpecs::Uncomparable.new, ArraySpecs::Uncomparable.new] + -> {a.sort}.should raise_error(ArgumentError) + end + + # From a strange Rubinius bug + it "handles a large array that has been pruned" do + pruned = ArraySpecs::LargeArray.dup.delete_if { |n| n !~ /^test./ } + pruned.sort.should == ArraySpecs::LargeTestArraySorted + end + + it "does not return subclass instance on Array subclasses" do + ary = ArraySpecs::MyArray[1, 2, 3] + ary.sort.should be_an_instance_of(Array) + end +end + +describe "Array#sort!" do + it "sorts array in place using <=>" do + a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0] + a.sort! + a.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000] + end + + it "sorts array in place using block value if a block given" do + a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13] + a.sort! { |x, y| y <=> x }.should == (0..15).to_a.reverse + end + + it "returns self if the order of elements changed" do + a = [6, 7, 2, 3, 7] + a.sort!.should equal(a) + a.should == [2, 3, 6, 7, 7] + end + + it "returns self even if makes no modification" do + a = [1, 2, 3, 4, 5] + a.sort!.should equal(a) + a.should == [1, 2, 3, 4, 5] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.sort!.should == empty + + array = [[]]; array << array + array.sort!.should == array + end + + it "uses #<=> of elements in order to sort" do + a = ArraySpecs::MockForCompared.new + b = ArraySpecs::MockForCompared.new + c = ArraySpecs::MockForCompared.new + + ArraySpecs::MockForCompared.should_not.compared? + [a, b, c].sort!.should == [c, b, a] + ArraySpecs::MockForCompared.should.compared? + end + + it "does not call #<=> on contained objects when invoked with a block" do + a = Array.new(25) + (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new } + + a.sort! { -1 }.should be_an_instance_of(Array) + end + + it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do + a = Array.new(1500) + (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new } + + a.sort! { -1 }.should be_an_instance_of(Array) + end + + it "completes when supplied a block that always returns the same result" do + a = [2, 3, 5, 1, 4] + a.sort!{ 1 }.should be_an_instance_of(Array) + a.sort!{ 0 }.should be_an_instance_of(Array) + a.sort!{ -1 }.should be_an_instance_of(Array) + end + + it "raises a FrozenError on a frozen array" do + -> { ArraySpecs.frozen_array.sort! }.should raise_error(FrozenError) + end + + it "returns the specified value when it would break in the given block" do + [1, 2, 3].sort{ break :a }.should == :a + end + + it "makes some modification even if finished sorting when it would break in the given block" do + partially_sorted = (1..5).map{|i| + ary = [5, 4, 3, 2, 1] + ary.sort!{|x,y| break if x==i; x<=>y} + ary + } + partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should be_true + end +end diff --git a/spec/ruby/core/array/sum_spec.rb b/spec/ruby/core/array/sum_spec.rb new file mode 100644 index 0000000000..06abe06135 --- /dev/null +++ b/spec/ruby/core/array/sum_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#sum" do + it "returns the sum of elements" do + [1, 2, 3].sum.should == 6 + end + + it "applies a block to each element before adding if it's given" do + [1, 2, 3].sum { |i| i * 10 }.should == 60 + end + + it "doesn't apply the block init" do + [1, 2, 3].sum(1) { |i| i * 10 }.should == 61 + end + + # https://bugs.ruby-lang.org/issues/12217 + # https://github.com/ruby/ruby/blob/master/doc/ChangeLog/ChangeLog-2.4.0#L6208-L6214 + it "uses Kahan's compensated summation algorithm for precise sum of float numbers" do + floats = [2.7800000000000002, 5.0, 2.5, 4.44, 3.89, 3.89, 4.44, 7.78, 5.0, 2.7800000000000002, 5.0, 2.5] + naive_sum = floats.reduce { |sum, e| sum + e } + naive_sum.should == 50.00000000000001 + floats.sum.should == 50.0 + end + + it "handles infinite values and NaN" do + [1.0, Float::INFINITY].sum.should == Float::INFINITY + [1.0, -Float::INFINITY].sum.should == -Float::INFINITY + [1.0, Float::NAN].sum.should.nan? + + [Float::INFINITY, 1.0].sum.should == Float::INFINITY + [-Float::INFINITY, 1.0].sum.should == -Float::INFINITY + [Float::NAN, 1.0].sum.should.nan? + + [Float::NAN, Float::INFINITY].sum.should.nan? + [Float::INFINITY, Float::NAN].sum.should.nan? + + [Float::INFINITY, -Float::INFINITY].sum.should.nan? + [-Float::INFINITY, Float::INFINITY].sum.should.nan? + + [Float::INFINITY, Float::INFINITY].sum.should == Float::INFINITY + [-Float::INFINITY, -Float::INFINITY].sum.should == -Float::INFINITY + [Float::NAN, Float::NAN].sum.should.nan? + end + + it "returns init value if array is empty" do + [].sum(-1).should == -1 + end + + it "returns 0 if array is empty and init is omitted" do + [].sum.should == 0 + end + + it "adds init value to the sum of elements" do + [1, 2, 3].sum(10).should == 16 + end + + it "can be used for non-numeric objects by providing init value" do + ["a", "b", "c"].sum("").should == "abc" + end + + it 'raises TypeError if any element are not numeric' do + -> { ["a"].sum }.should raise_error(TypeError) + end + + it 'raises TypeError if any element cannot be added to init value' do + -> { [1].sum([]) }.should raise_error(TypeError) + end + + it "calls + to sum the elements" do + a = mock("a") + b = mock("b") + a.should_receive(:+).with(b).and_return(42) + [b].sum(a).should == 42 + end + + ruby_bug '#19530', ''...'3.3' do + it "calls + on the init value" do + a = mock("a") + b = mock("b") + a.should_receive(:+).with(42).and_return(b) + [42].sum(a).should == b + end + end +end + +describe "Array#sum" do + @value_to_return = -> _ { 1 } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :sum +end diff --git a/spec/ruby/core/array/take_spec.rb b/spec/ruby/core/array/take_spec.rb new file mode 100644 index 0000000000..c4f0ac9aa4 --- /dev/null +++ b/spec/ruby/core/array/take_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#take" do + it "returns the first specified number of elements" do + [1, 2, 3].take(2).should == [1, 2] + end + + it "returns all elements when the argument is greater than the Array size" do + [1, 2].take(99).should == [1, 2] + end + + it "returns all elements when the argument is less than the Array size" do + [1, 2].take(4).should == [1, 2] + end + + it "returns an empty Array when passed zero" do + [1].take(0).should == [] + end + + it "returns an empty Array when called on an empty Array" do + [].take(3).should == [] + end + + it "raises an ArgumentError when the argument is negative" do + ->{ [1].take(-3) }.should raise_error(ArgumentError) + end + + it 'returns a Array instance for Array subclasses' do + ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should be_an_instance_of(Array) + end +end diff --git a/spec/ruby/core/array/take_while_spec.rb b/spec/ruby/core/array/take_while_spec.rb new file mode 100644 index 0000000000..8f50260b42 --- /dev/null +++ b/spec/ruby/core/array/take_while_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#take_while" do + it "returns all elements until the block returns false" do + [1, 2, 3].take_while{ |element| element < 3 }.should == [1, 2] + end + + it "returns all elements until the block returns nil" do + [1, 2, nil, 4].take_while{ |element| element }.should == [1, 2] + end + + it "returns all elements until the block returns false" do + [1, 2, false, 4].take_while{ |element| element }.should == [1, 2] + end + + it 'returns a Array instance for Array subclasses' do + ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should be_an_instance_of(Array) + end +end + +describe "Array#take_while" do + @value_to_return = -> _ { true } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :take_while +end diff --git a/spec/ruby/core/array/to_a_spec.rb b/spec/ruby/core/array/to_a_spec.rb new file mode 100644 index 0000000000..49d0a4782e --- /dev/null +++ b/spec/ruby/core/array/to_a_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#to_a" do + it "returns self" do + a = [1, 2, 3] + a.to_a.should == [1, 2, 3] + a.should equal(a.to_a) + end + + it "does not return subclass instance on Array subclasses" do + e = ArraySpecs::MyArray.new(1, 2) + e.to_a.should be_an_instance_of(Array) + e.to_a.should == [1, 2] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.to_a.should == empty + + array = ArraySpecs.recursive_array + array.to_a.should == array + end +end diff --git a/spec/ruby/core/array/to_ary_spec.rb b/spec/ruby/core/array/to_ary_spec.rb new file mode 100644 index 0000000000..314699b709 --- /dev/null +++ b/spec/ruby/core/array/to_ary_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#to_ary" do + it "returns self" do + a = [1, 2, 3] + a.should equal(a.to_ary) + a = ArraySpecs::MyArray[1, 2, 3] + a.should equal(a.to_ary) + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.to_ary.should == empty + + array = ArraySpecs.recursive_array + array.to_ary.should == array + end + +end diff --git a/spec/ruby/core/array/to_h_spec.rb b/spec/ruby/core/array/to_h_spec.rb new file mode 100644 index 0000000000..1c814f3d01 --- /dev/null +++ b/spec/ruby/core/array/to_h_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#to_h" do + it "converts empty array to empty hash" do + [].to_h.should == {} + end + + it "converts [key, value] pairs to a hash" do + hash = [[:a, 1], [:b, 2]].to_h + hash.should == { a: 1, b: 2 } + end + + it "uses the last value of a duplicated key" do + hash = [[:a, 1], [:b, 2], [:a, 3]].to_h + hash.should == { a: 3, b: 2 } + end + + it "calls #to_ary on contents" do + pair = mock('to_ary') + pair.should_receive(:to_ary).and_return([:b, 2]) + hash = [[:a, 1], pair].to_h + hash.should == { a: 1, b: 2 } + end + + it "raises TypeError if an element is not an array" do + -> { [:x].to_h }.should raise_error(TypeError) + end + + it "raises ArgumentError if an element is not a [key, value] pair" do + -> { [[:x]].to_h }.should raise_error(ArgumentError) + end + + it "does not accept arguments" do + -> { [].to_h(:a, :b) }.should raise_error(ArgumentError) + end + + it "produces a hash that returns nil for a missing element" do + [[:a, 1], [:b, 2]].to_h[:c].should be_nil + end + + context "with block" do + it "converts [key, value] pairs returned by the block to a Hash" do + [:a, :b].to_h { |k| [k, k.to_s] }.should == { a: 'a', b: 'b' } + end + + it "passes to a block each element as a single argument" do + ScratchPad.record [] + [[:a, 1], [:b, 2]].to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[[:a, 1]], [[:b, 2]]] + end + + it "raises ArgumentError if block returns longer or shorter array" do + -> do + [:a, :b].to_h { |k| [k, k.to_s, 1] } + end.should raise_error(ArgumentError, /wrong array length at 0/) + + -> do + [:a, :b].to_h { |k| [k] } + end.should raise_error(ArgumentError, /wrong array length at 0/) + end + + it "raises TypeError if block returns something other than Array" do + -> do + [:a, :b].to_h { |k| "not-array" } + end.should raise_error(TypeError, /wrong element type String at 0/) + end + + it "coerces returned pair to Array with #to_ary" do + x = mock('x') + x.stub!(:to_ary).and_return([:b, 'b']) + + [:a].to_h { |k| x }.should == { :b => 'b' } + end + + it "does not coerce returned pair to Array with #to_a" do + x = mock('x') + x.stub!(:to_a).and_return([:b, 'b']) + + -> do + [:a].to_h { |k| x } + end.should raise_error(TypeError, /wrong element type MockObject at 0/) + end + end +end + +describe "Array#to_h" do + @value_to_return = -> e { [e, e.to_s] } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :to_h +end diff --git a/spec/ruby/core/array/to_s_spec.rb b/spec/ruby/core/array/to_s_spec.rb new file mode 100644 index 0000000000..e8476702ec --- /dev/null +++ b/spec/ruby/core/array/to_s_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/join' +require_relative 'shared/inspect' + +describe "Array#to_s" do + it_behaves_like :array_inspect, :to_s +end diff --git a/spec/ruby/core/array/transpose_spec.rb b/spec/ruby/core/array/transpose_spec.rb new file mode 100644 index 0000000000..b39077f4c9 --- /dev/null +++ b/spec/ruby/core/array/transpose_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#transpose" do + it "assumes an array of arrays and returns the result of transposing rows and columns" do + [[1, 'a'], [2, 'b'], [3, 'c']].transpose.should == [[1, 2, 3], ["a", "b", "c"]] + [[1, 2, 3], ["a", "b", "c"]].transpose.should == [[1, 'a'], [2, 'b'], [3, 'c']] + [].transpose.should == [] + [[]].transpose.should == [] + [[], []].transpose.should == [] + [[0]].transpose.should == [[0]] + [[0], [1]].transpose.should == [[0, 1]] + end + + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('[1,2]') + obj.should_receive(:to_ary).and_return([1, 2]) + [obj, [:a, :b]].transpose.should == [[1, :a], [2, :b]] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.transpose.should == empty + + a = []; a << a + b = []; b << b + [a, b].transpose.should == [[a, b]] + + a = [1]; a << a + b = [2]; b << b + [a, b].transpose.should == [ [1, 2], [a, b] ] + end + + it "raises a TypeError if the passed Argument does not respond to #to_ary" do + -> { [Object.new, [:a, :b]].transpose }.should raise_error(TypeError) + end + + it "does not call to_ary on array subclass elements" do + ary = [ArraySpecs::ToAryArray[1, 2], ArraySpecs::ToAryArray[4, 6]] + ary.transpose.should == [[1, 4], [2, 6]] + end + + it "raises an IndexError if the arrays are not of the same length" do + -> { [[1, 2], [:a]].transpose }.should raise_error(IndexError) + end + + it "does not return subclass instance on Array subclasses" do + result = ArraySpecs::MyArray[ArraySpecs::MyArray[1, 2, 3], ArraySpecs::MyArray[4, 5, 6]].transpose + result.should be_an_instance_of(Array) + result[0].should be_an_instance_of(Array) + result[1].should be_an_instance_of(Array) + end +end diff --git a/spec/ruby/core/array/try_convert_spec.rb b/spec/ruby/core/array/try_convert_spec.rb new file mode 100644 index 0000000000..bea8815006 --- /dev/null +++ b/spec/ruby/core/array/try_convert_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array.try_convert" do + it "returns the argument if it's an Array" do + x = Array.new + Array.try_convert(x).should equal(x) + end + + it "returns the argument if it's a kind of Array" do + x = ArraySpecs::MyArray[] + Array.try_convert(x).should equal(x) + end + + it "returns nil when the argument does not respond to #to_ary" do + Array.try_convert(Object.new).should be_nil + end + + it "sends #to_ary to the argument and returns the result if it's nil" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(nil) + Array.try_convert(obj).should be_nil + end + + it "sends #to_ary to the argument and returns the result if it's an Array" do + x = Array.new + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(x) + Array.try_convert(obj).should equal(x) + end + + it "sends #to_ary to the argument and returns the result if it's a kind of Array" do + x = ArraySpecs::MyArray[] + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(x) + Array.try_convert(obj).should equal(x) + end + + it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(Object.new) + -> { Array.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_ary gives Object)") + end + + it "does not rescue exceptions raised by #to_ary" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_raise(RuntimeError) + -> { Array.try_convert obj }.should raise_error(RuntimeError) + end +end diff --git a/spec/ruby/core/array/union_spec.rb b/spec/ruby/core/array/union_spec.rb new file mode 100644 index 0000000000..ba2cc0d6b7 --- /dev/null +++ b/spec/ruby/core/array/union_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/union' + +describe "Array#|" do + it_behaves_like :array_binary_union, :| +end + +describe "Array#union" do + it_behaves_like :array_binary_union, :union + + it "returns unique elements when given no argument" do + x = [1, 2, 3, 2] + x.union.should == [1, 2, 3] + end + + it "does not return subclass instances for Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].union.should be_an_instance_of(Array) + end + + it "accepts multiple arguments" do + x = [1, 2, 3] + x.union(x, x, x, x, [3, 4], x).should == [1, 2, 3, 4] + end +end diff --git a/spec/ruby/core/array/uniq_spec.rb b/spec/ruby/core/array/uniq_spec.rb new file mode 100644 index 0000000000..d5d826db15 --- /dev/null +++ b/spec/ruby/core/array/uniq_spec.rb @@ -0,0 +1,243 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iterable_and_tolerating_size_increasing' + +describe "Array#uniq" do + it "returns an array with no duplicates" do + ["a", "a", "b", "b", "c"].uniq.should == ["a", "b", "c"] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.uniq.should == [empty] + + array = ArraySpecs.recursive_array + array.uniq.should == [1, 'two', 3.0, array] + end + + it "uses eql? semantics" do + [1.0, 1].uniq.should == [1.0, 1] + end + + it "compares elements first with hash" do + x = mock('0') + x.should_receive(:hash).at_least(1).and_return(0) + y = mock('0') + y.should_receive(:hash).at_least(1).and_return(0) + + [x, y].uniq.should == [x, y] + end + + it "does not compare elements with different hash codes via eql?" do + x = mock('0') + x.should_not_receive(:eql?) + y = mock('1') + y.should_not_receive(:eql?) + + x.should_receive(:hash).at_least(1).and_return(0) + y.should_receive(:hash).at_least(1).and_return(1) + + [x, y].uniq.should == [x, y] + end + + it "compares elements with matching hash codes with #eql?" do + a = Array.new(2) do + obj = mock('0') + obj.should_receive(:hash).at_least(1).and_return(0) + + def obj.eql?(o) + false + end + + obj + end + + a.uniq.should == a + + a = Array.new(2) do + obj = mock('0') + obj.should_receive(:hash).at_least(1).and_return(0) + + def obj.eql?(o) + true + end + + obj + end + + a.uniq.size.should == 1 + end + + it "compares elements based on the value returned from the block" do + a = [1, 2, 3, 4] + a.uniq { |x| x >= 2 ? 1 : 0 }.should == [1, 2] + end + + it "yields items in order" do + a = [1, 2, 3] + yielded = [] + a.uniq { |v| yielded << v } + yielded.should == a + end + + it "handles nil and false like any other values" do + [nil, false, 42].uniq { :foo }.should == [nil] + [false, nil, 42].uniq { :bar }.should == [false] + end + + it "returns Array instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].uniq.should be_an_instance_of(Array) + end + + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.should_receive(:hash).at_least(1).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + [x, x].uniq.should == [x] + end + + describe "given an array of BasicObject subclasses that define ==, eql?, and hash" do + # jruby/jruby#3227 + it "filters equivalent elements using those definitions" do + + basic = Class.new(BasicObject) do + attr_reader :x + + def initialize(x) + @x = x + end + + def ==(rhs) + @x == rhs.x + end + alias_method :eql?, :== + + def hash + @x.hash + end + end + + a = [basic.new(3), basic.new(2), basic.new(1), basic.new(4), basic.new(1), basic.new(2), basic.new(3)] + a.uniq.should == [basic.new(3), basic.new(2), basic.new(1), basic.new(4)] + end + end +end + +describe "Array#uniq" do + @value_to_return = -> e { e } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :uniq +end + +describe "Array#uniq!" do + it "modifies the array in place" do + a = [ "a", "a", "b", "b", "c" ] + a.uniq! + a.should == ["a", "b", "c"] + end + + it "returns self" do + a = [ "a", "a", "b", "b", "c" ] + a.should equal(a.uniq!) + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty_dup = empty.dup + empty.uniq! + empty.should == empty_dup + + array = ArraySpecs.recursive_array + expected = array[0..3] + array.uniq! + array.should == expected + end + + it "compares elements first with hash" do + x = mock('0') + x.should_receive(:hash).at_least(1).and_return(0) + y = mock('0') + y.should_receive(:hash).at_least(1).and_return(0) + + a = [x, y] + a.uniq! + a.should == [x, y] + end + + it "does not compare elements with different hash codes via eql?" do + x = mock('0') + x.should_not_receive(:eql?) + y = mock('1') + y.should_not_receive(:eql?) + + x.should_receive(:hash).at_least(1).and_return(0) + y.should_receive(:hash).at_least(1).and_return(1) + + a = [x, y] + a.uniq! + a.should == [x, y] + end + + it "returns nil if no changes are made to the array" do + [ "a", "b", "c" ].uniq!.should == nil + end + + it "raises a FrozenError on a frozen array when the array is modified" do + dup_ary = [1, 1, 2] + dup_ary.freeze + -> { dup_ary.uniq! }.should raise_error(FrozenError) + end + + # see [ruby-core:23666] + it "raises a FrozenError on a frozen array when the array would not be modified" do + -> { ArraySpecs.frozen_array.uniq!}.should raise_error(FrozenError) + -> { ArraySpecs.empty_frozen_array.uniq!}.should raise_error(FrozenError) + end + + it "doesn't yield to the block on a frozen array" do + -> { ArraySpecs.frozen_array.uniq!{ raise RangeError, "shouldn't yield"}}.should raise_error(FrozenError) + end + + it "compares elements based on the value returned from the block" do + a = [1, 2, 3, 4] + a.uniq! { |x| x >= 2 ? 1 : 0 }.should == [1, 2] + end + + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.should_receive(:hash).at_least(1).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + a = [x, x] + a.uniq! + a.should == [x] + end + + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.send(@method) { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + it "doesn't change array if error is raised" do + a = [1, 1, 2, 2, 3, 3, 4, 4] + begin + a.send(@method) do |e| + raise StandardError, 'Oops' if e == 3 + e + end + rescue StandardError + end + + a.should == [1, 1, 2, 2, 3, 3, 4, 4] + end +end + +describe "Array#uniq!" do + @value_to_return = -> e { e } + it_behaves_like :array_iterable_and_tolerating_size_increasing, :uniq! +end diff --git a/spec/ruby/core/array/unshift_spec.rb b/spec/ruby/core/array/unshift_spec.rb new file mode 100644 index 0000000000..b8b675e5f8 --- /dev/null +++ b/spec/ruby/core/array/unshift_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/unshift' + +describe "Array#unshift" do + it_behaves_like :array_unshift, :unshift +end diff --git a/spec/ruby/core/array/values_at_spec.rb b/spec/ruby/core/array/values_at_spec.rb new file mode 100644 index 0000000000..e85bbee400 --- /dev/null +++ b/spec/ruby/core/array/values_at_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +# Should be synchronized with core/struct/values_at_spec.rb +describe "Array#values_at" do + it "returns an array of elements at the indexes when passed indexes" do + [1, 2, 3, 4, 5].values_at().should == [] + [1, 2, 3, 4, 5].values_at(1, 0, 5, -1, -8, 10).should == [2, 1, nil, 5, nil, nil] + end + + it "calls to_int on its indices" do + obj = mock('1') + def obj.to_int() 1 end + [1, 2].values_at(obj, obj, obj).should == [2, 2, 2] + end + + it "properly handles recursive arrays" do + empty = ArraySpecs.empty_recursive_array + empty.values_at(0, 1, 2).should == [empty, nil, nil] + + array = ArraySpecs.recursive_array + array.values_at(0, 1, 2, 3).should == [1, 'two', 3.0, array] + end + + describe "when passed ranges" do + it "returns an array of elements in the ranges" do + [1, 2, 3, 4, 5].values_at(0..2, 1...3, 2..-2).should == [1, 2, 3, 2, 3, 3, 4] + [1, 2, 3, 4, 5].values_at(6..4).should == [] + end + + it "calls to_int on arguments of ranges" do + from = mock('from') + to = mock('to') + + # So we can construct a range out of them... + def from.<=>(o) 0 end + def to.<=>(o) 0 end + + def from.to_int() 1 end + def to.to_int() -2 end + + ary = [1, 2, 3, 4, 5] + ary.values_at(from .. to, from ... to, to .. from).should == [2, 3, 4, 2, 3] + end + end + + describe "when passed a range" do + it "fills with nil if the index is out of the range" do + [0, 1].values_at(0..3).should == [0, 1, nil, nil] + [0, 1].values_at(2..4).should == [nil, nil, nil] + end + + describe "on an empty array" do + it "fills with nils if the index is out of the range" do + [].values_at(0..2).should == [nil, nil, nil] + [].values_at(1..3).should == [nil, nil, nil] + end + end + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].values_at(0, 1..2, 1).should be_an_instance_of(Array) + end + + it "works when given endless ranges" do + [1, 2, 3, 4].values_at(eval("(1..)")).should == [2, 3, 4] + [1, 2, 3, 4].values_at(eval("(3...)")).should == [4] + end + + it "works when given beginless ranges" do + [1, 2, 3, 4].values_at((..2)).should == [1, 2, 3] + [1, 2, 3, 4].values_at((...2)).should == [1, 2] + end +end diff --git a/spec/ruby/core/array/zip_spec.rb b/spec/ruby/core/array/zip_spec.rb new file mode 100644 index 0000000000..2a0f64cb49 --- /dev/null +++ b/spec/ruby/core/array/zip_spec.rb @@ -0,0 +1,71 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#zip" do + it "returns an array of arrays containing corresponding elements of each array" do + [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]).should == + [[1, "a"], [2, "b"], [3, "c"], [4, "d"]] + end + + it "fills in missing values with nil" do + [1, 2, 3, 4, 5].zip(["a", "b", "c", "d"]).should == + [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, nil]] + end + + it "properly handles recursive arrays" do + a = []; a << a + b = [1]; b << b + + a.zip(a).should == [ [a[0], a[0]] ] + a.zip(b).should == [ [a[0], b[0]] ] + b.zip(a).should == [ [b[0], a[0]], [b[1], a[1]] ] + b.zip(b).should == [ [b[0], b[0]], [b[1], b[1]] ] + end + + it "calls #to_ary to convert the argument to an Array" do + obj = mock('[3,4]') + obj.should_receive(:to_ary).and_return([3, 4]) + [1, 2].zip(obj).should == [[1, 3], [2, 4]] + end + + it "uses #each to extract arguments' elements when #to_ary fails" do + obj = Class.new do + def each(&b) + [3,4].each(&b) + end + end.new + + [1, 2].zip(obj).should == [[1, 3], [2, 4]] + end + + it "stops at own size when given an infinite enumerator" do + [1, 2].zip(10.upto(Float::INFINITY)).should == [[1, 10], [2, 11]] + end + + it "fills nil when the given enumerator is shorter than self" do + obj = Object.new + def obj.each + yield 10 + end + [1, 2].zip(obj).should == [[1, 10], [2, nil]] + end + + it "calls block if supplied" do + values = [] + [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]) { |value| + values << value + }.should == nil + + values.should == [[1, "a"], [2, "b"], [3, "c"], [4, "d"]] + end + + it "does not return subclass instance on Array subclasses" do + ArraySpecs::MyArray[1, 2, 3].zip(["a", "b"]).should be_an_instance_of(Array) + end + + it "raises TypeError when some argument isn't Array and doesn't respond to #to_ary and #to_enum" do + -> { [1, 2, 3].zip(Object.new) }.should raise_error(TypeError, "wrong argument type Object (must respond to :each)") + -> { [1, 2, 3].zip(1) }.should raise_error(TypeError, "wrong argument type Integer (must respond to :each)") + -> { [1, 2, 3].zip(true) }.should raise_error(TypeError, "wrong argument type TrueClass (must respond to :each)") + end +end |
