Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Defering to rspec-core when available without depending on it #7

Merged
merged 1 commit into from
Oct 27, 2013

Conversation

JonRowe
Copy link
Member

@JonRowe JonRowe commented Oct 25, 2013

Quoting @myronmarston :

I'm worried that we're creating a circular dependency here: rspec-core depends on this gem, and this gem now implicitly depends on rspec-core, and that could create a maintenance headache. (Consider, for example, what happens when we decide to change the deprecation formatter API: we'll have to coordinate changes in rspec-core and rspec-support).

Do you think there's a reasonable way to get this to work that doesn't involve creating that circular dependency?

For example, maybe this gem could just define the core-less version of these methods, and then core can override those definitions. Or here it could define the methods only if defined?(::RSpec::Core) is false, and then rspec-core can take care of defining its versions.

Thoughts?

My own immediate thoughts are that I'd rather have all this code in -support but I can see @myronmarston's concerns. Originally the definition of this didn't depend on a formatter, and even if we were to only define the two deprecation methods if Core wasn't loaded we're splitting the responsibility for this functionality into two places. Which I don't like, however it would still eliminate 2 places (-mocks and -expectations) which is still a benefit. So...

Thoughts?

/cc @samphippen @alindeman @soulcutter @xaviershay

@fables-tales
Copy link
Member

I'm of the opinion that we need to move anything that's going to be shared between gems into -support, all the deprecation stuff falls under that definition, as such I'd recommend putting as much of the API we can over here. Adding extensions to what this gem does in the various gems if necessary.

@xaviershay
Copy link
Member

A few options:

  1. Define a "null" RSpec.configuration.reporter in -support that is overriden in -core. Configuration is enough of a general concept that I'm not so offended by it being in -support.
  2. Check for existence of RSpec.configuration rather than defined?(RSpec::Core).
  3. Add a new RSpec::Support.configuration that RSpec.configuration can delegate to.

I'm leaning towards 1 right now. 2 is very similar to what we have now, just refines the dependency. I still don't like the conditional. 3 is probably "most correct", but maybe too much overhead.

@myronmarston
Copy link
Member

I'm of the opinion that we need to move anything that's going to be shared between gems into -support, all the deprecation stuff falls under that definition, as such I'd recommend putting as much of the API we can over here. Adding extensions to what this gem does in the various gems if necessary.

That sounds good, but it's unclear from what you said if you favor putting the versions of those methods that depend on rspec-core here.

@myronmarston
Copy link
Member

@xaviershay -- the issue isn't RSpec.configuration. The issue is rspec-core's deprecation formatter (which is available off of RSpec.configuration). So it's unclear to me how your suggests address my concerns. Consider what happens when we need to update the deprecation formatter API (e.g. change the option keys or whatever): we have to coordinate that in two gems. In contrast, if rspec-support just defined the versions of those methods for use when rspec-core is not loaded, and rspec-core overrides those definitions with its own, then all the rspec-core-dependent stuff stays in core, and it makes future maintenance easier, I think.

@fables-tales
Copy link
Member

@myronmarston I think that this gem should have no dependencies on any of the other rspec gems. So no, we shouldn't depend on RSpec core here.

@soulcutter
Copy link
Member

I also think rspec-support should not have dependencies on other rspec gems.

@JonRowe
Copy link
Member Author

JonRowe commented Oct 18, 2013

@xaviershay also note that RSpec.configuration is never available when the warnings file is loaded.

@xaviershay
Copy link
Member

I'm going to retract 2 per @JonRowe's comment, and leave 1 and 3 for consideration. I don't understand the problem enough yet to argue for or against them :)

Helicopter, out!

@JonRowe
Copy link
Member Author

JonRowe commented Oct 19, 2013

What about if we rework this so it uses a 'deprecation output' and in RSpec Core we set this to the formatter, but support has a built in dumb one...

@myronmarston
Copy link
Member

What about if we rework this so it uses a 'deprecation output' and in RSpec Core we set this to the formatter, but support has a built in dumb one...

Maybe. It sounds overly complex to me. What about my proposed solutions (e.g. having rspec-core override the definitions of these methods) do you find objectionable?

@JonRowe
Copy link
Member Author

JonRowe commented Oct 19, 2013

Duplication and splitting of concerns...

@myronmarston
Copy link
Member

What duplication? You mean the method names?

@JonRowe
Copy link
Member Author

JonRowe commented Oct 19, 2013

The fact that the knowledge of how we issue deprecations becomes split into two places. Sure it'd be two places rather than 3 as it is now but still....

@myronmarston
Copy link
Member

I don't see duplication in that at all. I see a common interface (e.g. RSpec.deprecate) and two implementations of that interface.

@JonRowe
Copy link
Member Author

JonRowe commented Oct 21, 2013

I see two definitions of the same interface, which could just as easily split as before. You're worried about circular dependance, which I don't really think it is (It's just a separate reverse dependancy to me, with 1 thing that can change) where as this creates a split dependency with both sides having to change simultaneously if it needs to change.

So I would rather work on support owning this entirely...

It's worth noting I don't really like the current deprecation formatter, I feel it hides information...

@myronmarston
Copy link
Member

You're worried about circular dependance, which I don't really think it is (It's just a separate reverse dependancy to me, with 1 thing that can change)

I'm not sure what "separate reverse dependency" means. Never heard the term. And what's the 1 thing that can change?

where as this creates a split dependency with both sides having to change simultaneously if it needs to change.

Well, it depends entirely on what changes. Let me lay out how I see this.

With your solution (keeping the rspec-core version of RSpec.deprecate in this gem), both implementations of the RSpec.deprecate interface live here, so any changes to that interface can be made here, in one place. However, any changes to rspec-core deprecation formatter interface will need coordination between PRs in core (where the formatter lives) and here (where the formatter is used), in two places.

With my proposed solution (keeping the rspec-core version of RSpec.deprecate in rspec-core), the implementations of RSpec.deprecate will live in two places, so if we decide to change that interface, we'll need to coordinate that in core and here. However, if we want to make any changes to the rspec-core deprecation formatter, we won't have to make any changes here, because there's nothing here that depends on it.

I believe there is much more value in minimizing the coordination effort of changing the deprecation formatter than minimizing the coordination effort of changing the RSpec.deprecate interface, for a couple reasons:

  • The RSpec.deprecate interface is already something that we can't change without coordinating among multiple gems, because -mocks and -expectations (and -rails, perhaps) all depend on it.
  • The fact that it's already more widely used and depended upon than the deprecation formatter means it's less likely to change. It's been proven useful in more situations and would require more effort to change.
  • The rspec-core deprecation formatter seems more likely to change in contrast. It's newer (and thus less "proven" as an interface). It's called from far fewer places, so there are fewer call sites to update when updating the interface.
  • To summarize: RSpec.deprecate already requires coordination effort to update that interface. The deprecation formatter does not. Let's not make them both require coordination effort to update.

It's worth noting I don't really like the current deprecation formatter, I feel it hides information...

I'm not sure what you mean by "it hides information", but we can definitely make it better. It'll be much harder to make it better once rspec-support depends on it, though.

@soulcutter
Copy link
Member

FWIW the current deprecation formatter was a feature request to reduce the sheer quantity of deprecation messages that can spew out when upgrading rspec on older projects. Presumably you don't have to see the same message repeated a thousand times to get the point. You -can- get a full deprecation report if you point the output to a file. Anyway, I think it eases the transition to newer versions of rspec, which is a good thing. I was initially against the idea myself, thinking the full output being painful was a feature, but I was eventually convinced (and implemented this version). The discussion was in rspec/rspec-mocks/issues/386

If there's an alternate version that would address the original issue, I'm open to refining this.

@myronmarston
Copy link
Member

I'm definitely a fan of what we wound up with, too.

@JonRowe
Copy link
Member Author

JonRowe commented Oct 23, 2013

My concern with it is that when you have a high level of differing deprecations you end up obscuring the output of the tests, and when you have a high level of identical deprecations (as is the case with say, rspec-mocks and should) you don't get an indication of the scale of the problem.

The discussion about this happened whilst both I and @samphippen were offline and I know he's voiced similar concerns to myself.

But that's off topic.

I'm going to think about this some more.

@soulcutter
Copy link
Member

As far as the scale of the problem, it does report 1066 deprecation warnings total at the bottom.

As for the "differing deprecations", I do think this could be improved, but at the very least it now obscures the test output less because it's not interspersed within the test output.

In one of my legacy applications the difference is 1066 lines interspersed the old way, and the more-recent deprecation formatter prints 52 lines at the end of a test run (with mixed types of deprecations - some identical and some differing). The latter is far more readable for the deprecation warnings and for the test results.

@myronmarston
Copy link
Member

The latter is far more readable for the deprecation warnings and for the test results.

I agree completely.

Only define deprecation methods if they don't exist
@JonRowe
Copy link
Member Author

JonRowe commented Oct 25, 2013

Ok I'm deferring to everyone else here and just not defining the methods if they already exist. I'll update rspec/rspec-core#1115 accordingly

myronmarston added a commit that referenced this pull request Oct 27, 2013
Defering to `rspec-core` when available without depending on it
@myronmarston myronmarston merged commit 33f5948 into master Oct 27, 2013
@myronmarston myronmarston deleted the refactor_core_detection branch October 27, 2013 03:13
@myronmarston
Copy link
Member

Thanks @JonRowe. There's another reason this change is important that @alindeman discovered: when running the rspec-rails cukes, he was getting lots of failures like:


undefined method `configuration' for RSpec:Module (NoMethodError)
/Users/alindeman/workspace/rspec-dev/repos/rspec-support/lib/rspec/support/warnings.rb:11:in `deprecate'
/Users/alindeman/workspace/rspec-dev/repos/rspec-expectations/lib/rspec/expectations/syntax.rb:52:in `warn_about_should_unless_configured'
/Users/alindeman/workspace/rspec-dev/repos/rspec-expectations/lib/rspec/expectations/syntax.rb:69:in `should'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/aruba-0.4.11/lib/aruba/api.rb:176:in `assert_partial_output'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/aruba-0.4.11/lib/aruba/cucumber.rb:83:in `block in <top (required)>'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:48:in `instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:48:in `block in cucumber_instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:69:in `cucumber_run_with_backtrace_filtering'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:36:in `cucumber_instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/rb_support/rb_step_definition.rb:97:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/step_match.rb:25:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:60:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/rb_support/rb_world.rb:52:in `step'
/Users/alindeman/workspace/rspec-dev/repos/rspec-rails/features/step_definitions/additional_cli_steps.rb:2:in `block in <top (required)>'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:48:in `instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:48:in `block in cucumber_instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:69:in `cucumber_run_with_backtrace_filtering'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/core_ext/instance_exec.rb:36:in `cucumber_instance_exec'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/rb_support/rb_step_definition.rb:97:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/step_match.rb:25:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/step_invocation.rb:60:in `invoke'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/step_invocation.rb:38:in `accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:106:in `block in visit_step'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:105:in `visit_step'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/step_collection.rb:19:in `block in accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/step_collection.rb:18:in `each'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/step_collection.rb:18:in `accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:100:in `block in visit_steps'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:99:in `visit_steps'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:15:in `block in execute'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:82:in `block (2 levels) in with_hooks'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:98:in `before_and_after'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:81:in `block in with_hooks'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:120:in `call'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:120:in `block (3 levels) in around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/language_support/language_methods.rb:9:in `block in around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/language_support/language_methods.rb:97:in `call'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/language_support/language_methods.rb:97:in `execute_around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/language_support/language_methods.rb:8:in `around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:119:in `block (2 levels) in around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:123:in `call'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime/support_code.rb:123:in `around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:93:in `around'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:80:in `with_hooks'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:13:in `execute'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/scenario.rb:32:in `block in accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/scenario.rb:79:in `with_visitor'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/scenario.rb:31:in `accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:58:in `block in visit_feature_element'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:57:in `visit_feature_element'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/feature.rb:38:in `block in accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/feature.rb:37:in `each'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/feature.rb:37:in `accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:27:in `block in visit_feature'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:26:in `visit_feature'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/features.rb:28:in `block in accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/features.rb:17:in `each'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/features.rb:17:in `each'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/features.rb:27:in `accept'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:21:in `block in visit_features'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/ast/tree_walker.rb:20:in `visit_features'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/runtime.rb:48:in `run!'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/lib/cucumber/cli/main.rb:47:in `execute!'
/opt/boxen/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/cucumber-1.3.8/bin/cucumber:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.0.0/bin/cucumber:23:in `load'
/opt/boxen/rbenv/versions/2.0.0/bin/cucumber:23:in `<main>'
features/view_specs/stub_template.feature:30:in `Then the examples should all pass'

Cucumber doesn't load rspec-core but rspec-support was still defining the methods that depend on it! I think the reason this was happening is because our use of bundle exec to run the cukes means bundler loads all the gemspecs of the gems in the bundle, and when loading the rspec-core gemspec, it loads the version file which then defines the RSpec::Core constant even though rspec-core hasn't actually been loaded.

Andy found that applying this PR fixed his issue.

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.

5 participants