Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add specs for Range#reverse_each #1169

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions core/range/reverse_each_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require_relative '../../spec_helper'

ruby_version_is "3.3" do
describe "Range#reverse_each" do
it "works efficiently for very long Ranges of Integers" do
(1..2**100).reverse_each.take(3).size.should == 3
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this test fails in case #reverse_each works inefficiently.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is very much skirting the (MRI) implementation.

A Range is pretty much just a combination of a begin value, an end value, and an operation to get to the next value. In MRI 3.2 and below, there is no way to get to the previous value, so this code creates an array of 2**100 elements, then takes the last three elements of this array. So in MRI 3.2, this will result in either a NoMemoryError or an extremely long evaluation/timeout.
MRI 3.3 added a specialization for this method when using Integer values, this code now boils down to something like [2**100, (2**100).pred, (2**100).pred.pred] and finishes "instantly"
This specialization currently only exists for Integer values, that's why there I've added a spec for the a-z-Range as well, to ensure other types still work.


it "works for beginless Ranges of Integers" do
(..5).reverse_each.take(3).should == [5, 4, 3]
end

it "works for Ranges of Strings by converting the Range to an Array first" do
("a".."z").reverse_each.take(3).should == ["z", "y", "x"]
end

it "raises a TypeError for endless Ranges of Integers" do
-> {
(1..).reverse_each.take(3)
}.should raise_error(TypeError, "can't iterate from NilClass")
end

it "raises a TypeError for endless Ranges of other objects" do
-> {
("a"..).reverse_each.take(3)
}.should raise_error(TypeError, "can't iterate from NilClass")
end
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to check the following files and have here the similar cases/structure:

  • core/range/each_spec.rb
  • core/enumerable/reverse_each_spec.rb
  • core/array/reverse_each_spec.rb

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these tests are pretty specific for ranges. There is a spec in core/range/each_spec that validates the behaviour for a beginless range:

it "raises a TypeError beginless ranges" do
  -> { (..2).each { |x| x } }.should raise_error(TypeError)
end

This specific case is not relevant for arrays, you can simply reverse iterate over it, there is no need there to construct a temporary array and there is no chance for it to become infinite.

As for Enumerable#reverse_each: that depends on the object that's being enumerated. If the input is an infinite generator, it will probably result in memory exhaustion