summaryrefslogtreecommitdiff
path: root/spec/ruby/core/array/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/array/shared')
-rw-r--r--spec/ruby/core/array/shared/clone.rb20
-rw-r--r--spec/ruby/core/array/shared/delete_if.rb13
-rw-r--r--spec/ruby/core/array/shared/difference.rb78
-rw-r--r--spec/ruby/core/array/shared/enumeratorize.rb5
-rw-r--r--spec/ruby/core/array/shared/eql.rb92
-rw-r--r--spec/ruby/core/array/shared/intersection.rb85
-rw-r--r--spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb25
-rw-r--r--spec/ruby/core/array/shared/join.rb15
-rw-r--r--spec/ruby/core/array/shared/keep_if.rb95
-rw-r--r--spec/ruby/core/array/shared/union.rb79
10 files changed, 507 insertions, 0 deletions
diff --git a/spec/ruby/core/array/shared/clone.rb b/spec/ruby/core/array/shared/clone.rb
new file mode 100644
index 0000000000..1a45c2fe2c
--- /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.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2].send(@method).should.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/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..3fe22331bd
--- /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(TypeError)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[]).should.instance_of?(Array)
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[]).should.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..5beab5c4c4
--- /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.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..5e770bf167
--- /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 == true
+ end
+
+ it "returns true if corresponding elements are #eql?" do
+ [].send(@method, []).should == true
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4]).should == true
+ end
+
+ it "returns false if other is shorter than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3]).should == false
+ end
+
+ it "returns false if other is longer than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4, 5]).should == 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 == false
+ [obj] .send(@method, [] ).should == false
+ end
+
+ it "handles well recursive arrays" do
+ a = ArraySpecs.empty_recursive_array
+ a .send(@method, [a] ).should == true
+ a .send(@method, [[a]] ).should == true
+ [a] .send(@method, a ).should == true
+ [[a]] .send(@method, a ).should == 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 == true
+ a .send(@method, [a2] ).should == true
+ a .send(@method, [[a2]] ).should == true
+ [a] .send(@method, a2 ).should == true
+ [[a]] .send(@method, a2 ).should == true
+
+ back = []
+ forth = [back]; back << forth;
+ back .send(@method, a ).should == true
+
+ x = []; x << x << x
+ x .send(@method, a ).should == false # since x.size != a.size
+ x .send(@method, [a, a] ).should == false # since x[0].size != [a, a][0].size
+ x .send(@method, [x, a] ).should == false # since x[1].size != [x, a][1].size
+ [x, a] .send(@method, [a, x] ).should == false # etc...
+ x .send(@method, [x, x] ).should == true
+ x .send(@method, [[x, x], [x, x]] ).should == 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 == true
+ forest .send(@method, [tree2, branch, :bird, a, forest2]).should == true
+
+ diffforest = [branch2, tree2, :bird, a2]; diffforest << forest2
+ forest .send(@method, diffforest ).should == false # since forest[0].size == 1 != 3 == diffforest[0]
+ forest .send(@method, [nil] ).should == false
+ forest .send(@method, [forest] ).should == 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 == 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 == true
+ end
+
+ it "ignores array class differences" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, [1, 2, 3]).should == true
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should == true
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should == true
+ 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..dda72e8bd7
--- /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.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.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..93d5329ee3
--- /dev/null
+++ b/spec/ruby/core/array/shared/join.rb
@@ -0,0 +1,15 @@
+require_relative '../fixtures/classes'
+require_relative '../fixtures/encoded_strings'
+
+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..44625eebd1
--- /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.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.instance_of?(Enumerator)
+ end
+
+ describe "with truthy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { true } }.should.raise(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { true } }.should.raise(FrozenError)
+ end
+ end
+
+ describe "with falsy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { false } }.should.raise(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { false } }.should.raise(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(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/union.rb b/spec/ruby/core/array/shared/union.rb
new file mode 100644
index 0000000000..0b225b9a31
--- /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.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.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