Skip to content
This repository has been archived by the owner on Apr 14, 2021. It is now read-only.

Return Bundler::StubSpec if stub is a Bundler::StubSpec #5630

Merged
merged 4 commits into from May 8, 2017

Conversation

jules2689
Copy link
Contributor

@jules2689 jules2689 commented May 2, 2017

Supersedes #5593
Fixes #5592

Explanation

In some cases the Gem::Specification.stubs call in this method in the rubygems integration returns a mixed bag of Bundler::StubSpecification and Gem::StubSpecification objects. We then instantiate Bundler::StubSpecification objects and set the stub to be both Gem::StubSpecification and Bundler::StubSpecification objects.

This happens after we tell rubygems to use our overrides here.

A Bundler::StubSpecification does not define to_spec where Gem::StubSpecification does. In Bundler::StubSpecification we assume the stub to be a Gem::StubSpecification rather than the to_spec-less Bundler::StubSpecification. This means that in _remote_specification, the call to to_spec here fails. This falls back to method_missing here which, of course, calls _remote_specification (and thus an infinite failing loops occurs).

Why did this happen in such a weird way?

We needed to use a combination of foreman, unicorn, and a call to Gem::Specification.find_by_name(*args) to replicate.

I suspect this was required because Bundler doesn't call these methods as much. The last call in a doubly nested bundle exec resulted in the issue being exasperated.

You can however replicate with this:

gem_stub = Gem::Specification.stubs.first
bundler_stub = Bundler::StubSpecification.from_stub(gem_stub)
bundler_stub = Bundler::StubSpecification.from_stub(bundler_stub)
bundler_stub.to_spec

We basically got to a point where we tried calling a method that doesn't exist on a Bundler::StubSpecification, so _remote_specification was called, but that had a method which didn't exist since we had the weirdness going on described here.

It was just a very specific sequence of events that is hard to replicate.

Options

  1. We implement to_spec on Bundler::StubSpecification, as is done in Add to_spec to stub_specification #5593
  2. We assume that stub is a Gem::Specification. Therefore if we try to create a Bundler::StubSpecification with the stub being a Bundler::StubSpecification, we simply return that stub we already made instead.

Thoughts

  1. This basically ends up making a linked list of Bundler::StubSpecifications where you can follow stub all the way up until it's no longer a Bundler::StubSpecification. This means that the implementation is an accidental fix as to_spec in Add to_spec to stub_specification #5593 actually just calls stub.to_spec - which, if the stub is a Bundler:StubSpecification, would call that Bundler::StubSpecification, following the list up until we find a Gem::StubSpecification.
  2. This is the right solution IMO. This breaks the weird linked list we made by mistake and just returns the object as we'd expect. Then, when stub.to_spec is called in _remote_specification, we always know it is a Gem::StubSpecification which has it defined.

cc @segiddins

@segiddins
Copy link
Member

I'd like us to add an integration test as well, since in theory other weird things can happen when Gem.loaded_specs has our custom spec proxies

if Bundler.rubygems.provides?(">= 2.1")
RSpec.describe Bundler::StubSpecification do
let(:with_gem_stub_spec) do
stub = Gem::Specification.stubs.first
Copy link
Member

Choose a reason for hiding this comment

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

this can return nil on old rubies on CI

@@ -4,6 +4,7 @@
module Bundler
class StubSpecification < RemoteSpecification
def self.from_stub(stub)
return stub if stub.is_a?(Bundler::StubSpecification)
Copy link
Member

Choose a reason for hiding this comment

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

with this, we should be able to delete the conditional at line 91

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@jules2689 jules2689 force-pushed the jules2689-bundler-stub-spec branch from f771cf5 to c049b09 Compare May 5, 2017 14:04
@jules2689
Copy link
Contributor Author

jules2689 commented May 5, 2017

@segiddins removing the conditional on line 91 caused everything to break ( see the red failures on the commits ). I'm not able to investigate the failures in time for 1.15, would you be able to look at that and take this over if we want it for 1.15? (I'm about to be internetless for 2 weeks on vacation).

Or, since @indirect is cool with the unit tests, we could continue with this as is, since it passes with the conditional

@segiddins
Copy link
Member

👍

@segiddins segiddins modified the milestones: 1.15.0.pre.3, 1.15.0.pre.4 May 8, 2017
@segiddins
Copy link
Member

@bundlerbot r+

@bundlerbot
Copy link
Collaborator

📌 Commit a542e2b has been approved by segiddins

@bundlerbot
Copy link
Collaborator

⌛ Testing commit a542e2b with merge 47e7dd0...

bundlerbot added a commit that referenced this pull request May 8, 2017
Return Bundler::StubSpec if stub is a Bundler::StubSpec

Supersedes #5593
Fixes #5592

Explanation
---
In some cases the `Gem::Specification.stubs` call in [this method](https://github.com/bundler/bundler/blob/master/lib/bundler/rubygems_integration.rb#L773-L778) in the rubygems integration returns a mixed bag of `Bundler::StubSpecification` and `Gem::StubSpecification` objects. We then instantiate `Bundler::StubSpecification` objects and set the `stub` to be both `Gem::StubSpecification` and `Bundler::StubSpecification` objects.

This happens after we tell rubygems to use our overrides [here](https://github.com/bundler/bundler/blob/master/lib/bundler/runtime.rb#L21-L24).

A `Bundler::StubSpecification` does not define `to_spec` where `Gem::StubSpecification` does. In `Bundler::StubSpecification` we assume the `stub` to be a `Gem::StubSpecification` rather than the `to_spec`-less `Bundler::StubSpecification`. This means that in `_remote_specification`, the call to `to_spec` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/stub_specification.rb#L88) fails. This falls back to `method_missing` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/remote_specification.rb#L96-L98) which, of course, calls `_remote_specification` (and thus an infinite failing loops occurs).

### Why did this happen in such a weird way?

We needed to use a combination of `foreman`, `unicorn`, and a call to `Gem::Specification.find_by_name(*args)` to replicate.

I suspect this was required because Bundler doesn't call these methods as much. The last call in a doubly nested `bundle exec` resulted in the issue being exasperated.

You can however replicate with this:

```ruby
gem_stub = Gem::Specification.stubs.first
bundler_stub = Bundler::StubSpecification.from_stub(gem_stub)
bundler_stub = Bundler::StubSpecification.from_stub(bundler_stub)
bundler_stub.to_spec
```

We basically got to a point where we tried calling a method that doesn't exist on a `Bundler::StubSpecification`, so `_remote_specification` was called, but that had a method which didn't exist since we had the weirdness going on described here.

It was just a very specific sequence of events that is hard to replicate.

Options
---
1. We implement `to_spec` on `Bundler::StubSpecification`, as is done in #5593
2. We assume that `stub` is a `Gem::Specification`. Therefore if we try to create a `Bundler::StubSpecification` with the stub being a `Bundler::StubSpecification`, we simply return that stub we already made instead.

Thoughts
---
1. This basically ends up making a linked list of `Bundler::StubSpecifications` where you can follow `stub` all the way up until it's no longer a `Bundler::StubSpecification`. This means that the implementation is an accidental fix as `to_spec` in #5593 actually just calls `stub.to_spec` - which, if the stub is a `Bundler:StubSpecification`, would call that `Bundler::StubSpecification`, following the list up until we find a `Gem::StubSpecification`.
2. This is the right solution IMO. This breaks the weird linked list we made by mistake and just returns the object as we'd expect. Then, when `stub.to_spec` is called in `_remote_specification`, we always know it is a `Gem::StubSpecification` which has it defined.

cc @segiddins
@bundlerbot
Copy link
Collaborator

☀️ Test successful - status-travis
Approved by: segiddins
Pushing 47e7dd0 to master...

@bundlerbot bundlerbot merged commit a542e2b into master May 8, 2017
@segiddins segiddins deleted the jules2689-bundler-stub-spec branch May 10, 2017 17:51
segiddins pushed a commit that referenced this pull request May 10, 2017
Return Bundler::StubSpec if stub is a Bundler::StubSpec

Supersedes #5593
Fixes #5592

Explanation
---
In some cases the `Gem::Specification.stubs` call in [this method](https://github.com/bundler/bundler/blob/master/lib/bundler/rubygems_integration.rb#L773-L778) in the rubygems integration returns a mixed bag of `Bundler::StubSpecification` and `Gem::StubSpecification` objects. We then instantiate `Bundler::StubSpecification` objects and set the `stub` to be both `Gem::StubSpecification` and `Bundler::StubSpecification` objects.

This happens after we tell rubygems to use our overrides [here](https://github.com/bundler/bundler/blob/master/lib/bundler/runtime.rb#L21-L24).

A `Bundler::StubSpecification` does not define `to_spec` where `Gem::StubSpecification` does. In `Bundler::StubSpecification` we assume the `stub` to be a `Gem::StubSpecification` rather than the `to_spec`-less `Bundler::StubSpecification`. This means that in `_remote_specification`, the call to `to_spec` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/stub_specification.rb#L88) fails. This falls back to `method_missing` [here](https://github.com/bundler/bundler/blob/master/lib/bundler/remote_specification.rb#L96-L98) which, of course, calls `_remote_specification` (and thus an infinite failing loops occurs).

### Why did this happen in such a weird way?

We needed to use a combination of `foreman`, `unicorn`, and a call to `Gem::Specification.find_by_name(*args)` to replicate.

I suspect this was required because Bundler doesn't call these methods as much. The last call in a doubly nested `bundle exec` resulted in the issue being exasperated.

You can however replicate with this:

```ruby
gem_stub = Gem::Specification.stubs.first
bundler_stub = Bundler::StubSpecification.from_stub(gem_stub)
bundler_stub = Bundler::StubSpecification.from_stub(bundler_stub)
bundler_stub.to_spec
```

We basically got to a point where we tried calling a method that doesn't exist on a `Bundler::StubSpecification`, so `_remote_specification` was called, but that had a method which didn't exist since we had the weirdness going on described here.

It was just a very specific sequence of events that is hard to replicate.

Options
---
1. We implement `to_spec` on `Bundler::StubSpecification`, as is done in #5593
2. We assume that `stub` is a `Gem::Specification`. Therefore if we try to create a `Bundler::StubSpecification` with the stub being a `Bundler::StubSpecification`, we simply return that stub we already made instead.

Thoughts
---
1. This basically ends up making a linked list of `Bundler::StubSpecifications` where you can follow `stub` all the way up until it's no longer a `Bundler::StubSpecification`. This means that the implementation is an accidental fix as `to_spec` in #5593 actually just calls `stub.to_spec` - which, if the stub is a `Bundler:StubSpecification`, would call that `Bundler::StubSpecification`, following the list up until we find a `Gem::StubSpecification`.
2. This is the right solution IMO. This breaks the weird linked list we made by mistake and just returns the object as we'd expect. Then, when `stub.to_spec` is called in `_remote_specification`, we always know it is a `Gem::StubSpecification` which has it defined.

cc @segiddins

(cherry picked from commit 47e7dd0)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

to_spec is broken on 1.15.0.pre.1 in some cases
3 participants