Skip to content

Rescue TypeError if a top-level class named Behavior exists #1656

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

Closed
wants to merge 1 commit into from

Conversation

betesh
Copy link
Contributor

@betesh betesh commented Jul 5, 2016

If your application defines a top-level class named Behavior, loading
rspec/rails/example/request_example_group.rb with Rails 3.2.22.1
results in a compiler warning:

warning: toplevel constant Behavior referenced by ActionDispatch::IntegrationTest::Behavior

This warning is the result of the situation descibed in rails/rails#6931,
i.e. Rails auto-load magic concludes that the constant named Behavior is already
defined, and tries to use it instead of attempting to auto-load a properly namespaced one.

The result is a TypeError, which can safely be swallowed.

If your application defines a top-level class named Behavior, loading
rspec/rails/example/request_example_group.rb with Rails 3.2.22.1
results in a compiler warning:

`warning: toplevel constant Behavior referenced by ActionDispatch::IntegrationTest::Behavior`

This warning is the result of the situation descibed in rails/rails#6931,
i.e. Rails auto-load magic concludes that the constant named Behavior is already
defined, and tries to use it instead of attempting to auto-load a properly namespaced one.

The result is a TypeError, which can safely be swallowed.
@JonRowe
Copy link
Member

JonRowe commented Jul 5, 2016

Er no, this can't be safely swallowed, as the wrong module is now included in ActionDispatch::IntegrationTest::Behavior, they need to fix this by properly referencing the constant to fix the autoload.

@JonRowe JonRowe closed this Jul 5, 2016
@JonRowe
Copy link
Member

JonRowe commented Jul 5, 2016

Please open an issue with the rails team about this.

@betesh
Copy link
Contributor Author

betesh commented Jul 6, 2016

  1. If ::Behavior is a class, include Behavior will raise a TypeError, which can be swallowed safely. Merging this would have resolved that case and unblocked my team and others who have a class Behavior. If ::Behavior is a module, then, as @JonRowe pointed out, "the wrong module is now included", but that behavior is unchanged in this PR. Thus, the TypeError can be swallowed safely.

  2. Rails team can't fix this. As the issue I linked explains, they've escalated it to the Ruby core team, who is considering fixing it for Ruby 2.4.0.

  3. The problem can be fixed in rspec-rails by not using an exception for flow control:

    if ActionPack::VERSION::MAJOR >= 5
    include ActionDispatch::IntegrationTest::Behavior
    end

If I address it this way, will you accept the PR?

@JonRowe
Copy link
Member

JonRowe commented Jul 6, 2016

  1. If ::Behavior is a class, include Behavior will raise a TypeError, which can be swallowed safely. Merging this would have resolved that case and unblocked my team and others who have a class Behavior. If ::Behavior is a module, then, as @JonRowe pointed out, "the wrong module is now included", but that behavior is unchanged in this PR. Thus, the TypeError can be swallowed safely.

No it can't because the module behaviour is now not included, Ruby cannot have both a class and a module with the same name, if an error occurs expected functionality has not occured.

  1. Rails team can't fix this. As the issue I linked explains, they've escalated it to the Ruby core team, who is considering fixing it for Ruby 2.4.0.

They can they can load there module properly by fully qualifying it or swallowing the exception themselves if they think it's safe, this is not a general solution but it will fix this particular case.

  1. The problem can be fixed in rspec-rails by not using an exception for flow control:

if ActionPack::VERSION::MAJOR >= 5 include ActionDispatch::IntegrationTest::Behavior end

This won't fix it.

If I address it this way, will you accept the PR?

No, this should be addressed upstream, we are referencing a full qualified constant and it is producing a error based on its internals, those internals should be fixed.

@betesh
Copy link
Contributor Author

betesh commented Jul 7, 2016

No it can't because the module behaviour is now not included, Ruby cannot have both a class and a module with the same name, if an error occurs expected functionality has not occured.

What module behavior? In Rails 3 and 4, there is no such module.

In Rail 5, ActionDispatch::IntegrationTest is explicitly auto-loaded here, so require 'action_dispatch' should be enough to make sure the right module is included, and that's already happening as soon as you require 'rails'. The rails issue I mentioned above only occurs when the class you are trying to auto-load can be found in an autoload_path, not when it's explicitly auto-loaded.

The problem can be fixed in rspec-rails by not using an exception for flow control:

Shouldn't this be a good enough reason to fix it here even upstream changes would make it behave differently?

if ActionPack::VERSION::MAJOR >= 5

This won't fix it.

I must really be missing something here. If rspec-rails can safely swallow a NameError, that means that in Rails 3 and 4, it doesn't need this module. So why won't this fix it?

I made a mistake by linking to the Rails issue. I thought that would help by making it unnecessary to repeat what was already stated there, but it seems that instead it just gave the false impression that this is not a flaw in rspec-rails.

@myronmarston
Copy link
Member

Using exceptions for flow control is generally a bad idea, so I'd definitely be in favor of doing a version check instead of attempting inclusion with a rescue fallback.

@JonRowe
Copy link
Member

JonRowe commented Jul 7, 2016

@betesh if you want to convert the check to an if go ahead, but it won't fix any errors caused by Behaviour being misloaded.

@myronmarston
Copy link
Member

@betesh if you want to convert the check to an if go ahead, but it won't fix any errors caused by Behaviour being misloaded.

But it'll make rspec-rails avoid trying to load ActionDispatch::IntegrationTest::Behavior on rails versions that do not define that module. And, from what I understand about ruby constant resolution, on applications that define a Behavior class, it'll work properly on rails versions that do provide that module. Ruby constant resolution provides top-level constants when the fully qualified nested constant does not exist -- so if Behavior is returned from a ActionDispatch::IntegrationTest::Behavior reference only when ActionDispatch::IntegrationTest::Behavior does not exist.

@betesh
Copy link
Contributor Author

betesh commented Jul 7, 2016

Done. See #1660

Would it be possible to release this as v3.5.1, compatible with rspec v3.5.0?

myronmarston added a commit that referenced this pull request Jul 7, 2016
Don't swallow exceptions for flow control.  fixes #1656
JonRowe pushed a commit that referenced this pull request Jul 7, 2016
Don't swallow exceptions for flow control.  fixes #1656
sebjacobs pushed a commit to futurelearn/rspec-rails that referenced this pull request Mar 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants