diff options
Diffstat (limited to 'spec/ruby/core/enumerator')
85 files changed, 3319 insertions, 0 deletions
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb new file mode 100644 index 0000000000..bd243fa0b5 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#begin" do + it "returns the begin of the sequence" do + 1.step(10).begin.should == 1 + (1..10).step.begin.should == 1 + (1...10).step.begin.should == 1 + end + + context "with beginless" do + it "returns nil as begin of the sequence" do + (..10).step(1).begin.should == nil + (...10).step(1).begin.should == nil + end + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb new file mode 100644 index 0000000000..0a83019d49 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#each" do + before :each do + ScratchPad.record [] + @seq = 1.step(10, 4) + end + + it "calls given block on each item of the sequence" do + @seq.each { |item| ScratchPad << item } + ScratchPad.recorded.should == [1, 5, 9] + end + + it "returns self" do + @seq.each { |item| }.should.equal?(@seq) + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb new file mode 100644 index 0000000000..05429cac3e --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#end" do + it "returns the end of the sequence" do + 1.step(10).end.should == 10 + (1..10).step.end.should == 10 + (1...10).step(17).end.should == 10 + end + + context "with endless" do + it "returns nil as end of the sequence" do + (1..).step(1).end.should == nil + (1...).step(1).end.should == nil + end + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb new file mode 100644 index 0000000000..77eed02d8b --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#==" do + it "returns true if begin, end, step and exclude_end? are equal" do + 1.step(10).should == 1.step(10) + 1.step(10, 5).should == 1.step(10, 5) + + (1..10).step.should == (1..10).step + (1...10).step(8).should == (1...10).step(8) + + # both have exclude_end? == false + (1..10).step(100).should == 1.step(10, 100) + + ((1..10).step == (1..11).step).should == false + ((1..10).step == (1...10).step).should == false + ((1..10).step == (1..10).step(2)).should == false + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb new file mode 100644 index 0000000000..021fe7d90f --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#exclude_end?" do + context "when created using Numeric#step" do + it "always returns false" do + 1.step(10).should_not.exclude_end? + 10.step(1).should_not.exclude_end? + end + end + + context "when created using Range#step" do + it "mirrors range.exclude_end?" do + (1...10).step.should.exclude_end? + (1..10).step.should_not.exclude_end? + end + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb new file mode 100644 index 0000000000..ccd02be020 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#first" do + it "returns the first element of the sequence" do + 1.step(10).first.should == 1 + (1..10).step.first.should == 1 + (1...10).step.first.should == 1 + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb new file mode 100644 index 0000000000..a18c554fb3 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#hash" do + it "is based on begin, end, step and exclude_end?" do + 1.step(10).hash.should.instance_of?(Integer) + + 1.step(10).hash.should == 1.step(10).hash + 1.step(10, 5).hash.should == 1.step(10, 5).hash + + (1..10).step.hash.should == (1..10).step.hash + (1...10).step(8).hash.should == (1...10).step(8).hash + + # both have exclude_end? == false + (1..10).step(100).hash.should == 1.step(10, 100).hash + + ((1..10).step.hash == (1..11).step.hash).should == false + ((1..10).step.hash == (1...10).step.hash).should == false + ((1..10).step.hash == (1..10).step(2).hash).should == false + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb new file mode 100644 index 0000000000..b73b49d272 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#inspect" do + context 'when Numeric#step is used' do + it "returns '(begin.step(end{, step}))'" do + 1.step(10).inspect.should == "(1.step(10))" + 1.step(10, 3).inspect.should == "(1.step(10, 3))" + end + end + + context 'when Range#step is used' do + it "returns '((range).step{(step)})'" do + (1..10).step.inspect.should == "((1..10).step)" + (1..10).step(3).inspect.should == "((1..10).step(3))" + + (1...10).step.inspect.should == "((1...10).step)" + (1...10).step(3).inspect.should == "((1...10).step(3))" + end + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb new file mode 100644 index 0000000000..31f982b7c4 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#last" do + it "returns the last element of the sequence" do + 1.step(10).last.should == 10 + (1..10).step.last.should == 10 + (1...10).step(4).last.should == 9 + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb new file mode 100644 index 0000000000..1bd2f7f0f7 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence.new" do + it "is not defined" do + -> { + Enumerator::ArithmeticSequence.new + }.should.raise(NoMethodError) + end +end + +describe "Enumerator::ArithmeticSequence.allocate" do + it "is not defined" do + -> { + Enumerator::ArithmeticSequence.allocate + }.should.raise(TypeError, 'allocator undefined for Enumerator::ArithmeticSequence') + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb new file mode 100644 index 0000000000..7e03edd961 --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#size" do + context "for finite sequence" do + it "returns the number of elements in this arithmetic sequence" do + 1.step(10).size.should == 10 + (1...10).step.size.should == 9 + end + end + + context "for infinite sequence" do + it "returns Infinity" do + 1.step(Float::INFINITY).size.should == Float::INFINITY + (1..Float::INFINITY).step.size.should == Float::INFINITY + end + end +end diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb new file mode 100644 index 0000000000..c1f2d9173f --- /dev/null +++ b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::ArithmeticSequence#step" do + it "returns the original value given to step method" do + (1..10).step.step.should == 1 + (1..10).step(3).step.should == 3 + + 1.step(10).step.should == 1 + 1.step(10, 3).step.should == 3 + end +end diff --git a/spec/ruby/core/enumerator/chain/each_spec.rb b/spec/ruby/core/enumerator/chain/each_spec.rb new file mode 100644 index 0000000000..cc93cbac60 --- /dev/null +++ b/spec/ruby/core/enumerator/chain/each_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' +require_relative '../../enumerable/fixtures/classes' + +describe "Enumerator::Chain#each" do + it "calls each on its constituents as needed" do + a = EnumerableSpecs::EachCounter.new(:a, :b) + b = EnumerableSpecs::EachCounter.new(:c, :d) + + ScratchPad.record [] + Enumerator::Chain.new(a, b).each do |elem| + ScratchPad << elem << b.times_yielded + end + ScratchPad.recorded.should == [:a, 0, :b, 0, :c, 1, :d, 2] + end +end diff --git a/spec/ruby/core/enumerator/chain/initialize_spec.rb b/spec/ruby/core/enumerator/chain/initialize_spec.rb new file mode 100644 index 0000000000..1df1dec5f8 --- /dev/null +++ b/spec/ruby/core/enumerator/chain/initialize_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Chain#initialize" do + before :each do + @uninitialized = Enumerator::Chain.allocate + end + + it "is a private method" do + Enumerator::Chain.private_instance_methods(false).should.include?(:initialize) + end + + it "returns self" do + @uninitialized.send(:initialize).should.equal?(@uninitialized) + end + + it "accepts many arguments" do + @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should.equal?(@uninitialized) + end + + it "accepts arguments that are not Enumerable nor responding to :each" do + @uninitialized.send(:initialize, Object.new).should.equal?(@uninitialized) + end + + describe "on frozen instance" do + it "raises a FrozenError" do + -> { + @uninitialized.freeze.send(:initialize) + }.should.raise(FrozenError) + end + end +end diff --git a/spec/ruby/core/enumerator/chain/inspect_spec.rb b/spec/ruby/core/enumerator/chain/inspect_spec.rb new file mode 100644 index 0000000000..9b5a442b75 --- /dev/null +++ b/spec/ruby/core/enumerator/chain/inspect_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Chain#inspect" do + it "shows a representation of the Enumerator" do + Enumerator::Chain.new.inspect.should == "#<Enumerator::Chain: []>" + Enumerator::Chain.new(1..2, 3..4).inspect.should == "#<Enumerator::Chain: [1..2, 3..4]>" + end + + it "calls inspect on its chain elements" do + obj = mock('inspect') + obj.should_receive(:inspect).and_return('some desc') + Enumerator::Chain.new(obj).inspect.should == "#<Enumerator::Chain: [some desc]>" + end + + it "returns a not initialized representation if #initialized is not called yet" do + Enumerator::Chain.allocate.inspect.should == "#<Enumerator::Chain: uninitialized>" + end +end diff --git a/spec/ruby/core/enumerator/chain/rewind_spec.rb b/spec/ruby/core/enumerator/chain/rewind_spec.rb new file mode 100644 index 0000000000..4525b82f7b --- /dev/null +++ b/spec/ruby/core/enumerator/chain/rewind_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Chain#rewind" do + before(:each) do + @obj = mock('obj') + @obj.should_receive(:each).any_number_of_times.and_yield + @second = mock('obj') + @second.should_receive(:each).any_number_of_times.and_yield + @enum = Enumerator::Chain.new(@obj, @second) + end + + it "returns self" do + @enum.rewind.should.equal? @enum + end + + it "does nothing if receiver has not been iterated" do + @obj.should_not_receive(:rewind) + @obj.respond_to?(:rewind).should == true # sanity check + @enum.rewind + end + + it "does nothing on objects that don't respond_to rewind" do + @obj.respond_to?(:rewind).should == false # sanity check + @enum.each {} + @enum.rewind + end + + it "calls_rewind its objects" do + @obj.should_receive(:rewind) + @enum.each {} + @enum.rewind + end + + it "calls_rewind in reverse order" do + @obj.should_not_receive(:rewind) + @second.should_receive(:rewind).and_raise(RuntimeError) + @enum.each {} + -> { @enum.rewind }.should.raise(RuntimeError) + end + + it "calls rewind only for objects that have actually been iterated on" do + @obj = mock('obj') + @obj.should_receive(:each).any_number_of_times.and_raise(RuntimeError) + @enum = Enumerator::Chain.new(@obj, @second) + + @obj.should_receive(:rewind) + @second.should_not_receive(:rewind) + -> { @enum.each {} }.should.raise(RuntimeError) + @enum.rewind + end +end diff --git a/spec/ruby/core/enumerator/chain/size_spec.rb b/spec/ruby/core/enumerator/chain/size_spec.rb new file mode 100644 index 0000000000..d85b88ee8b --- /dev/null +++ b/spec/ruby/core/enumerator/chain/size_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require_relative '../../enumerable/fixtures/classes' + +describe "Enumerator::Chain#size" do + it "returns the sum of the sizes of the elements" do + a = mock('size') + a.should_receive(:size).and_return(40) + Enumerator::Chain.new(a, [:a, :b]).size.should == 42 + end + + it "returns nil or Infinity for the first element of such a size" do + [nil, Float::INFINITY].each do |special| + a = mock('size') + a.should_receive(:size).and_return(40) + b = mock('special') + b.should_receive(:size).and_return(special) + c = mock('not called') + c.should_not_receive(:size) + Enumerator::Chain.new(a, b, c).size.should == special + end + end +end diff --git a/spec/ruby/core/enumerator/each_spec.rb b/spec/ruby/core/enumerator/each_spec.rb new file mode 100644 index 0000000000..03be53fe05 --- /dev/null +++ b/spec/ruby/core/enumerator/each_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' + +describe "Enumerator#each" do + before :each do + object_each_with_arguments = Object.new + def object_each_with_arguments.each_with_arguments(arg, *args) + yield arg, *args + :method_returned + end + + @enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2) + + @enum_with_yielder = Enumerator.new { |y| y.yield :ok } + end + + it "yields each element of self to the given block" do + acc = [] + [1, 2, 3].to_enum.each { |e| acc << e } + acc.should == [1, 2, 3] + end + + it "calls #each on the object given in the constructor by default" do + each = mock('each') + each.should_receive(:each) + each.to_enum.each { |e| e } + end + + it "calls #each on the underlying object until it's exhausted" do + each = mock('each') + each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3) + acc = [] + each.to_enum.each { |e| acc << e } + acc.should == [1, 2, 3] + end + + it "calls the method given in the constructor instead of #each" do + each = mock('peach') + each.should_receive(:peach) + each.to_enum(:peach).each { |e| e } + end + + it "calls the method given in the constructor until it's exhausted" do + each = mock('peach') + each.should_receive(:peach).and_yield(1).and_yield(2).and_yield(3) + acc = [] + each.to_enum(:peach).each { |e| acc << e } + acc.should == [1, 2, 3] + end + + it "raises a NoMethodError if the object doesn't respond to #each" do + enum = Object.new.to_enum + -> do + enum.each { |e| e } + end.should.raise(NoMethodError) + end + + it "returns self if not given arguments and not given a block" do + @enum_with_arguments.each.should.equal?(@enum_with_arguments) + + @enum_with_yielder.each.should.equal?(@enum_with_yielder) + end + + it "returns the same value from receiver.each if block is given" do + @enum_with_arguments.each {}.should.equal?(:method_returned) + end + + it "passes given arguments at initialized to receiver.each" do + @enum_with_arguments.each.to_a.should == [[:arg0, :arg1, :arg2]] + end + + it "requires multiple arguments" do + Enumerator.instance_method(:each).arity.should < 0 + end + + it "appends given arguments to receiver.each" do + @enum_with_arguments.each(:each0, :each1).to_a.should == [[:arg0, :arg1, :arg2, :each0, :each1]] + @enum_with_arguments.each(:each2, :each3).to_a.should == [[:arg0, :arg1, :arg2, :each2, :each3]] + end + + it "returns the same value from receiver.each if block and arguments are given" do + @enum_with_arguments.each(:each1, :each2) {}.should.equal?(:method_returned) + end + + it "returns new Enumerator if given arguments but not given a block" do + ret = @enum_with_arguments.each 1 + ret.should.instance_of?(Enumerator) + ret.should_not.equal?(@enum_with_arguments) + end + + it "does not destructure yielded array values when chaining each.map" do + result = [[[1]]].each.map { |a, b| [a, b] } + result.should == [[[1], nil]] + end + + it "preserves array values yielded from the enumerator" do + result = [[1, 2]].each.map { |a| a } + result.should == [[1, 2]] + end + + it "allows destructuring to occur in the block, not the enumerator" do + result = [[1, 2]].each.map { |a, b| a } + result.should == [1] + end +end diff --git a/spec/ruby/core/enumerator/each_with_index_spec.rb b/spec/ruby/core/enumerator/each_with_index_spec.rb new file mode 100644 index 0000000000..0630b7045e --- /dev/null +++ b/spec/ruby/core/enumerator/each_with_index_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require_relative 'shared/with_index' +require_relative '../enumerable/shared/enumeratorized' + +describe "Enumerator#each_with_index" do + it_behaves_like :enum_with_index, :each_with_index + it_behaves_like :enumeratorized_with_origin_size, :each_with_index, [1,2,3].select + + it "returns a new Enumerator when no block is given" do + enum1 = [1,2,3].select + enum2 = enum1.each_with_index + enum2.should.instance_of?(Enumerator) + enum1.should_not == enum2 + end + + it "raises an ArgumentError if passed extra arguments" do + -> do + [1].to_enum.each_with_index(:glark) + end.should.raise(ArgumentError) + end + + it "passes on the given block's return value" do + arr = [1,2,3] + arr.delete_if.each_with_index { |a,b| false } + arr.should == [1,2,3] + end + + it "returns the iterator's return value" do + [1,2,3].select.each_with_index { |a,b| false }.should == [] + [1,2,3].select.each_with_index { |a,b| true }.should == [1,2,3] + end + + it "returns the correct value if chained with itself" do + [:a].each_with_index.each_with_index.to_a.should == [[[:a,0],0]] + end +end diff --git a/spec/ruby/core/enumerator/each_with_object_spec.rb b/spec/ruby/core/enumerator/each_with_object_spec.rb new file mode 100644 index 0000000000..84a45ae89d --- /dev/null +++ b/spec/ruby/core/enumerator/each_with_object_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/with_object' + +describe "Enumerator#each_with_object" do + it_behaves_like :enum_with_object, :each_with_object +end diff --git a/spec/ruby/core/enumerator/enum_for_spec.rb b/spec/ruby/core/enumerator/enum_for_spec.rb new file mode 100644 index 0000000000..fbdf98545a --- /dev/null +++ b/spec/ruby/core/enumerator/enum_for_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/enum_for' + +describe "Enumerator#enum_for" do + it_behaves_like :enum_for, :enum_for +end diff --git a/spec/ruby/core/enumerator/enumerator_spec.rb b/spec/ruby/core/enumerator/enumerator_spec.rb new file mode 100644 index 0000000000..7a263336cb --- /dev/null +++ b/spec/ruby/core/enumerator/enumerator_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Enumerator" do + it "includes Enumerable" do + Enumerator.include?(Enumerable).should == true + end +end diff --git a/spec/ruby/core/enumerator/feed_spec.rb b/spec/ruby/core/enumerator/feed_spec.rb new file mode 100644 index 0000000000..781947a8c7 --- /dev/null +++ b/spec/ruby/core/enumerator/feed_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Enumerator#feed" do + before :each do + ScratchPad.record [] + @enum = EnumeratorSpecs::Feed.new.to_enum(:each) + end + + it "sets the future return value of yield if called before advancing the iterator" do + @enum.feed :a + @enum.next + @enum.next + @enum.next + ScratchPad.recorded.should == [:a, nil] + end + + it "causes yield to return the value if called during iteration" do + @enum.next + @enum.feed :a + @enum.next + @enum.next + ScratchPad.recorded.should == [:a, nil] + end + + it "can be called for each iteration" do + @enum.next + @enum.feed :a + @enum.next + @enum.feed :b + @enum.next + ScratchPad.recorded.should == [:a, :b] + end + + it "returns nil" do + @enum.feed(:a).should == nil + end + + it "raises a TypeError if called more than once without advancing the enumerator" do + @enum.feed :a + @enum.next + -> { @enum.feed :b }.should.raise(TypeError) + end + + it "sets the return value of Yielder#yield" do + enum = Enumerator.new { |y| ScratchPad << y.yield } + enum.next + enum.feed :a + -> { enum.next }.should.raise(StopIteration) + ScratchPad.recorded.should == [:a] + end +end diff --git a/spec/ruby/core/enumerator/first_spec.rb b/spec/ruby/core/enumerator/first_spec.rb new file mode 100644 index 0000000000..458080bb31 --- /dev/null +++ b/spec/ruby/core/enumerator/first_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Enumerator#first" do + it "returns arrays correctly when calling #first (2376)" do + Enumerator.new {|y| y << [42] }.first.should == [42] + end +end diff --git a/spec/ruby/core/enumerator/fixtures/classes.rb b/spec/ruby/core/enumerator/fixtures/classes.rb new file mode 100644 index 0000000000..6f285b8efa --- /dev/null +++ b/spec/ruby/core/enumerator/fixtures/classes.rb @@ -0,0 +1,15 @@ +module EnumSpecs + class Numerous + + include Enumerable + + def initialize(*list) + @list = list.empty? ? [2, 5, 3, 6, 1, 4] : list + end + + def each + @list.each { |i| yield i } + end + end + +end diff --git a/spec/ruby/core/enumerator/fixtures/common.rb b/spec/ruby/core/enumerator/fixtures/common.rb new file mode 100644 index 0000000000..e332e3195b --- /dev/null +++ b/spec/ruby/core/enumerator/fixtures/common.rb @@ -0,0 +1,9 @@ +module EnumeratorSpecs + class Feed + def each + ScratchPad << yield + ScratchPad << yield + ScratchPad << yield + end + end +end diff --git a/spec/ruby/core/enumerator/initialize_spec.rb b/spec/ruby/core/enumerator/initialize_spec.rb new file mode 100644 index 0000000000..9929494b5a --- /dev/null +++ b/spec/ruby/core/enumerator/initialize_spec.rb @@ -0,0 +1,57 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../spec_helper' + +describe "Enumerator#initialize" do + before :each do + @uninitialized = Enumerator.allocate + end + + it "is a private method" do + Enumerator.private_instance_methods(false).should.include?(:initialize) + end + + it "returns self when given a block" do + @uninitialized.send(:initialize) {}.should.equal?(@uninitialized) + end + + # Maybe spec should be broken up? + it "accepts a block" do + @uninitialized.send(:initialize) do |yielder| + r = yielder.yield 3 + yielder << r << 2 << 1 + end + @uninitialized.should.instance_of?(Enumerator) + r = [] + @uninitialized.each{|x| r << x; x * 2} + r.should == [3, 6, 2, 1] + end + + it "sets size to nil if size is not given" do + @uninitialized.send(:initialize) {}.size.should == nil + end + + it "sets size to nil if the given size is nil" do + @uninitialized.send(:initialize, nil) {}.size.should == nil + end + + it "sets size to the given size if the given size is Float::INFINITY" do + @uninitialized.send(:initialize, Float::INFINITY) {}.size.should.equal?(Float::INFINITY) + end + + it "sets size to the given size if the given size is an Integer" do + @uninitialized.send(:initialize, 100) {}.size.should == 100 + end + + it "sets size to the given size if the given size is a Proc" do + @uninitialized.send(:initialize, -> { 200 }) {}.size.should == 200 + end + + describe "on frozen instance" do + it "raises a FrozenError" do + -> { + @uninitialized.freeze.send(:initialize) {} + }.should.raise(FrozenError) + end + end +end diff --git a/spec/ruby/core/enumerator/inspect_spec.rb b/spec/ruby/core/enumerator/inspect_spec.rb new file mode 100644 index 0000000000..7e97864246 --- /dev/null +++ b/spec/ruby/core/enumerator/inspect_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' + +describe "Enumerator#inspect" do + describe "shows a representation of the Enumerator" do + it "including receiver and method" do + (1..3).each.inspect.should == "#<Enumerator: 1..3:each>" + end + + it "including receiver and method and arguments" do + (1..3).each_slice(2).inspect.should == "#<Enumerator: 1..3:each_slice(2)>" + end + + it "including the nested Enumerator" do + (1..3).each.each_slice(2).inspect.should == "#<Enumerator: #<Enumerator: 1..3:each>:each_slice(2)>" + end + end + + it "returns a not initialized representation if #initialized is not called yet" do + Enumerator.allocate.inspect.should == "#<Enumerator: uninitialized>" + Enumerator::Lazy.allocate.inspect.should == "#<Enumerator::Lazy: uninitialized>" + end +end diff --git a/spec/ruby/core/enumerator/lazy/chunk_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_spec.rb new file mode 100644 index 0000000000..d0179d32b6 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/chunk_spec.rb @@ -0,0 +1,67 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#chunk" do + + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.chunk {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.chunk { |v| v }.size.should == nil + end + + it "returns an Enumerator if called without a block" do + chunk = @yieldsmixed.chunk + chunk.should.instance_of?(Enumerator::Lazy) + + res = chunk.each { |v| true }.force + res.should == [[true, EnumeratorLazySpecs::YieldsMixed.gathered_yields]] + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + first_two = (0..Float::INFINITY).lazy.chunk { |n| n.even? }.first(2) + first_two.should == [[true, [0]], [false, [1]]] + end + end + + it "calls the block with gathered values when yield with multiple arguments" do + yields = [] + @yieldsmixed.chunk { |v| yields << v; true }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).chunk { |v| v }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + remains_lazy = (0..Float::INFINITY).lazy.chunk { |n| n } + remains_lazy.chunk { |n| n }.first(2).size.should == 2 + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.chunk { |n| n.even? }.first(100).should == + s.first(100).chunk { |n| n.even? }.to_a + end +end diff --git a/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb new file mode 100644 index 0000000000..edba8e1363 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#chunk_while" do + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.chunk_while { |a, b| false }.first(100).should == + s.first(100).chunk_while { |a, b| false }.to_a + end + + it "should return a lazy enumerator" do + s = 0..Float::INFINITY + s.lazy.chunk_while { |a, b| false }.should.is_a?(Enumerator::Lazy) + end +end diff --git a/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb new file mode 100644 index 0000000000..8765bb2190 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb @@ -0,0 +1,8 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/collect_concat' + +describe "Enumerator::Lazy#collect_concat" do + it_behaves_like :enumerator_lazy_collect_concat, :collect_concat +end diff --git a/spec/ruby/core/enumerator/lazy/collect_spec.rb b/spec/ruby/core/enumerator/lazy/collect_spec.rb new file mode 100644 index 0000000000..14b79ce16d --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/collect_spec.rb @@ -0,0 +1,8 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/collect' + +describe "Enumerator::Lazy#collect" do + it_behaves_like :enumerator_lazy_collect, :collect +end diff --git a/spec/ruby/core/enumerator/lazy/compact_spec.rb b/spec/ruby/core/enumerator/lazy/compact_spec.rb new file mode 100644 index 0000000000..7305e1c9c4 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/compact_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#compact" do + it 'returns array without nil elements' do + arr = [1, nil, 3, false, 5].to_enum.lazy.compact + arr.should.instance_of?(Enumerator::Lazy) + arr.force.should == [1, 3, false, 5] + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.compact.size.should == nil + end +end diff --git a/spec/ruby/core/enumerator/lazy/drop_spec.rb b/spec/ruby/core/enumerator/lazy/drop_spec.rb new file mode 100644 index 0000000000..95ac7e9ecc --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/drop_spec.rb @@ -0,0 +1,58 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#drop" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.drop(1) + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets difference of given count with old size to new size" do + Enumerator::Lazy.new(Object.new, 100) {}.drop(20).size.should == 80 + Enumerator::Lazy.new(Object.new, 100) {}.drop(200).size.should == 0 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.drop(2).first(2).should == [2, 3] + + @eventsmixed.drop(0).first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + describe "on a nested Lazy" do + it "sets difference of given count with old size to new size" do + Enumerator::Lazy.new(Object.new, 100) {}.drop(20).drop(50).size.should == 30 + Enumerator::Lazy.new(Object.new, 100) {}.drop(50).drop(20).size.should == 30 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.drop(2).drop(2).first(2).should == [4, 5] + + @eventsmixed.drop(0).drop(0).first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.drop(100).first(100).should == + s.first(200).drop(100) + end +end diff --git a/spec/ruby/core/enumerator/lazy/drop_while_spec.rb b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb new file mode 100644 index 0000000000..65f3007dec --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb @@ -0,0 +1,66 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#drop_while" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.drop_while {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.drop_while { |v| v }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.first(2).should == [5, 6] + + @eventsmixed.drop_while { false }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.drop_while { |v| yields << v; true }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.drop_while }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).drop_while { |v| v }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.drop_while { |n| n.odd? }.first(2).should == [6, 7] + + @eventsmixed.drop_while { false }.drop_while { false }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.drop_while { |n| n < 100 }.first(100).should == + s.first(200).drop_while { |n| n < 100 } + end +end diff --git a/spec/ruby/core/enumerator/lazy/eager_spec.rb b/spec/ruby/core/enumerator/lazy/eager_spec.rb new file mode 100644 index 0000000000..592da4fd8c --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/eager_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#eager" do + it "returns a non-lazy Enumerator converted from the lazy enumerator" do + enum = [1, 2, 3].lazy + + enum.class.should == Enumerator::Lazy + enum.eager.class.should == Enumerator + end + + it "does not enumerate an enumerator" do + ScratchPad.record [] + + sequence = [1, 2, 3] + enum_lazy = Enumerator::Lazy.new(sequence) do |yielder, value| + yielder << value + ScratchPad << value + end + + ScratchPad.recorded.should == [] + enum = enum_lazy.eager + ScratchPad.recorded.should == [] + + enum.map { |i| i }.should == [1, 2, 3] + ScratchPad.recorded.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/enumerator/lazy/enum_for_spec.rb b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb new file mode 100644 index 0000000000..7e7783f6f1 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb @@ -0,0 +1,8 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/to_enum' + +describe "Enumerator::Lazy#enum_for" do + it_behaves_like :enumerator_lazy_to_enum, :enum_for +end diff --git a/spec/ruby/core/enumerator/lazy/filter_map_spec.rb b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb new file mode 100644 index 0000000000..99dd59cebe --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb @@ -0,0 +1,14 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#filter_map" do + it "maps only truthy results" do + (1..Float::INFINITY).lazy.filter_map { |i| i if i.odd? }.first(4).should == [1, 3, 5, 7] + end + + it "does not map false results" do + (1..Float::INFINITY).lazy.filter_map { |i| i.odd? ? i : false }.first(4).should == [1, 3, 5, 7] + end +end diff --git a/spec/ruby/core/enumerator/lazy/filter_spec.rb b/spec/ruby/core/enumerator/lazy/filter_spec.rb new file mode 100644 index 0000000000..43128241e0 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/filter_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/select' + +describe "Enumerator::Lazy#filter" do + it_behaves_like :enumerator_lazy_select, :filter +end diff --git a/spec/ruby/core/enumerator/lazy/find_all_spec.rb b/spec/ruby/core/enumerator/lazy/find_all_spec.rb new file mode 100644 index 0000000000..8b05c53803 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/find_all_spec.rb @@ -0,0 +1,8 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/select' + +describe "Enumerator::Lazy#find_all" do + it_behaves_like :enumerator_lazy_select, :find_all +end diff --git a/spec/ruby/core/enumerator/lazy/fixtures/classes.rb b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb new file mode 100644 index 0000000000..e35592ba1c --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb @@ -0,0 +1,54 @@ +# -*- encoding: us-ascii -*- + +module EnumeratorLazySpecs + class SpecificError < Exception; end + + class YieldsMixed + def self.initial_yields + [nil, 0, 0, 0, 0, nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]] + end + + def self.gathered_yields + [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]] + end + + def self.gathered_non_array_yields + [nil, 0, nil, :default_arg] + end + + def self.gathered_yields_with_args(arg, *args) + [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, arg, args, [], [0], [0, 1], [0, 1, 2]] + end + + def each(arg=:default_arg, *args) + yield + yield 0 + yield 0, 1 + yield 0, 1, 2 + yield(*[0, 1, 2]) + yield nil + yield arg + yield args + yield [] + yield [0] + yield [0, 1] + yield [0, 1, 2] + end + end + + class EventsMixed + def each + ScratchPad << :before_yield + + yield 0 + + ScratchPad << :after_yield + + raise SpecificError + + ScratchPad << :after_error + + :should_not_reach_here + end + end +end diff --git a/spec/ruby/core/enumerator/lazy/flat_map_spec.rb b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb new file mode 100644 index 0000000000..5dcaa8bfa1 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb @@ -0,0 +1,16 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/collect_concat' + +describe "Enumerator::Lazy#flat_map" do + it_behaves_like :enumerator_lazy_collect_concat, :flat_map + + it "properly unwraps nested yields" do + s = Enumerator.new do |y| loop do y << [1, 2] end end + + expected = s.take(3).flat_map { |x| x }.to_a + actual = s.lazy.take(3).flat_map{ |x| x }.force + actual.should == expected + end +end diff --git a/spec/ruby/core/enumerator/lazy/force_spec.rb b/spec/ruby/core/enumerator/lazy/force_spec.rb new file mode 100644 index 0000000000..a7fa029135 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/force_spec.rb @@ -0,0 +1,36 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#force" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "passes given arguments to receiver.each" do + @yieldsmixed.force(:arg1, :arg2, :arg3).should == + EnumeratorLazySpecs::YieldsMixed.gathered_yields_with_args(:arg1, :arg2, :arg3) + end + + describe "on a nested Lazy" do + it "calls all block and returns an Array" do + (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2] + + @eventsmixed.take(1).map(&:succ).force.should == [1] + ScratchPad.recorded.should == [:before_yield] + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.take(100).force.should == + s.take(100) + end +end diff --git a/spec/ruby/core/enumerator/lazy/grep_spec.rb b/spec/ruby/core/enumerator/lazy/grep_spec.rb new file mode 100644 index 0000000000..383f80a918 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/grep_spec.rb @@ -0,0 +1,121 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#grep" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "requires an argument" do + Enumerator::Lazy.instance_method(:grep).arity.should == 1 + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.grep(Object) {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + + ret = @yieldsmixed.grep(Object) + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil + Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil + end + + it "sets $~ in the block" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep(/b/) { |e| + e.should == "abc" + $&.should == "b" + }.force + + # Set by the failed match of "def" + $~.should == nil + end + + it "sets $~ in the next block with each" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep(/b/).each { |e| + e.should == "abc" + $&.should == "b" + } + + # Set by the failed match of "def" + $~.should == nil + end + + it "sets $~ in the next block with map" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep(/b/).map { |e| + e.should == "abc" + $&.should == "b" + }.force + + # Set by the failed match of "def" + $~.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times when not given a block" do + (0..Float::INFINITY).lazy.grep(Integer).first(3).should == [0, 1, 2] + + @eventsmixed.grep(BasicObject).first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "stops after specified times when given a block" do + (0..Float::INFINITY).lazy.grep(Integer, &:succ).first(3).should == [1, 2, 3] + + @eventsmixed.grep(BasicObject) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = [] + @yieldsmixed.grep(BasicObject) { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields + + @yieldsmixed.grep(BasicObject).force.should == yields + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil + Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times when not given a block" do + (0..Float::INFINITY).lazy.grep(Integer).grep(Object).first(3).should == [0, 1, 2] + + @eventsmixed.grep(BasicObject).grep(Object).first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "stops after specified times when given a block" do + (0..Float::INFINITY).lazy.grep(Integer) { |n| n > 3 ? n : false }.grep(Integer) { |n| n.even? ? n : false }.first(3).should == [4, false, 6] + + @eventsmixed.grep(BasicObject) {}.grep(Object) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.grep(Numeric).first(100).should == + s.first(100).grep(Numeric) + end +end diff --git a/spec/ruby/core/enumerator/lazy/grep_v_spec.rb b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb new file mode 100644 index 0000000000..19c917f254 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb @@ -0,0 +1,123 @@ +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#grep_v" do + before(:each) do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after(:each) do + ScratchPad.clear + end + + it "requires an argument" do + Enumerator::Lazy.instance_method(:grep_v).arity.should == 1 + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.grep_v(Object) {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + + ret = @yieldsmixed.grep_v(Object) + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object) {}.size.should == nil + Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).size.should == nil + end + + it "sets $~ in the block" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep_v(/e/) { |e| + e.should == "abc" + $~.should == nil + }.force + + # Set by the match of "def" + $&.should == "e" + end + + it "sets $~ in the next block with each" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep_v(/e/).each { |e| + e.should == "abc" + $~.should == nil + } + + # Set by the match of "def" + $&.should == "e" + end + + it "sets $~ in the next block with map" do + "z" =~ /z/ # Reset $~ + ["abc", "def"].lazy.grep_v(/e/).map { |e| + e.should == "abc" + $~.should == nil + }.force + + # Set by the match of "def" + $&.should == "e" + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times when not given a block" do + (0..Float::INFINITY).lazy.grep_v(3..5).first(3).should == [0, 1, 2] + + @eventsmixed.grep_v(Symbol).first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "stops after specified times when given a block" do + (0..Float::INFINITY).lazy.grep_v(4..8, &:succ).first(3).should == [1, 2, 3] + + @eventsmixed.grep_v(Symbol) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = [] + @yieldsmixed.grep_v(Array) { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_non_array_yields + + @yieldsmixed.grep_v(Array).force.should == yields + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object) {}.size.should == nil + Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object).size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times when not given a block" do + (0..Float::INFINITY).lazy.grep_v(3..5).grep_v(6..10).first(3).should == [0, 1, 2] + + @eventsmixed.grep_v(Symbol).grep_v(String).first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "stops after specified times when given a block" do + (0..Float::INFINITY).lazy + .grep_v(1..2) { |n| n > 3 ? n : false } + .grep_v(false) { |n| n.even? ? n : false } + .first(3) + .should == [4, false, 6] + + @eventsmixed.grep_v(Symbol) {}.grep_v(String) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.grep_v(String).first(100).should == + s.first(100).grep_v(String) + end +end diff --git a/spec/ruby/core/enumerator/lazy/initialize_spec.rb b/spec/ruby/core/enumerator/lazy/initialize_spec.rb new file mode 100644 index 0000000000..47765d32c3 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/initialize_spec.rb @@ -0,0 +1,63 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#initialize" do + before :each do + @receiver = receiver = Object.new + + def receiver.each + yield 0 + yield 1 + yield 2 + end + + @uninitialized = Enumerator::Lazy.allocate + end + + it "is a private method" do + Enumerator::Lazy.private_instance_methods(false).should.include?(:initialize) + end + + it "returns self" do + @uninitialized.send(:initialize, @receiver) {}.should.equal?(@uninitialized) + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + @uninitialized.send(:initialize, @receiver) do |yielder, *values| + yielder.<<(*values) + end.first(2).should == [0, 1] + end + end + + it "sets #size to nil if not given a size" do + @uninitialized.send(:initialize, @receiver) {}.size.should == nil + end + + it "sets #size to nil if given size is nil" do + @uninitialized.send(:initialize, @receiver, nil) {}.size.should == nil + end + + it "sets given size to own size if the given size is Float::INFINITY" do + @uninitialized.send(:initialize, @receiver, Float::INFINITY) {}.size.should.equal?(Float::INFINITY) + end + + it "sets given size to own size if the given size is an Integer" do + @uninitialized.send(:initialize, @receiver, 100) {}.size.should == 100 + end + + it "sets given size to own size if the given size is a Proc" do + @uninitialized.send(:initialize, @receiver, -> { 200 }) {}.size.should == 200 + end + + it "raises an ArgumentError when block is not given" do + -> { @uninitialized.send :initialize, @receiver }.should.raise(ArgumentError) + end + + describe "on frozen instance" do + it "raises a FrozenError" do + -> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should.raise(FrozenError) + end + end +end diff --git a/spec/ruby/core/enumerator/lazy/lazy_spec.rb b/spec/ruby/core/enumerator/lazy/lazy_spec.rb new file mode 100644 index 0000000000..12107383d6 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/lazy_spec.rb @@ -0,0 +1,27 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy" do + it "is a subclass of Enumerator" do + Enumerator::Lazy.superclass.should.equal?(Enumerator) + end + + it "defines lazy versions of a whitelist of Enumerator methods" do + lazy_methods = Set[ + :chunk, :chunk_while, :collect, :collect_concat, :compact, :drop, :drop_while, :enum_for, + :find_all, :flat_map, :force, :grep, :grep_v, :lazy, :map, :reject, + :select, :slice_after, :slice_before, :slice_when, :take, :take_while, + :to_enum, :uniq, :zip + ] + + Enumerator::Lazy.instance_methods(false).to_set.should >= lazy_methods + end +end + +describe "Enumerator::Lazy#lazy" do + it "returns self" do + lazy = (1..3).to_enum.lazy + lazy.lazy.should.equal?(lazy) + end +end diff --git a/spec/ruby/core/enumerator/lazy/map_spec.rb b/spec/ruby/core/enumerator/lazy/map_spec.rb new file mode 100644 index 0000000000..5cb998f5f7 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/map_spec.rb @@ -0,0 +1,12 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/collect' + +describe "Enumerator::Lazy#map" do + it_behaves_like :enumerator_lazy_collect, :map + + it "doesn't unwrap Arrays" do + Enumerator.new {|y| y.yield([1])}.lazy.to_a.should == [[1]] + end +end diff --git a/spec/ruby/core/enumerator/lazy/reject_spec.rb b/spec/ruby/core/enumerator/lazy/reject_spec.rb new file mode 100644 index 0000000000..374d4df14e --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/reject_spec.rb @@ -0,0 +1,78 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#reject" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.reject {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.reject {}.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.reject(&:even?).first(3).should == [1, 3, 5] + + @eventsmixed.reject { false }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "lets exceptions raised in the block go through" do + lazy = 10.times.lazy.map do |i| + raise "foo" + end + + lazy = lazy.reject(&:nil?) + + -> { + lazy.first + }.should.raise(RuntimeError, "foo") + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = [] + @yieldsmixed.reject { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.reject }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).reject {}.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.reject { |n| n < 4 }.reject(&:even?).first(3).should == [5, 7, 9] + + @eventsmixed.reject { false }.reject { false }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.reject { |n| false }.first(100).should == + s.first(100).reject { |n| false } + end +end diff --git a/spec/ruby/core/enumerator/lazy/select_spec.rb b/spec/ruby/core/enumerator/lazy/select_spec.rb new file mode 100644 index 0000000000..3773d8f0a8 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/select_spec.rb @@ -0,0 +1,47 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/select' + +describe "Enumerator::Lazy#select" do + it_behaves_like :enumerator_lazy_select, :select + + it "doesn't pre-evaluate the next element" do + eval_count = 0 + enum = %w[Text1 Text2 Text3].lazy.select do + eval_count += 1 + true + end + + eval_count.should == 0 + enum.next + eval_count.should == 1 + end + + it "doesn't over-evaluate when peeked" do + eval_count = 0 + enum = %w[Text1 Text2 Text3].lazy.select do + eval_count += 1 + true + end + + eval_count.should == 0 + enum.peek + enum.peek + eval_count.should == 1 + end + + it "doesn't re-evaluate after peek" do + eval_count = 0 + enum = %w[Text1 Text2 Text3].lazy.select do + eval_count += 1 + true + end + + eval_count.should == 0 + enum.peek + eval_count.should == 1 + enum.next + eval_count.should == 1 + end +end diff --git a/spec/ruby/core/enumerator/lazy/shared/collect.rb b/spec/ruby/core/enumerator/lazy/shared/collect.rb new file mode 100644 index 0000000000..0ed04c8e02 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/shared/collect.rb @@ -0,0 +1,62 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../../spec_helper' +require_relative '../fixtures/classes' + +describe :enumerator_lazy_collect, shared: true do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.send(@method) {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.size.should == 100 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.send(@method, &:succ).first(3).should == [1, 2, 3] + + @eventsmixed.send(@method) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.send(@method) { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + describe "on a nested Lazy" do + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.send(@method) {}.size.should == 100 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.send(@method, &:succ).send(@method, &:succ).first(3).should == [2, 3, 4] + + @eventsmixed.send(@method) {}.send(@method) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.send(@method) { |n| n }.first(100).should == + s.first(100).send(@method) { |n| n }.to_a + end +end diff --git a/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb new file mode 100644 index 0000000000..685e6d0594 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb @@ -0,0 +1,78 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../../spec_helper' +require_relative '../fixtures/classes' + +describe :enumerator_lazy_collect_concat, shared: true do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.send(@method) {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s }.first(6).should == %w[0 10 20 30 40 50] + + @eventsmixed.send(@method) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "flattens elements when the given block returned an array or responding to .each and .force" do + (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.chars }.first(6).should == %w[0 1 0 2 0 3] + (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true + (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.send(@method) { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) {}.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s }.first(6).should == %w[0 10 20 30 40 50] + + @eventsmixed.send(@method) {}.send(@method) {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "flattens elements when the given block returned an array or responding to .each and .force" do + (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.chars }.first(6).should == %w[0 1 0 2 0 3] + (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true + (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.send(@method) { |n| [-n, +n] }.first(200).should == + s.first(100).send(@method) { |n| [-n, +n] }.to_a + end +end diff --git a/spec/ruby/core/enumerator/lazy/shared/select.rb b/spec/ruby/core/enumerator/lazy/shared/select.rb new file mode 100644 index 0000000000..2d15176620 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/shared/select.rb @@ -0,0 +1,66 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../../spec_helper' +require_relative '../fixtures/classes' + +describe :enumerator_lazy_select, shared: true do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.send(@method) {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.send(@method, &:even?).first(3).should == [0, 2, 4] + + @eventsmixed.send(@method) { true }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = [] + @yieldsmixed.send(@method) { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.send(@method) { |n| n > 5 }.send(@method, &:even?).first(3).should == [6, 8, 10] + + @eventsmixed.send(@method) { true }.send(@method) { true }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.send(@method) { |n| true }.first(100).should == + s.first(100).send(@method) { |n| true } + end +end diff --git a/spec/ruby/core/enumerator/lazy/shared/to_enum.rb b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb new file mode 100644 index 0000000000..75b9aefe8c --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb @@ -0,0 +1,55 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../../spec_helper' + +describe :enumerator_lazy_to_enum, shared: true do + before :each do + @infinite = (0..Float::INFINITY).lazy + end + + it "requires multiple arguments" do + Enumerator::Lazy.instance_method(@method).arity.should < 0 + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @infinite.send @method + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@infinite) + end + + it "sets #size to nil when not given a block" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method).size.should == nil + end + + it "sets given block to size when given a block" do + Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { 30 }.size.should == 30 + end + + it "generates a lazy enumerator from the given name" do + @infinite.send(@method, :with_index, 10).first(3).should == [[0, 10], [1, 11], [2, 12]] + end + + it "passes given arguments to wrapped method" do + @infinite.send(@method, :each_slice, 2).map { |assoc| assoc.first * assoc.last }.first(4).should == [0, 6, 20, 42] + end + + it "used by some parent's methods though returning Lazy" do + { each_with_index: [], + with_index: [], + cycle: [1], + each_with_object: [Object.new], + with_object: [Object.new], + each_slice: [2], + each_entry: [], + each_cons: [2] + }.each_pair do |method, args| + @infinite.send(method, *args).should.instance_of?(Enumerator::Lazy) + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.send(@method, :with_index).first(100).should == + s.first(100).to_enum.send(@method, :with_index).to_a + end +end diff --git a/spec/ruby/core/enumerator/lazy/slice_after_spec.rb b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb new file mode 100644 index 0000000000..f8cd97178f --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#slice_after" do + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.slice_after { |n| true }.first(100).should == + s.first(100).slice_after { |n| true }.to_a + end + + it "should return a lazy enumerator" do + s = 0..Float::INFINITY + s.lazy.slice_after { |n| true }.should.is_a?(Enumerator::Lazy) + end +end diff --git a/spec/ruby/core/enumerator/lazy/slice_before_spec.rb b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb new file mode 100644 index 0000000000..192e853343 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#slice_before" do + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.slice_before { |n| true }.first(100).should == + s.first(100).slice_before { |n| true }.to_a + end + + it "should return a lazy enumerator" do + s = 0..Float::INFINITY + s.lazy.slice_before { |n| true }.should.is_a?(Enumerator::Lazy) + end +end diff --git a/spec/ruby/core/enumerator/lazy/slice_when_spec.rb b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb new file mode 100644 index 0000000000..fc9d5f5069 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Lazy#slice_when" do + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.slice_when { |a, b| true }.first(100).should == + s.first(100).slice_when { |a, b| true }.to_a + end + + it "should return a lazy enumerator" do + s = 0..Float::INFINITY + s.lazy.slice_when { |a, b| true }.should.is_a?(Enumerator::Lazy) + end +end diff --git a/spec/ruby/core/enumerator/lazy/take_spec.rb b/spec/ruby/core/enumerator/lazy/take_spec.rb new file mode 100644 index 0000000000..2dd5b939e2 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/take_spec.rb @@ -0,0 +1,74 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../enumerable/shared/value_packing' + +describe "Enumerator::Lazy#take" do + describe "value packing of source yields (matches Enumerable#take)" do + before :each do + @take = -> e { e.lazy.take(1) } + end + it_behaves_like :enumerable_value_packing, nil + end + + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.take(1) + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets given count to size if the given count is less than old size" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).size.should == 20 + Enumerator::Lazy.new(Object.new, 100) {}.take(200).size.should == 100 + end + + it "sets given count to size if the old size is Infinity" do + loop.lazy.take(20).size.should == 20 + end + + describe "when the returned lazy enumerator is evaluated by .force" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.take(2).force.should == [0, 1] + + @eventsmixed.take(1).force + ScratchPad.recorded.should == [:before_yield] + end + + it "stops without iterations if the given argument is 0" do + @eventsmixed.take(0).force + ScratchPad.recorded.should == [] + end + end + + describe "on a nested Lazy" do + it "sets given count to size if the given count is less than old size" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).take(50).size.should == 20 + Enumerator::Lazy.new(Object.new, 100) {}.take(50).take(20).size.should == 20 + end + + describe "when the returned lazy enumerator is evaluated by .force" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2] + + @eventsmixed.take(10).take(1).force + ScratchPad.recorded.should == [:before_yield] + end + + it "stops without iterations if the given argument is 0" do + @eventsmixed.take(10).take(0).force + ScratchPad.recorded.should == [] + end + end + end +end diff --git a/spec/ruby/core/enumerator/lazy/take_while_spec.rb b/spec/ruby/core/enumerator/lazy/take_while_spec.rb new file mode 100644 index 0000000000..c369712c56 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/take_while_spec.rb @@ -0,0 +1,60 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#take_while" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.take_while {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take_while { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by .force" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.force.should == [0, 1, 2] + + @eventsmixed.take_while { false }.force + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.take_while { |v| yields << v; true }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.take_while }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(20).take_while { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by .force" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.take_while(&:even?).force.should == [0] + + @eventsmixed.take_while { true }.take_while { false }.force + ScratchPad.recorded.should == [:before_yield] + end + end + end +end diff --git a/spec/ruby/core/enumerator/lazy/to_enum_spec.rb b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb new file mode 100644 index 0000000000..210e5294b7 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb @@ -0,0 +1,8 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'shared/to_enum' + +describe "Enumerator::Lazy#to_enum" do + it_behaves_like :enumerator_lazy_to_enum, :to_enum +end diff --git a/spec/ruby/core/enumerator/lazy/uniq_spec.rb b/spec/ruby/core/enumerator/lazy/uniq_spec.rb new file mode 100644 index 0000000000..d30ed8df2f --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/uniq_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe 'Enumerator::Lazy#uniq' do + context 'without block' do + before :each do + @lazy = [0, 1, 0, 1].to_enum.lazy.uniq + end + + it 'returns a lazy enumerator' do + @lazy.should.instance_of?(Enumerator::Lazy) + @lazy.force.should == [0, 1] + end + + it 'return same value after rewind' do + @lazy.force.should == [0, 1] + @lazy.force.should == [0, 1] + end + + it 'sets the size to nil' do + @lazy.size.should == nil + end + end + + context 'when yielded with an argument' do + before :each do + @lazy = [0, 1, 2, 3].to_enum.lazy.uniq(&:even?) + end + + it 'returns a lazy enumerator' do + @lazy.should.instance_of?(Enumerator::Lazy) + @lazy.force.should == [0, 1] + end + + it 'return same value after rewind' do + @lazy.force.should == [0, 1] + @lazy.force.should == [0, 1] + end + + it 'sets the size to nil' do + @lazy.size.should == nil + end + end + + context 'when yielded with multiple arguments' do + before :each do + enum = Object.new.to_enum + class << enum + def each + yield 0, 'foo' + yield 1, 'FOO' + yield 2, 'bar' + end + end + @lazy = enum.lazy + end + + it 'return same value after rewind' do + enum = @lazy.uniq { |_, label| label.downcase } + enum.force.should == [[0, 'foo'], [2, 'bar']] + enum.force.should == [[0, 'foo'], [2, 'bar']] + end + + it 'returns all yield arguments as an array' do + @lazy.uniq { |_, label| label.downcase }.force.should == [[0, 'foo'], [2, 'bar']] + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.uniq.first(100).should == + s.first(100).uniq + end +end diff --git a/spec/ruby/core/enumerator/lazy/with_index_spec.rb b/spec/ruby/core/enumerator/lazy/with_index_spec.rb new file mode 100644 index 0000000000..2e983fd3b1 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/with_index_spec.rb @@ -0,0 +1,36 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#with_index" do + it "enumerates with an index" do + (0..Float::INFINITY).lazy.with_index.map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]] + end + + it "enumerates with an index starting at a given offset" do + (0..Float::INFINITY).lazy.with_index(3).map { |i, idx| [i, idx] }.first(3).should == [[0, 3], [1, 4], [2, 5]] + end + + it "enumerates with an index starting at 0 when offset is nil" do + (0..Float::INFINITY).lazy.with_index(nil).map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]] + end + + it "raises TypeError when offset does not convert to Integer" do + -> { (0..Float::INFINITY).lazy.with_index(false).map { |i, idx| i }.first(3) }.should.raise(TypeError) + end + + it "enumerates with a given block" do + result = [] + (0..Float::INFINITY).lazy.with_index { |i, idx| result << [i * 2, idx] }.first(3) + result.should == [[0,0],[2,1],[4,2]] + end + + it "resets after a new call to each" do + enum = (0..2).lazy.with_index.map { |i, idx| [i, idx] } + result = [] + enum.each { |i, idx| result << [i, idx] } + enum.each { |i, idx| result << [i, idx] } + result.should == [[0,0], [1,1], [2,2], [0,0], [1,1], [2,2]] + end +end diff --git a/spec/ruby/core/enumerator/lazy/zip_spec.rb b/spec/ruby/core/enumerator/lazy/zip_spec.rb new file mode 100644 index 0000000000..9f612542d7 --- /dev/null +++ b/spec/ruby/core/enumerator/lazy/zip_spec.rb @@ -0,0 +1,86 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerator::Lazy#zip" do + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.zip [] + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.zip([], []).size.should == 100 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.zip([4, 5], [8]).first(2).should == [[0, 4, 8], [1, 5, nil]] + + @eventsmixed.zip([0, 1]).first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum).force + yields.should == [EnumeratorLazySpecs::YieldsMixed.gathered_yields, + EnumeratorLazySpecs::YieldsMixed.gathered_yields].transpose + end + + it "returns a Lazy when no arguments given" do + @yieldsmixed.zip.should.instance_of?(Enumerator::Lazy) + end + + it "raises a TypeError if arguments contain non-list object" do + -> { @yieldsmixed.zip [], Object.new, [] }.should.raise(TypeError) + end + + describe "on a nested Lazy" do + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.map {}.zip([], []).size.should == 100 + end + + it "behaves as Enumerable#zip when given a block" do + lazy_yields = [] + lazy_ret = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| lazy_yields << lists } + enum_yields = [] + enum_ret = EnumeratorLazySpecs::YieldsMixed.new.to_enum.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| enum_yields << lists } + + lazy_yields.should == enum_yields + lazy_ret.should == enum_ret + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map(&:succ).zip([4, 5], [8]).first(2).should == [[1, 4, 8], [2, 5, nil]] + + @eventsmixed.zip([0, 1]).zip([0, 1]).first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable and an array" do + s = 0..Float::INFINITY + s.lazy.zip(0..1000).first(100).should == + s.first(100).zip(0..100) + end + + it "works with two infinite enumerables" do + s = 0..Float::INFINITY + s.lazy.zip(s).first(100).should == + s.first(100).zip(s) + end +end diff --git a/spec/ruby/core/enumerator/new_spec.rb b/spec/ruby/core/enumerator/new_spec.rb new file mode 100644 index 0000000000..eb6c13759e --- /dev/null +++ b/spec/ruby/core/enumerator/new_spec.rb @@ -0,0 +1,115 @@ +require_relative '../../spec_helper' + +describe "Enumerator.new" do + context "no block given" do + it "raises" do + -> { Enumerator.new(1, :upto, 3) }.should.raise(ArgumentError) + end + end + + context "when passed a block" do + it "defines iteration with block, yielder argument and calling << method" do + enum = Enumerator.new do |yielder| + a = 1 + + loop do + yielder << a + a = a + 1 + end + end + + enum.take(3).should == [1, 2, 3] + end + + it "defines iteration with block, yielder argument and calling yield method" do + enum = Enumerator.new do |yielder| + a = 1 + + loop do + yielder.yield(a) + a = a + 1 + end + end + + enum.take(3).should == [1, 2, 3] + end + + it "defines iteration with block, yielder argument and treating it as a proc" do + enum = Enumerator.new do |yielder| + "a\nb\nc".each_line(&yielder) + end + + enum.to_a.should == ["a\n", "b\n", "c"] + end + + describe '#yield' do + it 'accepts a single argument' do + Enumerator.new { |y| y.yield(1) }.to_a.should == [1] + Enumerator.new { |y| y.yield(1) }.first.should == 1 + end + + it 'accepts multiple arguments' do + Enumerator.new { |y| y.yield(1, 2) }.to_a.should == [[1, 2]] + Enumerator.new { |y| y.yield(1, 2) }.first.should == [1, 2] + end + + it "doesn't double-wrap arrays" do + Enumerator.new { |y| y.yield([1]) }.to_a.should == [[1]] + Enumerator.new { |y| y.yield([1]) }.first.should == [1] + + Enumerator.new { |y| y.yield([1, 2]) }.to_a.should == [[1, 2]] + Enumerator.new { |y| y.yield([1, 2]) }.first.should == [1, 2] + end + + it 'returns nil' do + ScratchPad.record [] + Enumerator.new do |y| + ScratchPad << y.yield(1) + end.to_a + + ScratchPad.recorded.should == [nil] + end + + it 'accepts keyword arguments and treats them as a positional hash' do + Enumerator.new { |y| y.yield(foo: 42) }.to_a.should == [{ foo: 42 }] + Enumerator.new { |y| y.yield(foo: 42) }.first.should == { foo: 42 } + + Enumerator.new { |y| y.yield(123, foo: 42) }.to_a.should == [[123, { foo: 42 }]] + Enumerator.new { |y| y.yield(123, foo: 42) }.first.should == [123, { foo: 42 }] + end + end + + describe '#<<' do + it 'accepts a single argument' do + Enumerator.new { |y| y.<<(1) }.to_a.should == [1] + Enumerator.new { |y| y.<<(1) }.first.should == 1 + end + + it "doesn't double-wrap arrays" do + Enumerator.new { |y| y.<<([1]) }.to_a.should == [[1]] + Enumerator.new { |y| y.<<([1]) }.first.should == [1] + + Enumerator.new { |y| y.<<([1, 2]) }.to_a.should == [[1, 2]] + Enumerator.new { |y| y.<<([1, 2]) }.first.should == [1, 2] + end + + it 'accepts keyword arguments and treats them as a positional hash' do + Enumerator.new { |y| y.<<(foo: 42) }.to_a.should == [{ foo: 42 }] + Enumerator.new { |y| y.<<(foo: 42) }.first.should == { foo: 42 } + end + + it 'can be chained' do + enum = Enumerator.new do |y| + y << 1 << 2 + end + enum.to_a.should == [1, 2] + end + + it 'raises ArgumentError when given more than one argument' do + -> { + Enumerator.new { |y| y.<<(1, 2) }.to_a + }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + end + end +end diff --git a/spec/ruby/core/enumerator/next_spec.rb b/spec/ruby/core/enumerator/next_spec.rb new file mode 100644 index 0000000000..77e79185a9 --- /dev/null +++ b/spec/ruby/core/enumerator/next_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' + +describe "Enumerator#next" do + before :each do + @enum = 1.upto(3) + end + + it "returns the next element of the enumeration" do + @enum.next.should == 1 + @enum.next.should == 2 + @enum.next.should == 3 + end + + it "raises a StopIteration exception at the end of the stream" do + 3.times { @enum.next } + -> { @enum.next }.should.raise(StopIteration) + end + + it "cannot be called again until the enumerator is rewound" do + 3.times { @enum.next } + -> { @enum.next }.should.raise(StopIteration) + -> { @enum.next }.should.raise(StopIteration) + -> { @enum.next }.should.raise(StopIteration) + @enum.rewind + @enum.next.should == 1 + end + + it "restarts the enumerator if an exception terminated a previous iteration" do + exception = StandardError.new + enum = Enumerator.new do + raise exception + end + + result = 2.times.map { enum.next rescue $! } + + result.should == [exception, exception] + end +end diff --git a/spec/ruby/core/enumerator/next_values_spec.rb b/spec/ruby/core/enumerator/next_values_spec.rb new file mode 100644 index 0000000000..63e024e2b4 --- /dev/null +++ b/spec/ruby/core/enumerator/next_values_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' + +describe "Enumerator#next_values" do + before :each do + o = Object.new + def o.each + yield :a + yield :b1, :b2 + yield :c + yield :d1, :d2 + yield :e1, :e2, :e3 + yield nil + yield + yield [:f1, :f2] + end + + @e = o.to_enum + end + + it "returns the next element in self" do + @e.next_values.should == [:a] + end + + it "advances the position of the current element" do + @e.next.should == :a + @e.next_values.should == [:b1, :b2] + @e.next.should == :c + end + + it "advances the position of the enumerator each time when called multiple times" do + 2.times { @e.next_values } + @e.next_values.should == [:c] + @e.next.should == [:d1, :d2] + end + + it "works in concert with #rewind" do + 2.times { @e.next } + @e.rewind + @e.next_values.should == [:a] + end + + it "returns an array with only nil if yield is called with nil" do + 5.times { @e.next } + @e.next_values.should == [nil] + end + + it "returns an empty array if yield is called without arguments" do + 6.times { @e.next } + @e.next_values.should == [] + end + + it "returns an array of array if yield is called with an array" do + 7.times { @e.next } + @e.next_values.should == [[:f1, :f2]] + end + + it "raises StopIteration if called on a finished enumerator" do + 8.times { @e.next } + -> { @e.next_values }.should.raise(StopIteration) + end +end diff --git a/spec/ruby/core/enumerator/peek_spec.rb b/spec/ruby/core/enumerator/peek_spec.rb new file mode 100644 index 0000000000..096fd2b10c --- /dev/null +++ b/spec/ruby/core/enumerator/peek_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +describe "Enumerator#peek" do + before :each do + @e = (1..5).to_a.to_enum + end + + it "returns the next element in self" do + @e.peek.should == 1 + end + + it "does not advance the position of the current element" do + @e.next.should == 1 + @e.peek.should == 2 + @e.next.should == 2 + end + + it "can be called repeatedly without advancing the position of the current element" do + @e.peek + @e.peek + @e.peek.should == 1 + @e.next.should == 1 + end + + it "works in concert with #rewind" do + @e.next + @e.next + @e.rewind + @e.peek.should == 1 + end + + it "raises StopIteration if called on a finished enumerator" do + 5.times { @e.next } + -> { @e.peek }.should.raise(StopIteration) + end +end diff --git a/spec/ruby/core/enumerator/peek_values_spec.rb b/spec/ruby/core/enumerator/peek_values_spec.rb new file mode 100644 index 0000000000..63f7988bcd --- /dev/null +++ b/spec/ruby/core/enumerator/peek_values_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' + +describe "Enumerator#peek_values" do + before :each do + o = Object.new + def o.each + yield :a + yield :b1, :b2 + yield :c + yield :d1, :d2 + yield :e1, :e2, :e3 + yield nil + yield + yield [:f1, :f2] + end + + @e = o.to_enum + end + + it "returns the next element in self" do + @e.peek_values.should == [:a] + end + + it "does not advance the position of the current element" do + @e.next.should == :a + @e.peek_values.should == [:b1, :b2] + @e.next.should == [:b1, :b2] + end + + it "can be called repeatedly without advancing the position of the current element" do + @e.peek_values + @e.peek_values + @e.peek_values.should == [:a] + @e.next.should == :a + end + + it "works in concert with #rewind" do + @e.next + @e.next + @e.rewind + @e.peek_values.should == [:a] + end + + it "returns an array with only nil if yield is called with nil" do + 5.times { @e.next } + @e.peek_values.should == [nil] + end + + it "returns an empty array if yield is called without arguments" do + 6.times { @e.next } + @e.peek_values.should == [] + end + + it "returns an array of array if yield is called with an array" do + 7.times { @e.next } + @e.peek_values.should == [[:f1, :f2]] + end + + it "raises StopIteration if called on a finished enumerator" do + 8.times { @e.next } + -> { @e.peek_values }.should.raise(StopIteration) + end +end diff --git a/spec/ruby/core/enumerator/plus_spec.rb b/spec/ruby/core/enumerator/plus_spec.rb new file mode 100644 index 0000000000..d6c0fa93ac --- /dev/null +++ b/spec/ruby/core/enumerator/plus_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' + +describe "Enumerator#+" do + before :each do + ScratchPad.record [] + end + + it "returns a chain of self and provided enumerators" do + one = Enumerator.new { |y| y << 1 } + two = Enumerator.new { |y| y << 2 } + three = Enumerator.new { |y| y << 3 } + + chain = one + two + three + + chain.should.instance_of?(Enumerator::Chain) + chain.each { |item| ScratchPad << item } + ScratchPad.recorded.should == [1, 2, 3] + end + + it "calls #each on each argument" do + enum = Enumerator.new { |y| y << "one" } + + obj1 = mock("obj1") + obj1.should_receive(:each).once.and_yield("two") + + obj2 = mock("obj2") + obj2.should_receive(:each).once.and_yield("three") + + chain = enum + obj1 + obj2 + chain.each { |item| ScratchPad << item } + ScratchPad.recorded.should == ["one", "two", "three"] + end +end diff --git a/spec/ruby/core/enumerator/produce_spec.rb b/spec/ruby/core/enumerator/produce_spec.rb new file mode 100644 index 0000000000..eb1b09294e --- /dev/null +++ b/spec/ruby/core/enumerator/produce_spec.rb @@ -0,0 +1,78 @@ +require_relative '../../spec_helper' + +describe "Enumerator.produce" do + it "creates an infinite enumerator" do + enum = Enumerator.produce(0) { |prev| prev + 1 } + + enum.size.should == Float::INFINITY + enum.take(5).should == [0, 1, 2, 3, 4] + end + + it "terminates iteration when block raises StopIteration exception" do + enum = Enumerator.produce(0) do | prev| + raise StopIteration if prev >= 2 + prev + 1 + end + + enum.to_a.should == [0, 1, 2] + end + + context "when initial value skipped" do + it "uses nil instead" do + ScratchPad.record [] + enum = Enumerator.produce { |prev| ScratchPad << prev; (prev || 0) + 1 } + + enum.take(3).should == [1, 2, 3] + ScratchPad.recorded.should == [nil, 1, 2] + end + + it "starts enumerable from result of first block call" do + array = "a\nb\nc\nd".lines + lines = Enumerator.produce { array.shift }.take_while { |s| s } + + lines.should == ["a\n", "b\n", "c\n", "d"] + end + end + + it "raises ArgumentError when no block is given" do + -> { Enumerator.produce }.should.raise(ArgumentError, "no block given") + end + + ruby_version_is ""..."4.0" do + it "accepts keyword arguments as the initial value" do + enum = Enumerator.produce(a: 1, b: 1) {} + enum.take(1).should == [{a: 1, b: 1}] + end + end + + ruby_version_is "4.0" do + it "raises ArgumentError for unknown keyword arguments" do + -> { Enumerator.produce(a: 1, b: 1) {} }.should.raise(ArgumentError, /unknown keywords/) + end + end + + ruby_version_is "4.0" do + context "with size keyword argument" do + it "sets the size of the enumerator" do + enum = Enumerator.produce(0, size: 10) { |n| n + 1 } + + enum.size.should == 10 + enum.take(5).should == [0, 1, 2, 3, 4] + end + + it "accepts a callable" do + enum = Enumerator.produce(0, size: -> { 5 * 5 }) { |n| n + 1 } + + enum.size.should == 25 + enum.take(5).should == [0, 1, 2, 3, 4] + end + + it "accepts nil" do + enum = Enumerator.produce(0, size: nil) { |n| n + 1 } + + enum.size.should == nil + enum.take(5).should == [0, 1, 2, 3, 4] + end + end + end +end diff --git a/spec/ruby/core/enumerator/product/each_spec.rb b/spec/ruby/core/enumerator/product/each_spec.rb new file mode 100644 index 0000000000..a5dced4db1 --- /dev/null +++ b/spec/ruby/core/enumerator/product/each_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../../spec_helper' +require_relative '../../enumerable/shared/enumeratorized' + +describe "Enumerator::Product#each" do + it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b]) + + it "yields each element of Cartesian product of enumerators" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + acc = [] + enum.each { |e| acc << e } + acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "calls #each_entry method on enumerators" do + object1 = Object.new + def object1.each_entry + yield 1 + yield 2 + end + + object2 = Object.new + def object2.each_entry + yield :a + yield :b + end + + enum = Enumerator::Product.new(object1, object2) + acc = [] + enum.each { |e| acc << e } + acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "raises a NoMethodError if the object doesn't respond to #each_entry" do + -> { + Enumerator::Product.new(Object.new).each {} + }.should.raise(NoMethodError, /undefined method [`']each_entry' for/) + end + + it "returns enumerator if not given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.should.kind_of?(Enumerator) + + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self if given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each {}.should.equal?(enum) + end + + it "doesn't accept arguments" do + Enumerator::Product.instance_method(:each).arity.should == 0 + end + + it "yields each element to a block that takes multiple arguments" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + + acc = [] + enum.each { |x, y| acc << x } + acc.should == [1, 1, 2, 2] + + acc = [] + enum.each { |x, y| acc << y } + acc.should == [:a, :b, :a, :b] + + acc = [] + enum.each { |x, y, z| acc << z } + acc.should == [nil, nil, nil, nil] + end + + it "yields no element when any enumerable is empty" do + enum = Enumerator::Product.new([], [1]) + + acc = [] + enum.each { |x| acc << x } + acc.should == [] + + enum = Enumerator::Product.new([1], []) + + acc = [] + enum.each { |x| acc << x } + acc.should == [] + end +end diff --git a/spec/ruby/core/enumerator/product/initialize_copy_spec.rb b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb new file mode 100644 index 0000000000..b5d2b345a9 --- /dev/null +++ b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#initialize_copy" do + it "replaces content of the receiver with content of the other object" do + enum = Enumerator::Product.new([true, false]) + enum2 = Enumerator::Product.new([1, 2], [:a, :b]) + + enum.send(:initialize_copy, enum2) + enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self" do + enum = Enumerator::Product.new([true, false]) + enum2 = Enumerator::Product.new([1, 2], [:a, :b]) + + enum.send(:initialize_copy, enum2).should.equal?(enum) + end + + it "is a private method" do + Enumerator::Product.private_instance_methods(false).should.include?(:initialize_copy) + end + + it "does nothing if the argument is the same as the receiver" do + enum = Enumerator::Product.new(1..2) + enum.send(:initialize_copy, enum).should.equal?(enum) + + enum.freeze + enum.send(:initialize_copy, enum).should.equal?(enum) + end + + it "raises FrozenError if the receiver is frozen" do + enum = Enumerator::Product.new(1..2) + enum2 = Enumerator::Product.new(3..4) + + -> { enum.freeze.send(:initialize_copy, enum2) }.should.raise(FrozenError) + end + + it "raises TypeError if the objects are of different class" do + enum = Enumerator::Product.new(1..2) + enum2 = Class.new(Enumerator::Product).new(3..4) + + -> { enum.send(:initialize_copy, enum2) }.should.raise(TypeError, 'initialize_copy should take same class object') + -> { enum2.send(:initialize_copy, enum) }.should.raise(TypeError, 'initialize_copy should take same class object') + end + + it "raises ArgumentError if the argument is not initialized yet" do + enum = Enumerator::Product.new(1..2) + enum2 = Enumerator::Product.allocate + + -> { enum.send(:initialize_copy, enum2) }.should.raise(ArgumentError, 'uninitialized product') + end +end diff --git a/spec/ruby/core/enumerator/product/initialize_spec.rb b/spec/ruby/core/enumerator/product/initialize_spec.rb new file mode 100644 index 0000000000..8814f9d3c7 --- /dev/null +++ b/spec/ruby/core/enumerator/product/initialize_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#initialize" do + before :each do + @uninitialized = Enumerator::Product.allocate + end + + it "is a private method" do + Enumerator::Product.private_instance_methods(false).should.include?(:initialize) + end + + it "returns self" do + @uninitialized.send(:initialize).should.equal?(@uninitialized) + end + + it "accepts many arguments" do + @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should.equal?(@uninitialized) + end + + it "accepts arguments that are not Enumerable nor responding to :each_entry" do + @uninitialized.send(:initialize, Object.new).should.equal?(@uninitialized) + end + + describe "on frozen instance" do + it "raises a FrozenError" do + -> { + @uninitialized.freeze.send(:initialize, 0..1) + }.should.raise(FrozenError) + end + end +end diff --git a/spec/ruby/core/enumerator/product/inspect_spec.rb b/spec/ruby/core/enumerator/product/inspect_spec.rb new file mode 100644 index 0000000000..e0d7441f26 --- /dev/null +++ b/spec/ruby/core/enumerator/product/inspect_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#inspect" do + it "returns a String including enumerators" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.inspect.should == "#<Enumerator::Product: [[1, 2], [:a, :b]]>" + end + + it "represents a recursive element with '[...]'" do + enum = [1, 2] + enum_recursive = Enumerator::Product.new(enum) + + enum << enum_recursive + enum_recursive.inspect.should == "#<Enumerator::Product: [[1, 2, #<Enumerator::Product: ...>]]>" + end + + it "returns a not initialized representation if #initialized is not called yet" do + Enumerator::Product.allocate.inspect.should == "#<Enumerator::Product: uninitialized>" + end +end diff --git a/spec/ruby/core/enumerator/product/rewind_spec.rb b/spec/ruby/core/enumerator/product/rewind_spec.rb new file mode 100644 index 0000000000..2beffaf5c1 --- /dev/null +++ b/spec/ruby/core/enumerator/product/rewind_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#rewind" do + before :each do + @enum = Enumerator::Product.new([1, 2].each.to_enum, [:a, :b].each.to_enum) + end + + it "resets the enumerator to its initial state" do + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self" do + @enum.rewind.should.equal? @enum + end + + it "has no effect on a new enumerator" do + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "has no effect if called multiple, consecutive times" do + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + @enum.rewind + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "calls the enclosed object's rewind method if one exists" do + obj = mock('rewinder') + enum = Enumerator::Product.new(obj.to_enum) + + obj.should_receive(:rewind) + enum.rewind + end + + it "does nothing if the object doesn't have a #rewind method" do + obj = mock('rewinder') + enum = Enumerator::Product.new(obj.to_enum) + + enum.rewind.should == enum + end + + it "calls a rewind method on each enumerable in direct order" do + ScratchPad.record [] + + object1 = Object.new + def object1.rewind; ScratchPad << :object1; end + + object2 = Object.new + def object2.rewind; ScratchPad << :object2; end + + object3 = Object.new + def object3.rewind; ScratchPad << :object3; end + + enum = Enumerator::Product.new(object1, object2, object3) + enum.rewind + + ScratchPad.recorded.should == [:object1, :object2, :object3] + end +end diff --git a/spec/ruby/core/enumerator/product/size_spec.rb b/spec/ruby/core/enumerator/product/size_spec.rb new file mode 100644 index 0000000000..0ba427af9a --- /dev/null +++ b/spec/ruby/core/enumerator/product/size_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#size" do + it "returns the total size of the enumerator product calculated by multiplying the sizes of enumerables in the product" do + product = Enumerator::Product.new(1..2, 1..3, 1..4) + product.size.should == 24 # 2 * 3 * 4 + end + + it "returns nil if any enumerable reports its size as nil" do + enum = Object.new + def enum.size; nil; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns Float::INFINITY if any enumerable reports its size as Float::INFINITY" do + enum = Object.new + def enum.size; Float::INFINITY; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == Float::INFINITY + end + + it "returns nil if any enumerable reports its size as Float::NAN" do + enum = Object.new + def enum.size; Float::NAN; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable doesn't respond to #size" do + enum = Object.new + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable reports a not-convertible to Integer" do + enum = Object.new + def enum.size; :symbol; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable reports a non-Integer but convertible to Integer size" do + enum = Object.new + def enum.size; 1.0; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + ruby_version_is "3.4" do + it "returns zero when any enumerable reports zero" do + enum = Enumerator::Product.new(1...1, ["A", "B"]) + enum.size.should == 0 + + enum = Enumerator::Product.new(["A", "B"], 1...1) + enum.size.should == 0 + end + end +end diff --git a/spec/ruby/core/enumerator/product_spec.rb b/spec/ruby/core/enumerator/product_spec.rb new file mode 100644 index 0000000000..0f37ef76e7 --- /dev/null +++ b/spec/ruby/core/enumerator/product_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' + +describe "Enumerator.product" do + it "returns a Cartesian product of enumerators" do + enum = Enumerator.product(1..2, ["A", "B"]) + enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] + end + + it "accepts a list of enumerators of any length" do + enum = Enumerator.product(1..2) + enum.to_a.should == [[1], [2]] + + enum = Enumerator.product(1..2, ["A"]) + enum.to_a.should == [[1, "A"], [2, "A"]] + + enum = Enumerator.product(1..2, ["A"], ["B"]) + enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]] + + enum = Enumerator.product(2..3, ["A"], ["B"], ["C"]) + enum.to_a.should == [[2, "A", "B", "C"], [3, "A", "B", "C"]] + end + + it "returns an enumerator with an empty array when no arguments passed" do + enum = Enumerator.product + enum.to_a.should == [[]] + end + + it "returns an instance of Enumerator::Product" do + enum = Enumerator.product + enum.class.should == Enumerator::Product + end + + it "accepts infinite enumerators and returns infinite enumerator" do + enum = Enumerator.product(1.., ["A", "B"]) + enum.take(5).should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"]] + enum.size.should == Float::INFINITY + end + + it "accepts a block" do + elems = [] + enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 } + + elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]] + end + + it "returns nil when a block passed" do + Enumerator.product(1..2) {}.should == nil + end + + # https://bugs.ruby-lang.org/issues/19829 + it "reject keyword arguments" do + -> { + Enumerator.product(1..3, foo: 1, bar: 2) + }.should.raise(ArgumentError, "unknown keywords: :foo, :bar") + end + + it "calls only #each_entry method on arguments" do + object = Object.new + def object.each_entry + yield 1 + yield 2 + end + + enum = Enumerator.product(object, ["A", "B"]) + enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] + end + + it "raises NoMethodError when argument doesn't respond to #each_entry" do + -> { + Enumerator.product(Object.new).to_a + }.should.raise(NoMethodError, /undefined method [`']each_entry' for/) + end + + it "calls #each_entry lazily" do + Enumerator.product(Object.new).should.is_a?(Enumerator) + end + + it "iterates through consuming enumerator elements only once" do + a = [1, 2, 3] + i = 0 + + enum = Enumerator.new do |y| + while i < a.size + y << a[i] + i += 1 + end + end + + Enumerator.product(['a', 'b'], enum).to_a.should == [["a", 1], ["a", 2], ["a", 3]] + end +end diff --git a/spec/ruby/core/enumerator/rewind_spec.rb b/spec/ruby/core/enumerator/rewind_spec.rb new file mode 100644 index 0000000000..6ba0edf174 --- /dev/null +++ b/spec/ruby/core/enumerator/rewind_spec.rb @@ -0,0 +1,70 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Enumerator#rewind" do + before :each do + @enum = 1.upto(3) + end + + it "resets the enumerator to its initial state" do + @enum.next.should == 1 + @enum.next.should == 2 + @enum.rewind + @enum.next.should == 1 + end + + it "returns self" do + @enum.rewind.should.equal? @enum + end + + it "has no effect on a new enumerator" do + @enum.rewind + @enum.next.should == 1 + end + + it "has no effect if called multiple, consecutive times" do + @enum.next.should == 1 + @enum.rewind + @enum.rewind + @enum.next.should == 1 + end + + it "works with peek to reset the position" do + @enum.next + @enum.next + @enum.rewind + @enum.next + @enum.peek.should == 2 + end + + it "calls the enclosed object's rewind method if one exists" do + obj = mock('rewinder') + enum = obj.to_enum + obj.should_receive(:each).at_most(1) + obj.should_receive(:rewind) + enum.rewind + end + + it "does nothing if the object doesn't have a #rewind method" do + obj = mock('rewinder') + enum = obj.to_enum + obj.should_receive(:each).at_most(1) + enum.rewind.should == enum + end +end + +describe "Enumerator#rewind" do + before :each do + ScratchPad.record [] + @enum = EnumeratorSpecs::Feed.new.to_enum(:each) + end + + it "clears a pending #feed value" do + @enum.next + @enum.feed :a + @enum.rewind + @enum.next + @enum.next + ScratchPad.recorded.should == [nil] + end +end diff --git a/spec/ruby/core/enumerator/shared/enum_for.rb b/spec/ruby/core/enumerator/shared/enum_for.rb new file mode 100644 index 0000000000..4388103ecf --- /dev/null +++ b/spec/ruby/core/enumerator/shared/enum_for.rb @@ -0,0 +1,57 @@ +describe :enum_for, shared: true do + it "is defined in Kernel" do + Kernel.method_defined?(@method).should == true + end + + it "returns a new enumerator" do + "abc".send(@method).should.instance_of?(Enumerator) + end + + it "defaults the first argument to :each" do + enum = [1,2].send(@method) + enum.map { |v| v }.should == [1,2].each { |v| v } + end + + it "sets regexp matches in the caller" do + "wawa".send(@method, :scan, /./).map {|o| $& }.should == ["w", "a", "w", "a"] + a = [] + "wawa".send(@method, :scan, /./).each {|o| a << $& } + a.should == ["w", "a", "w", "a"] + end + + it "exposes multi-arg yields as an array" do + o = Object.new + def o.each + yield :a + yield :b1, :b2 + yield [:c] + yield :d1, :d2 + yield :e1, :e2, :e3 + end + + enum = o.send(@method) + enum.next.should == :a + enum.next.should == [:b1, :b2] + enum.next.should == [:c] + enum.next.should == [:d1, :d2] + enum.next.should == [:e1, :e2, :e3] + end + + it "uses the passed block's value to calculate the size of the enumerator" do + Object.new.enum_for { 100 }.size.should == 100 + end + + it "defers the evaluation of the passed block until #size is called" do + ScratchPad.record [] + + enum = Object.new.enum_for do + ScratchPad << :called + 100 + end + + ScratchPad.recorded.should.empty? + + enum.size + ScratchPad.recorded.should == [:called] + end +end diff --git a/spec/ruby/core/enumerator/shared/with_index.rb b/spec/ruby/core/enumerator/shared/with_index.rb new file mode 100644 index 0000000000..0992397e95 --- /dev/null +++ b/spec/ruby/core/enumerator/shared/with_index.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' + +describe :enum_with_index, shared: true do + + require_relative '../fixtures/classes' + + before :each do + @origin = [1, 2, 3, 4] + @enum = @origin.to_enum + end + + it "passes each element and its index to block" do + a = [] + @enum.send(@method) { |o, i| a << [o, i] } + a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end + + it "returns the object being enumerated when given a block" do + @enum.send(@method) { |o, i| :glark }.should.equal?(@origin) + end + + it "binds splat arguments properly" do + acc = [] + @enum.send(@method) { |*b| c,d = b; acc << c; acc << d } + [1, 0, 2, 1, 3, 2, 4, 3].should == acc + end + + it "returns an enumerator if no block is supplied" do + ewi = @enum.send(@method) + ewi.should.instance_of?(Enumerator) + ewi.to_a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end +end diff --git a/spec/ruby/core/enumerator/shared/with_object.rb b/spec/ruby/core/enumerator/shared/with_object.rb new file mode 100644 index 0000000000..50d4f24eb3 --- /dev/null +++ b/spec/ruby/core/enumerator/shared/with_object.rb @@ -0,0 +1,42 @@ +require_relative '../../../spec_helper' + +describe :enum_with_object, shared: true do + before :each do + @enum = [:a, :b].to_enum + @memo = '' + @block_params = @enum.send(@method, @memo).to_a + end + + it "receives an argument" do + @enum.method(@method).arity.should == 1 + end + + context "with block" do + it "returns the given object" do + ret = @enum.send(@method, @memo) do |elm, memo| + # nothing + end + ret.should.equal?(@memo) + end + + context "the block parameter" do + it "passes each element to first parameter" do + @block_params[0][0].should.equal?(:a) + @block_params[1][0].should.equal?(:b) + end + + it "passes the given object to last parameter" do + @block_params[0][1].should.equal?(@memo) + @block_params[1][1].should.equal?(@memo) + end + end + end + + context "without block" do + it "returns new Enumerator" do + ret = @enum.send(@method, @memo) + ret.should.instance_of?(Enumerator) + ret.should_not.equal?(@enum) + end + end +end diff --git a/spec/ruby/core/enumerator/size_spec.rb b/spec/ruby/core/enumerator/size_spec.rb new file mode 100644 index 0000000000..4b2beffbbe --- /dev/null +++ b/spec/ruby/core/enumerator/size_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +describe "Enumerator#size" do + it "returns same value if set size is an Integer" do + Enumerator.new(100) {}.size.should == 100 + end + + it "returns nil if set size is nil" do + Enumerator.new(nil) {}.size.should == nil + end + + it "returns returning value from size.call if set size is a Proc" do + base_size = 100 + enum = Enumerator.new(-> { base_size + 1 }) {} + base_size = 200 + enum.size.should == 201 + base_size = 300 + enum.size.should == 301 + end + + it "returns the result from size.call if the size respond to call" do + obj = mock('call') + obj.should_receive(:call).and_return(42) + Enumerator.new(obj) {}.size.should == 42 + end +end diff --git a/spec/ruby/core/enumerator/to_enum_spec.rb b/spec/ruby/core/enumerator/to_enum_spec.rb new file mode 100644 index 0000000000..7fb73d0c3c --- /dev/null +++ b/spec/ruby/core/enumerator/to_enum_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/enum_for' + +describe "Enumerator#to_enum" do + it_behaves_like :enum_for, :to_enum +end diff --git a/spec/ruby/core/enumerator/with_index_spec.rb b/spec/ruby/core/enumerator/with_index_spec.rb new file mode 100644 index 0000000000..ca90fd18f7 --- /dev/null +++ b/spec/ruby/core/enumerator/with_index_spec.rb @@ -0,0 +1,89 @@ +require_relative '../../spec_helper' +require_relative 'shared/with_index' +require_relative '../enumerable/shared/enumeratorized' + +describe "Enumerator#with_index" do + it_behaves_like :enum_with_index, :with_index + it_behaves_like :enumeratorized_with_origin_size, :with_index, [1,2,3].select + + it "returns a new Enumerator when no block is given" do + enum1 = [1,2,3].select + enum2 = enum1.with_index + enum2.should.instance_of?(Enumerator) + enum1.should_not === enum2 + end + + it "accepts an optional argument when given a block" do + -> do + @enum.with_index(1) { |f| f} + end.should_not.raise(ArgumentError) + end + + it "accepts an optional argument when not given a block" do + -> do + @enum.with_index(1) + end.should_not.raise(ArgumentError) + end + + it "numbers indices from the given index when given an offset but no block" do + @enum.with_index(1).to_a.should == [[1,1], [2,2], [3,3], [4,4]] + end + + it "numbers indices from the given index when given an offset and block" do + acc = [] + @enum.with_index(1) {|e,i| acc << [e,i] } + acc.should == [[1,1], [2,2], [3,3], [4,4]] + end + + it "raises a TypeError when the argument cannot be converted to numeric" do + -> do + @enum.with_index('1') {|*i| i} + end.should.raise(TypeError) + end + + it "converts non-numeric arguments to Integer via #to_int" do + (o = mock('1')).should_receive(:to_int).and_return(1) + @enum.with_index(o).to_a.should == [[1,1], [2,2], [3,3], [4,4]] + end + + it "coerces the given numeric argument to an Integer" do + @enum.with_index(1.678).to_a.should == [[1,1], [2,2], [3,3], [4,4]] + + res = [] + @enum.with_index(1.001) { |*x| res << x} + res.should == [[1,1], [2,2], [3,3], [4,4]] + end + + it "treats nil argument as no argument" do + @enum.with_index(nil).to_a.should == [[1,0], [2,1], [3,2], [4,3]] + + res = [] + @enum.with_index(nil) { |*x| res << x} + res.should == [[1,0], [2,1], [3,2], [4,3]] + end + + it "accepts negative argument" do + @enum.with_index(-1).to_a.should == [[1,-1], [2,0], [3,1], [4,2]] + + res = [] + @enum.with_index(-1) { |*x| res << x} + res.should == [[1,-1], [2,0], [3,1], [4,2]] + end + + it "passes on the given block's return value" do + arr = [1,2,3] + arr.delete_if.with_index { |a,b| false } + arr.should == [1,2,3] + + arr.delete_if.with_index { |a,b| true } + arr.should == [] + end + + it "returns the iterator's return value" do + @enum.select.with_index { |a,b| false }.should == [] + end + + it "returns the correct value if chained with itself" do + [:a].each.with_index.with_index.to_a.should == [[[:a,0],0]] + end +end diff --git a/spec/ruby/core/enumerator/with_object_spec.rb b/spec/ruby/core/enumerator/with_object_spec.rb new file mode 100644 index 0000000000..58031fd765 --- /dev/null +++ b/spec/ruby/core/enumerator/with_object_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/with_object' + +describe "Enumerator#with_object" do + it_behaves_like :enum_with_object, :with_object +end |
