summaryrefslogtreecommitdiff
path: root/spec/ruby/core/range/step_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/range/step_spec.rb')
-rw-r--r--spec/ruby/core/range/step_spec.rb535
1 files changed, 373 insertions, 162 deletions
diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb
index 61ddc5205d..faab95d88d 100644
--- a/spec/ruby/core/range/step_spec.rb
+++ b/spec/ruby/core/range/step_spec.rb
@@ -7,143 +7,188 @@ describe "Range#step" do
it "returns self" do
r = 1..2
- r.step { }.should equal(r)
+ r.step { }.should.equal?(r)
end
- it "raises TypeError if step" do
- obj = mock("mock")
- -> { (1..10).step(obj) { } }.should raise_error(TypeError)
- end
+ ruby_version_is ""..."3.4" do
+ it "calls #to_int to coerce step to an Integer" do
+ obj = mock("Range#step")
+ obj.should_receive(:to_int).and_return(1)
- it "calls #to_int to coerce step to an Integer" do
- obj = mock("Range#step")
- obj.should_receive(:to_int).and_return(1)
+ (1..2).step(obj) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1, 2])
+ end
- (1..2).step(obj) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([1, 2])
- end
+ it "raises a TypeError if step does not respond to #to_int" do
+ obj = mock("Range#step non-integer")
- it "raises a TypeError if step does not respond to #to_int" do
- obj = mock("Range#step non-integer")
+ -> { (1..2).step(obj) { } }.should.raise(TypeError)
+ end
- -> { (1..2).step(obj) { } }.should raise_error(TypeError)
- end
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock("Range#step non-integer")
+ obj.should_receive(:to_int).and_return("1")
+
+ -> { (1..2).step(obj) { } }.should.raise(TypeError)
+ end
- it "raises a TypeError if #to_int does not return an Integer" do
- obj = mock("Range#step non-integer")
- obj.should_receive(:to_int).and_return("1")
+ it "raises a TypeError if the first element does not respond to #succ" do
+ obj = mock("Range#step non-comparable")
+ obj.should_receive(:<=>).with(obj).and_return(1)
- -> { (1..2).step(obj) { } }.should raise_error(TypeError)
+ -> { (obj..obj).step { |x| x } }.should.raise(TypeError)
+ end
end
- it "coerces the argument to integer by invoking to_int" do
- (obj = mock("2")).should_receive(:to_int).and_return(2)
- res = []
- (1..10).step(obj) {|x| res << x}
- res.should == [1, 3, 5, 7, 9]
- end
+ ruby_version_is "3.4" do
+ it "calls #coerce to coerce step to an Integer" do
+ obj = mock("Range#step")
+ obj.should_receive(:coerce).at_least(:once).and_return([1, 2])
- it "raises a TypeError if the first element does not respond to #succ" do
- obj = mock("Range#step non-comparable")
- obj.should_receive(:<=>).with(obj).and_return(1)
+ (1..3).step(obj) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1, 3])
+ end
+
+ it "raises a TypeError if step does not respond to #coerce" do
+ obj = mock("Range#step non-coercible")
- -> { (obj..obj).step { |x| x } }.should raise_error(TypeError)
+ -> { (1..2).step(obj) { } }.should.raise(TypeError)
+ end
end
it "raises an ArgumentError if step is 0" do
- -> { (-1..1).step(0) { |x| x } }.should raise_error(ArgumentError)
+ -> { (-1..1).step(0) { |x| x } }.should.raise(ArgumentError)
end
it "raises an ArgumentError if step is 0.0" do
- -> { (-1..1).step(0.0) { |x| x } }.should raise_error(ArgumentError)
+ -> { (-1..1).step(0.0) { |x| x } }.should.raise(ArgumentError)
end
- it "raises an ArgumentError if step is negative" do
- -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError)
+ ruby_version_is "3.4" do
+ it "does not iterate if step is 0 for bounded non-numeric ranges" do
+ t = Time.utc(2023, 2, 24)
+ (t..t + 1).step(0) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == []
+ end
+
+ it "raises an ArgumentError when iterating a beginless range" do
+ -> { (..10).step(1) { break } }.should.raise(ArgumentError,
+ "#step iteration for beginless ranges is meaningless")
+ end
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "raises an ArgumentError if step is negative" do
+ -> { (-1..1).step(-2) { |x| x } }.should.raise(ArgumentError)
+ end
end
describe "with inclusive end" do
describe "and Integer values" do
it "yields Integer values incremented by 1 and less than or equal to end when not passed a step" do
(-2..2).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+ ScratchPad.recorded.should.eql?([-2, -1, 0, 1, 2])
end
it "yields Integer values incremented by an Integer step" do
(-5..5).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5, -3, -1, 1, 3, 5])
+ ScratchPad.recorded.should.eql?([-5, -3, -1, 1, 3, 5])
end
it "yields Float values incremented by a Float step" do
(-2..2).step(1.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ ScratchPad.recorded.should.eql?([-2.0, -0.5, 1.0])
+ end
+
+ ruby_version_is "3.4" do
+ it "does not iterate if step is negative for forward range" do
+ (-1..1).step(-1) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([])
+ end
+
+ it "iterates backward if step is negative for backward range" do
+ (1..-1).step(-1) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1, 0, -1])
+ end
end
end
describe "and Float values" do
it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
(-2.0..2.0).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0, 2.0])
end
it "yields Float values incremented by an Integer step" do
(-5.0..5.0).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
end
it "yields Float values incremented by a Float step" do
(-1.0..1.0).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5, 1.0])
end
it "returns Float values of 'step * n + begin <= end'" do
(1.0..6.4).step(1.8) { |x| ScratchPad << x }
(1.0..12.7).step(1.3) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 6.4, 1.0, 2.3, 3.6,
+ ScratchPad.recorded.should.eql?([1.0, 2.8, 4.6, 6.4, 1.0, 2.3, 3.6,
4.9, 6.2, 7.5, 8.8, 10.1, 11.4, 12.7])
end
it "handles infinite values at either end" do
(-Float::INFINITY..0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+ ScratchPad.recorded.should.eql?([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
ScratchPad.record []
(0.0..Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
+ ScratchPad.recorded.should.eql?([0.0, 2.0, 4.0])
+ end
+
+ ruby_version_is "3.4" do
+ it "does not iterate if step is negative for forward range" do
+ (-1.0..1.0).step(-0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([])
+ end
+
+ it "iterates backward if step is negative for backward range" do
+ (1.0..-1.0).step(-0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1.0, 0.5, 0.0, -0.5, -1.0])
+ end
end
end
describe "and Integer, Float values" do
it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
(-2..2.0).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0, 2.0])
end
it "yields Float values incremented by an Integer step" do
(-5..5.0).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
end
it "yields Float values incremented by a Float step" do
(-1..1.0).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5, 1.0])
end
end
describe "and Float, Integer values" do
it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
(-2.0..2).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0, 2.0])
end
it "yields Float values incremented by an Integer step" do
(-5.0..5).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
end
it "yields Float values incremented by a Float step" do
(-1.0..1).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5, 1.0])
end
end
@@ -159,16 +204,99 @@ describe "Range#step" do
end
it "raises a TypeError when passed a Float step" do
- -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError)
+ -> { ("A".."G").step(2.0) { } }.should.raise(TypeError)
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "calls #succ on begin and each element returned by #succ" do
+ obj = mock("Range#step String start")
+ obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0)
+ obj.should_receive(:succ).exactly(2).times.and_return(obj)
+
+ (obj..obj).step { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [obj, obj, obj]
+ end
end
- it "calls #succ on begin and each element returned by #succ" do
- obj = mock("Range#step String start")
- obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0)
- obj.should_receive(:succ).exactly(2).times.and_return(obj)
+ ruby_version_is "3.4" do
+ it "yields String values adjusted by step and less than or equal to end" do
+ ("A".."AAA").step("A") { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "AA", "AAA"]
+ end
+
+ it "raises a TypeError when passed an incompatible type step" do
+ -> { ("A".."G").step([]) { } }.should.raise(TypeError)
+ end
+
+ it "calls #+ on begin and each element returned by #+" do
+ start = mock("Range#step String start")
+ stop = mock("Range#step String stop")
+
+ mid1 = mock("Range#step String mid1")
+ mid2 = mock("Range#step String mid2")
+
+ step = mock("Range#step String step")
+
+ # Deciding on the direction of iteration
+ start.should_receive(:<=>).with(stop).at_least(:twice).and_return(-1)
+ # Deciding whether the step moves iteration in the right direction
+ start.should_receive(:<=>).with(mid1).and_return(-1)
+ # Iteration 1
+ start.should_receive(:+).at_least(:once).with(step).and_return(mid1)
+ # Iteration 2
+ mid1.should_receive(:<=>).with(stop).and_return(-1)
+ mid1.should_receive(:+).with(step).and_return(mid2)
+ # Iteration 3
+ mid2.should_receive(:<=>).with(stop).and_return(0)
+
+ (start..stop).step(step) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [start, mid1, mid2]
+ end
+
+ it "iterates backward if the step is decreasing values, and the range is backward" do
+ start = mock("Range#step String start")
+ stop = mock("Range#step String stop")
+
+ mid1 = mock("Range#step String mid1")
+ mid2 = mock("Range#step String mid2")
+
+ step = mock("Range#step String step")
+
+ # Deciding on the direction of iteration
+ start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1)
+ # Deciding whether the step moves iteration in the right direction
+ start.should_receive(:<=>).with(mid1).and_return(1)
+ # Iteration 1
+ start.should_receive(:+).at_least(:once).with(step).and_return(mid1)
+ # Iteration 2
+ mid1.should_receive(:<=>).with(stop).and_return(1)
+ mid1.should_receive(:+).with(step).and_return(mid2)
+ # Iteration 3
+ mid2.should_receive(:<=>).with(stop).and_return(0)
+
+ (start..stop).step(step) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == [start, mid1, mid2]
+ end
+
+ it "does no iteration of the direction of the range and of the step don't match" do
+ start = mock("Range#step String start")
+ stop = mock("Range#step String stop")
+
+ mid1 = mock("Range#step String mid1")
+ mid2 = mock("Range#step String mid2")
+
+ step = mock("Range#step String step")
- (obj..obj).step { |x| ScratchPad << x }
- ScratchPad.recorded.should == [obj, obj, obj]
+ # Deciding on the direction of iteration: stop > start
+ start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1)
+ # Deciding whether the step moves iteration in the right direction
+ # start + step < start, the direction is opposite to the range's
+ start.should_receive(:+).with(step).and_return(mid1)
+ start.should_receive(:<=>).with(mid1).and_return(-1)
+
+ (start..stop).step(step) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == []
+ end
end
end
end
@@ -177,107 +305,125 @@ describe "Range#step" do
describe "and Integer values" do
it "yields Integer values incremented by 1 and less than end when not passed a step" do
(-2...2).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2, -1, 0, 1])
+ ScratchPad.recorded.should.eql?([-2, -1, 0, 1])
end
it "yields Integer values incremented by an Integer step" do
(-5...5).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+ ScratchPad.recorded.should.eql?([-5, -3, -1, 1, 3])
end
it "yields Float values incremented by a Float step" do
(-2...2).step(1.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ ScratchPad.recorded.should.eql?([-2.0, -0.5, 1.0])
end
end
describe "and Float values" do
it "yields Float values incremented by 1 and less than end when not passed a step" do
(-2.0...2.0).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0])
end
it "yields Float values incremented by an Integer step" do
(-5.0...5.0).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0])
end
it "yields Float values incremented by a Float step" do
(-1.0...1.0).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5])
end
it "returns Float values of 'step * n + begin < end'" do
(1.0...6.4).step(1.8) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([1.0, 2.8, 4.6])
+ ScratchPad.recorded.should.eql?([1.0, 2.8, 4.6])
end
- ruby_version_is '3.1' do
- it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612
- (1.0...55.6).step(18.2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([1.0, 19.2, 37.4, 55.599999999999994])
+ it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612
+ (1.0...55.6).step(18.2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1.0, 19.2, 37.4, 55.599999999999994])
- (1.0...55.6).step(18.2).size.should == 4
- end
+ (1.0...55.6).step(18.2).size.should == 4
end
it "handles infinite values at either end" do
(-Float::INFINITY...0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+ ScratchPad.recorded.should.eql?([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
ScratchPad.record []
(0.0...Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
+ ScratchPad.recorded.should.eql?([0.0, 2.0, 4.0])
+ end
+
+ ruby_version_is "3.4" do
+ it "iterates backward with exclusive end if step is negative" do
+ (1.0...-1.0).step(-0.5) { |x| ScratchPad << x }
+ ScratchPad.recorded.should.eql?([1.0, 0.5, 0.0, -0.5])
+ end
end
end
describe "and Integer, Float values" do
it "yields Float values incremented by 1 and less than end when not passed a step" do
(-2...2.0).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0])
end
it "yields Float values incremented by an Integer step" do
(-5...5.0).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0])
end
it "yields an Float and then Float values incremented by a Float step" do
(-1...1.0).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5])
end
end
describe "and Float, Integer values" do
it "yields Float values incremented by 1 and less than end when not passed a step" do
(-2.0...2).step { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0])
end
it "yields Float values incremented by an Integer step" do
(-5.0...5).step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0])
end
it "yields Float values incremented by a Float step" do
(-1.0...1).step(0.5) { |x| ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5])
end
end
describe "and String values" do
- it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
- ("A"..."E").step { |x| ScratchPad << x }
- ScratchPad.recorded.should == ["A", "B", "C", "D"]
- end
+ ruby_version_is ""..."3.4" do
+ it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
+ ("A"..."E").step { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "B", "C", "D"]
+ end
- it "yields String values incremented by #succ called Integer step times" do
- ("A"..."G").step(2) { |x| ScratchPad << x }
- ScratchPad.recorded.should == ["A", "C", "E"]
+ it "yields String values incremented by #succ called Integer step times" do
+ ("A"..."G").step(2) { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "C", "E"]
+ end
+
+ it "raises a TypeError when passed a Float step" do
+ -> { ("A"..."G").step(2.0) { } }.should.raise(TypeError)
+ end
end
- it "raises a TypeError when passed a Float step" do
- -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError)
+ ruby_version_is "3.4" do
+ it "yields String values adjusted by step and less than or equal to end" do
+ ("A"..."AAA").step("A") { |x| ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "AA"]
+ end
+
+ it "raises a TypeError when passed an incompatible type step" do
+ -> { ("A".."G").step([]) { } }.should.raise(TypeError)
+ end
end
end
end
@@ -285,139 +431,144 @@ describe "Range#step" do
describe "with an endless range" do
describe "and Integer values" do
it "yield Integer values incremented by 1 when not passed a step" do
- eval("(-2..)").step { |x| break if x > 2; ScratchPad << x }
- ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+ (-2..).step { |x| break if x > 2; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2, -1, 0, 1, 2])
ScratchPad.record []
- eval("(-2...)").step { |x| break if x > 2; ScratchPad << x }
- ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
+ (-2...).step { |x| break if x > 2; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2, -1, 0, 1, 2])
end
it "yields Integer values incremented by an Integer step" do
- eval("(-5..)").step(2) { |x| break if x > 3; ScratchPad << x }
- ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+ (-5..).step(2) { |x| break if x > 3; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-5, -3, -1, 1, 3])
ScratchPad.record []
- eval("(-5...)").step(2) { |x| break if x > 3; ScratchPad << x }
- ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
+ (-5...).step(2) { |x| break if x > 3; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-5, -3, -1, 1, 3])
end
it "yields Float values incremented by a Float step" do
- eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2.0, -0.5, 1.0])
ScratchPad.record []
- eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
+ (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2.0, -0.5, 1.0])
end
end
describe "and Float values" do
it "yields Float values incremented by 1 and less than end when not passed a step" do
- eval("(-2.0..)").step { |x| break if x > 1.5; ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ (-2.0..).step { |x| break if x > 1.5; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0])
ScratchPad.record []
- eval("(-2.0...)").step { |x| break if x > 1.5; ScratchPad << x }
- ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
+ (-2.0...).step { |x| break if x > 1.5; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-2.0, -1.0, 0.0, 1.0])
end
it "yields Float values incremented by an Integer step" do
- eval("(-5.0..)").step(2) { |x| break if x > 3.5; ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ (-5.0..).step(2) { |x| break if x > 3.5; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0])
ScratchPad.record []
- eval("(-5.0...)").step(2) { |x| break if x > 3.5; ScratchPad << x }
- ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
+ (-5.0...).step(2) { |x| break if x > 3.5; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-5.0, -3.0, -1.0, 1.0, 3.0])
end
it "yields Float values incremented by a Float step" do
- eval("(-1.0..)").step(0.5) { |x| break if x > 0.6; ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ (-1.0..).step(0.5) { |x| break if x > 0.6; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5])
ScratchPad.record []
- eval("(-1.0...)").step(0.5) { |x| break if x > 0.6; ScratchPad << x }
- ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
+ (-1.0...).step(0.5) { |x| break if x > 0.6; ScratchPad << x }
+ ScratchPad.recorded.should.eql?([-1.0, -0.5, 0.0, 0.5])
+ end
+
+ it "computes each value independently to avoid accumulating floating-point errors" do
+ result = []
+ (0.0..).step(0.1) { |x| result << x; break if result.size == 20 }
+ expected = 20.times.map { |i| i * 0.1 + 0.0 }
+ result.should.eql?(expected)
end
it "handles infinite values at the start" do
- eval("(-Float::INFINITY..)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+ (-Float::INFINITY..).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should.eql?([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
ScratchPad.record []
- eval("(-Float::INFINITY...)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
- ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
+ (-Float::INFINITY...).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
+ ScratchPad.recorded.should.eql?([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
end
end
describe "and String values" do
it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
- eval("('A'..)").step { |x| break if x > "D"; ScratchPad << x }
+ ('A'..).step { |x| break if x > "D"; ScratchPad << x }
ScratchPad.recorded.should == ["A", "B", "C", "D"]
ScratchPad.record []
- eval("('A'...)").step { |x| break if x > "D"; ScratchPad << x }
+ ('A'...).step { |x| break if x > "D"; ScratchPad << x }
ScratchPad.recorded.should == ["A", "B", "C", "D"]
end
it "yields String values incremented by #succ called Integer step times" do
- eval("('A'..)").step(2) { |x| break if x > "F"; ScratchPad << x }
+ ('A'..).step(2) { |x| break if x > "F"; ScratchPad << x }
ScratchPad.recorded.should == ["A", "C", "E"]
ScratchPad.record []
- eval("('A'...)").step(2) { |x| break if x > "F"; ScratchPad << x }
+ ('A'...).step(2) { |x| break if x > "F"; ScratchPad << x }
ScratchPad.recorded.should == ["A", "C", "E"]
end
it "raises a TypeError when passed a Float step" do
- -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError)
- -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError)
+ -> { ('A'..).step(2.0) { } }.should.raise(TypeError)
+ -> { ('A'...).step(2.0) { } }.should.raise(TypeError)
+ end
+
+ ruby_version_is "3.4" do
+ it "yields String values adjusted by step" do
+ ('A'..).step("A") { |x| break if x > "AAA"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "AA", "AAA"]
+
+ ScratchPad.record []
+ ('A'...).step("A") { |x| break if x > "AAA"; ScratchPad << x }
+ ScratchPad.recorded.should == ["A", "AA", "AAA"]
+ end
+
+ it "raises a TypeError when passed an incompatible type step" do
+ -> { ('A'..).step([]) { } }.should.raise(TypeError)
+ -> { ('A'...).step([]) { } }.should.raise(TypeError)
+ end
end
end
end
describe "when no block is given" do
- ruby_version_is "3.0" do
- it "raises an ArgumentError if step is 0" do
- -> { (-1..1).step(0) }.should raise_error(ArgumentError)
- end
+ it "raises an ArgumentError if step is 0" do
+ -> { (-1..1).step(0) }.should.raise(ArgumentError)
end
describe "returned Enumerator" do
describe "size" do
- ruby_version_is ""..."3.0" do
+ ruby_version_is ""..."3.4" do
it "raises a TypeError if step does not respond to #to_int" do
obj = mock("Range#step non-integer")
- enum = (1..2).step(obj)
- -> { enum.size }.should raise_error(TypeError)
+ -> { (1..2).step(obj) }.should.raise(TypeError)
end
it "raises a TypeError if #to_int does not return an Integer" do
obj = mock("Range#step non-integer")
obj.should_receive(:to_int).and_return("1")
- enum = (1..2).step(obj)
-
- -> { enum.size }.should raise_error(TypeError)
+ -> { (1..2).step(obj) }.should.raise(TypeError)
end
end
- ruby_version_is "3.0" do
- it "raises a TypeError if step does not respond to #to_int" do
+ ruby_version_is "3.4" do
+ it "does not raise if step is incompatible" do
obj = mock("Range#step non-integer")
- -> { (1..2).step(obj) }.should raise_error(TypeError)
- end
-
- it "raises a TypeError if #to_int does not return an Integer" do
- obj = mock("Range#step non-integer")
- obj.should_receive(:to_int).and_return("1")
- -> { (1..2).step(obj) }.should raise_error(TypeError)
- end
- end
-
- ruby_version_is ""..."3.0" do
- it "returns Float::INFINITY for zero step" do
- (-1..1).step(0).size.should == Float::INFINITY
- (-1..1).step(0.0).size.should == Float::INFINITY
+ -> { (1..2).step(obj) }.should_not.raise
end
end
@@ -458,26 +609,47 @@ describe "Range#step" do
(1.0...6.4).step(1.8).size.should == 3
end
- it "returns nil with begin and end are String" do
- ("A".."E").step(2).size.should == nil
- ("A"..."E").step(2).size.should == nil
- ("A".."E").step.size.should == nil
- ("A"..."E").step.size.should == nil
+ ruby_version_is ""..."3.4" do
+ it "returns nil with begin and end are String" do
+ ("A".."E").step(2).size.should == nil
+ ("A"..."E").step(2).size.should == nil
+ ("A".."E").step.size.should == nil
+ ("A"..."E").step.size.should == nil
+ end
+
+ it "return nil and not raises a TypeError if the first element does not respond to #succ" do
+ obj = mock("Range#step non-comparable")
+ obj.should_receive(:<=>).with(obj).and_return(1)
+ enum = (obj..obj).step
+ -> { enum.size }.should_not.raise
+ enum.size.should == nil
+ end
end
- it "return nil and not raises a TypeError if the first element does not respond to #succ" do
- obj = mock("Range#step non-comparable")
- obj.should_receive(:<=>).with(obj).and_return(1)
- enum = (obj..obj).step
- -> { enum.size }.should_not raise_error
- enum.size.should == nil
+ ruby_version_is "3.4" do
+ it "returns nil with begin and end are String" do
+ ("A".."E").step("A").size.should == nil
+ ("A"..."E").step("A").size.should == nil
+ end
+
+ it "return nil and not raises a TypeError if the first element is not of compatible type" do
+ obj = mock("Range#step non-comparable")
+ obj.should_receive(:<=>).with(obj).and_return(1)
+ enum = (obj..obj).step(obj)
+ -> { enum.size }.should_not.raise
+ enum.size.should == nil
+ end
end
end
+ # We use .take below to ensure the enumerator works
+ # because that's an Enumerable method and so it uses the Enumerator behavior
+ # not just a method overridden in Enumerator::ArithmeticSequence.
describe "type" do
context "when both begin and end are numerics" do
it "returns an instance of Enumerator::ArithmeticSequence" do
(1..10).step.class.should == Enumerator::ArithmeticSequence
+ (1..10).step(3).take(4).should == [1, 4, 7, 10]
end
end
@@ -490,22 +662,61 @@ describe "Range#step" do
context "when range is endless" do
it "returns an instance of Enumerator::ArithmeticSequence when begin is numeric" do
(1..).step.class.should == Enumerator::ArithmeticSequence
+ (1..).step(2).take(3).should == [1, 3, 5]
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "returns an instance of Enumerator when begin is not numeric" do
+ ("a"..).step.class.should == Enumerator
+ ("a"..).step(2).take(3).should == %w[a c e]
+ end
end
- it "returns an instance of Enumerator when begin is not numeric" do
- ("a"..).step.class.should == Enumerator
+ ruby_version_is "3.4" do
+ it "returns an instance of Enumerator when begin is not numeric" do
+ ("a"..).step("a").class.should == Enumerator
+ ("a"..).step("a").take(3).should == %w[a aa aaa]
+ end
end
end
context "when range is beginless and endless" do
- it "returns an instance of Enumerator" do
- Range.new(nil, nil).step.class.should == Enumerator
+ ruby_version_is ""..."3.4" do
+ it "returns an instance of Enumerator" do
+ Range.new(nil, nil).step.class.should == Enumerator
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "raises an ArgumentError" do
+ -> { Range.new(nil, nil).step(1) }.should.raise(ArgumentError,
+ "#step for non-numeric beginless ranges is meaningless")
+ end
+ end
+ end
+
+ context "when range is beginless and finite" do
+ ruby_version_is "3.4" do
+ it "raises an ArgumentError if step is non-numeric" do
+ -> { (..10).step("a") }.should.raise(ArgumentError,
+ "#step for non-numeric beginless ranges is meaningless")
+ end
end
end
context "when begin and end are not numerics" do
- it "returns an instance of Enumerator" do
- ("a".."z").step.class.should == Enumerator
+ ruby_version_is ""..."3.4" do
+ it "returns an instance of Enumerator" do
+ ("a".."z").step.class.should == Enumerator
+ ("a".."z").step(3).take(4).should == %w[a d g j]
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "returns an instance of Enumerator" do
+ ("a".."z").step("a").class.should == Enumerator
+ ("a".."z").step("a").take(4).should == %w[a aa aaa aaaa]
+ end
end
end
end