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

Conversation

herwinw
Copy link
Member

@herwinw herwinw commented Jun 27, 2024

No description provided.

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.

(1..2**100).reverse_each.take(3).size.should == 3
end

it "works for infinite Ranges of Integers" do
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
it "works for infinite Ranges of Integers" do
it "works for beginless Ranges of Integers" do

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed that one

}.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

@herwinw
Copy link
Member Author

herwinw commented Nov 16, 2024

This should add two more checkmarks in #1216

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants