Remove monkey patching #1036

Merged
merged 16 commits into from Dec 3, 2013

Conversation

Projects
None yet
4 participants
@JonRowe
Member

JonRowe commented Aug 8, 2013

ATM I've defaulted to off in our specs, and on in the cucumbers, but we can discuss that if you disagree.

Thoughts?

/cc @alindeman @myronmarston @samphippen @soulcutter

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Aug 12, 2013

Member

Ping! Has anyone had a chance to start to review this yet?

Member

JonRowe commented Aug 12, 2013

Ping! Has anyone had a chance to start to review this yet?

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Aug 12, 2013

Member

I haven't yet...too busy with moving still. I'll try to get to it soon.

One general comment: there's also #870 where some work was started to make example group alias methods available at the top level. Does this PR fit in with that? I'd like to find a way to integrate that at some point.

Member

myronmarston commented Aug 12, 2013

I haven't yet...too busy with moving still. I'll try to get to it soon.

One general comment: there's also #870 where some work was started to make example group alias methods available at the top level. Does this PR fit in with that? I'd like to find a way to integrate that at some point.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Aug 13, 2013

Member

I need to rework this to take advantage of #870 me thinks...

Member

JonRowe commented Aug 13, 2013

I need to rework this to take advantage of #870 me thinks...

@ghost ghost assigned JonRowe Oct 3, 2013

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Oct 14, 2013

Member

Ok so I'd like to get this sorted, and then take #870 back on top of this, I don't mind assisting or taking over from @michihuber to get these two across the line.

Member

JonRowe commented Oct 14, 2013

Ok so I'd like to get this sorted, and then take #870 back on top of this, I don't mind assisting or taking over from @michihuber to get these two across the line.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Oct 14, 2013

Member

So a review would be good, then I'll sort squashing etc, at a minium a change log entry needs adding ;)

Member

JonRowe commented Oct 14, 2013

So a review would be good, then I'll sort squashing etc, at a minium a change log entry needs adding ;)

@@ -0,0 +1,44 @@
+Feature: Monkey patching
+
+ Use the monkey patching to tell RSpec to allow or disallow the top level dsl.

This comment has been minimized.

@xaviershay

xaviershay Oct 14, 2013

Member

"the monkey patching" is funny, but should just be "monkey patching" to be correct. I don't mind one way or the other :)

@xaviershay

xaviershay Oct 14, 2013

Member

"the monkey patching" is funny, but should just be "monkey patching" to be correct. I don't mind one way or the other :)

This comment has been minimized.

@JonRowe

JonRowe Oct 15, 2013

Member

I actually wonder if we should call this monkey patching at all, perhaps we should state that what this is doing more explicitly? How about:

Tell RSpec to expose the dsl via the global namespace.

@JonRowe

JonRowe Oct 15, 2013

Member

I actually wonder if we should call this monkey patching at all, perhaps we should state that what this is doing more explicitly? How about:

Tell RSpec to expose the dsl via the global namespace.

This comment has been minimized.

+ Scenario: when monkey patch is disabled top level dsl no longer works
+ Given a file named "config.rb" with:
+ """ruby
+ RSpec.configure { |c| c.enable_monkey_patching = false }

This comment has been minimized.

@xaviershay

xaviershay Oct 14, 2013

Member

Should this option just be c.monkey_patching? c.monkey_patch? enable = false is weird.

@xaviershay

xaviershay Oct 14, 2013

Member

Should this option just be c.monkey_patching? c.monkey_patch? enable = false is weird.

This comment has been minimized.

@JonRowe

JonRowe Oct 15, 2013

Member

See previous comment, perhaps we should name this entirely differently, c.expose_globally ?

@JonRowe

JonRowe Oct 15, 2013

Member

See previous comment, perhaps we should name this entirely differently, c.expose_globally ?

.rspec
@@ -1,3 +1,4 @@
--default_path spec
--order rand
--warnings
+-r spec_helper

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

Why did you add this?

@myronmarston

myronmarston Oct 15, 2013

Member

Why did you add this?

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

I understand now: you have to do this so that the config setting that disables monkey patches can be set before loading spec files. I think it creates a burden on our users that this requires a -r option like this, though, so I'd like to use a different mechanism that doesn't require this.

@myronmarston

myronmarston Oct 15, 2013

Member

I understand now: you have to do this so that the config setting that disables monkey patches can be set before loading spec files. I think it creates a burden on our users that this requires a -r option like this, though, so I'd like to use a different mechanism that doesn't require this.

lib/rspec/core/configuration.rb
@@ -871,6 +876,9 @@ def configure_expectation_framework
# @private
def load_spec_files
+ if enable_monkey_patching?
+ require 'rspec/core/monkey_patch'
+ end

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

Given how small the monkey patch file is, and that it doesn't define any classes or methods, but just calls a few methods, I think it would make more sense to put that code into a private configuration method that is called from here, rather than requiring a file.

@myronmarston

myronmarston Oct 15, 2013

Member

Given how small the monkey patch file is, and that it doesn't define any classes or methods, but just calls a few methods, I think it would make more sense to put that code into a private configuration method that is called from here, rather than requiring a file.

spec/command_line/order_spec.rb
@@ -205,6 +205,7 @@ def split_in_half(array)
def run_command(cmd)
in_current_dir do
RSpec::Core::Runner.run(cmd.split, stderr, stdout)
+ RSpec::configuration.enable_monkey_patching = false

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

Ruby allows you to use :: to call class methods, but I prefer the consistency of always doing message sends with .. Any reason you chose :: here?

@myronmarston

myronmarston Oct 15, 2013

Member

Ruby allows you to use :: to call class methods, but I prefer the consistency of always doing message sends with .. Any reason you chose :: here?

spec/rspec/core/dsl_spec.rb
@@ -13,9 +13,16 @@
methods.each do |method_name|
describe "##{method_name}" do
- it "is not added to every object in the system" do
+ it "is added to the main object and Module when monkey patching is enabled" do
+ pending "how do we sandbox this?"

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

Use in_sub_process to sandbox it. Subprocesses are great for sandboxing :).

@myronmarston

myronmarston Oct 15, 2013

Member

Use in_sub_process to sandbox it. Subprocesses are great for sandboxing :).

spec/rspec/core/ordering_spec.rb
- describe Identity do
- it "does not affect the ordering of the items" do
- expect(Identity.new.order([1, 2, 3])).to eq([1, 2, 3])
+ RSpec.describe do

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

Why did you wrap things in an anonymous describe?

@myronmarston

myronmarston Oct 15, 2013

Member

Why did you wrap things in an anonymous describe?

This comment has been minimized.

@JonRowe

JonRowe Oct 15, 2013

Member

It seemed easier than changing all three describes, I can swap it back.

@JonRowe

JonRowe Oct 15, 2013

Member

It seemed easier than changing all three describes, I can swap it back.

This comment has been minimized.

@myronmarston

myronmarston Oct 16, 2013

Member

Please do.

@myronmarston

myronmarston Oct 16, 2013

Member

Please do.

@@ -38,10 +38,15 @@ module SharedExampleGroup
group.send(shared_method_name, *args, &block)
end
- it "is exposed to the global namespace" do
+ it "is exposed to the global namespace when monkey patching is enabled" do
+ pending "how do we sandbox this"

This comment has been minimized.

@myronmarston

myronmarston Oct 15, 2013

Member

in_sub_process.

@myronmarston

myronmarston Oct 15, 2013

Member

in_sub_process.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Oct 15, 2013

Member

The difficulty is finding "when" to do it, as I don't think it's feasible to remove monkey patching after its happened, so we need to do it at the last possible minute (before spec files are loaded).

You can always undefine the monkey-patched methods as we do in rspec-mocks and rspec-expectations, but that would require bare method defs rather than module inclusion/extension. And actually, I think I prefer the way you have it.

Hmm, actually, thinking about it some more: I'm not sure that the way you have it will work for end users. You add the monkey patches before loading the spec files. Most users who will disable this will disable it in spec_helper.rb, which will not be loaded and evaluated until load_spec_files loads a file that requires it.

So I think we probably want to move to a mechanism like rspec-mocks and rspec-expectatations. See the syntax files there.

Overall this is looking great. I want to hold off on merging this until we release our 3.0.0.alpha, though.

Member

myronmarston commented Oct 15, 2013

The difficulty is finding "when" to do it, as I don't think it's feasible to remove monkey patching after its happened, so we need to do it at the last possible minute (before spec files are loaded).

You can always undefine the monkey-patched methods as we do in rspec-mocks and rspec-expectations, but that would require bare method defs rather than module inclusion/extension. And actually, I think I prefer the way you have it.

Hmm, actually, thinking about it some more: I'm not sure that the way you have it will work for end users. You add the monkey patches before loading the spec files. Most users who will disable this will disable it in spec_helper.rb, which will not be loaded and evaluated until load_spec_files loads a file that requires it.

So I think we probably want to move to a mechanism like rspec-mocks and rspec-expectatations. See the syntax files there.

Overall this is looking great. I want to hold off on merging this until we release our 3.0.0.alpha, though.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Oct 20, 2013

Member

Okay, this has been reworked to change the name to expose_globally which defaults to true, and will enable / disable the top level stuff whenever it's invoked (no need for presetting)

Member

JonRowe commented Oct 20, 2013

Okay, this has been reworked to change the name to expose_globally which defaults to true, and will enable / disable the top level stuff whenever it's invoked (no need for presetting)

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Oct 20, 2013

Member

Build failure is due to @alindeman's JRuby issue in rspec-mocks :/

Member

JonRowe commented Oct 20, 2013

Build failure is due to @alindeman's JRuby issue in rspec-mocks :/

+
+ Tell RSpec to expose the DSL via the global namespace
+
+ Scenario: by default RSpec allows the top level monkey patching

This comment has been minimized.

@xaviershay

xaviershay Oct 21, 2013

Member

should update this text to enable global.

@xaviershay

xaviershay Oct 21, 2013

Member

should update this text to enable global.

@xaviershay

This comment has been minimized.

Show comment
Hide comment
@xaviershay

xaviershay Oct 21, 2013

Member

I want to hold off on merging this until we release our 3.0.0.alpha, though.

@myronmarston why?

Member

xaviershay commented Oct 21, 2013

I want to hold off on merging this until we release our 3.0.0.alpha, though.

@myronmarston why?

+ Scenario: when exposing globally is disabled top level dsl no longer works
+ Given a file named "spec/example_spec.rb" with:
+ """ruby
+ RSpec.configure { |c| c.expose_globally = false }

This comment has been minimized.

@myronmarston

myronmarston Oct 21, 2013

Member

You know, I'm not sure that expose_globally has the right sense. It's not exposed in the global scope like a global variable is. It's exposed at the top level scope off of main.

Also, from the config option name it's not clear what is being exposed.

What do you think about expose_dsl_off_of_main instead?

@myronmarston

myronmarston Oct 21, 2013

Member

You know, I'm not sure that expose_globally has the right sense. It's not exposed in the global scope like a global variable is. It's exposed at the top level scope off of main.

Also, from the config option name it's not clear what is being exposed.

What do you think about expose_dsl_off_of_main instead?

This comment has been minimized.

@JonRowe

JonRowe Oct 21, 2013

Member

It's not just main though, it's also off Module (so you can nest describe inside modules/classes) so it is kind of globally... it's also too wordy for my liking.

@JonRowe

JonRowe Oct 21, 2013

Member

It's not just main though, it's also off Module (so you can nest describe inside modules/classes) so it is kind of globally... it's also too wordy for my liking.

This comment has been minimized.

@JonRowe

JonRowe Oct 21, 2013

Member

I could go for expose_dsl though.

@JonRowe

JonRowe Oct 21, 2013

Member

I could go for expose_dsl though.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Oct 21, 2013

Member

@xaviershay -- my primary concern is that I want a release to be consistent, without in-progress, half-done features. #870 is another aspect of this feature, I think, and it feels incomplete without that. As such, I don't want to have this merged and then block our being able to release a solid alpha. Once we ship that first alpha, it's fine to merge things like this that will have later PRs to round-out the features.

Member

myronmarston commented Oct 21, 2013

@xaviershay -- my primary concern is that I want a release to be consistent, without in-progress, half-done features. #870 is another aspect of this feature, I think, and it feels incomplete without that. As such, I don't want to have this merged and then block our being able to release a solid alpha. Once we ship that first alpha, it's fine to merge things like this that will have later PRs to round-out the features.

@michihuber

This comment has been minimized.

Show comment
Hide comment
@michihuber

michihuber Oct 21, 2013

Contributor

@JonRowe, I'll keep an eye on this PR and resubmit #870 once this is ready for master.

Contributor

michihuber commented Oct 21, 2013

@JonRowe, I'll keep an eye on this PR and resubmit #870 once this is ready for master.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 10, 2013

Member

So I rebased this and it broke horrifically. I'm at a loss as to what's changed in master that breaks this, my hunch is that it's something to do with the sandboxing going awry... See the diff or pull down the no_monkey_patch_rebased branch and run for yourself o_O

e.g. HALP

/cc @myronmarston @xaviershay @samphippen

Member

JonRowe commented Nov 10, 2013

So I rebased this and it broke horrifically. I'm at a loss as to what's changed in master that breaks this, my hunch is that it's something to do with the sandboxing going awry... See the diff or pull down the no_monkey_patch_rebased branch and run for yourself o_O

e.g. HALP

/cc @myronmarston @xaviershay @samphippen

@yujinakayama yujinakayama referenced this pull request in yujinakayama/transpec Nov 13, 2013

Closed

Support conversion to non-monkey-patch #describe #22

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 14, 2013

Member

Did anyone get a chance to look at my other branch? @myronmarston @xaviershay @samphippen

Member

JonRowe commented Nov 14, 2013

Did anyone get a chance to look at my other branch? @myronmarston @xaviershay @samphippen

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Nov 14, 2013

Member

Not yet -- I'll take a look later this week.

Member

myronmarston commented Nov 14, 2013

Not yet -- I'll take a look later this week.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Nov 15, 2013

Member

I pulled your branch now and bisected it. b9adedd is the source commit that introduces the problems.

Member

myronmarston commented Nov 15, 2013

I pulled your branch now and bisected it. b9adedd is the source commit that introduces the problems.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 15, 2013

Member

Yes, it's also the first commit commit that turns off monkey patching on this and that branch. However it hasn't changed since before I rebased it, where it works fine. There seem to be no related changes, the commit actually causing the failure lies between the point where I originally branched off and the current master.

My investigations have lead me to uncover that somehow run is being undefined from ExampleGroup subclasses or the subclasses themselves are not inheriting changes properly. My assumption is that this is somehow related to no longer mixing in our classes into the top level namespace.

Member

JonRowe commented Nov 15, 2013

Yes, it's also the first commit commit that turns off monkey patching on this and that branch. However it hasn't changed since before I rebased it, where it works fine. There seem to be no related changes, the commit actually causing the failure lies between the point where I originally branched off and the current master.

My investigations have lead me to uncover that somehow run is being undefined from ExampleGroup subclasses or the subclasses themselves are not inheriting changes properly. My assumption is that this is somehow related to no longer mixing in our classes into the top level namespace.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Nov 20, 2013

Member

@JonRowe -- would you like me to take this over or are you still planning to get to the bottom of the problem you're having?

Member

myronmarston commented Nov 20, 2013

@JonRowe -- would you like me to take this over or are you still planning to get to the bottom of the problem you're having?

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 20, 2013

Member

I'm planning to get back to this sometime this week.

Member

JonRowe commented Nov 20, 2013

I'm planning to get back to this sometime this week.

+
+ top_level.instance_eval(&to_define)
+ Module.class_exec(&to_define)
+ @exposed_globally = true

This comment has been minimized.

@myronmarston

myronmarston Nov 20, 2013

Member

One suggestion here...I think this can be simplified with an alternate approch:

  • Have a module like RSpec::Core::TopLevelDSL that is always extended onto main and included in Module.
  • When the expose_globally config changes, it can define or undefine the appropriate methods in that module, which will automatically make it available (or not) from main and from modules.

That way, you don't have to apply all of this to two different contexts.

@myronmarston

myronmarston Nov 20, 2013

Member

One suggestion here...I think this can be simplified with an alternate approch:

  • Have a module like RSpec::Core::TopLevelDSL that is always extended onto main and included in Module.
  • When the expose_globally config changes, it can define or undefine the appropriate methods in that module, which will automatically make it available (or not) from main and from modules.

That way, you don't have to apply all of this to two different contexts.

This comment has been minimized.

@JonRowe

JonRowe Nov 20, 2013

Member

The point is not to monkey patch! By doing that we'd always monkey patch and then have to control that...

@JonRowe

JonRowe Nov 20, 2013

Member

The point is not to monkey patch! By doing that we'd always monkey patch and then have to control that...

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 21, 2013

Member

Right. Rebased. Successfully. Turns out something with the refactored runner stuff and our sandboxing made this go one helluva wonky with setting a StringIO as the output in the sandboxed config, something thats not even necessary.

Member

JonRowe commented Nov 21, 2013

Right. Rebased. Successfully. Turns out something with the refactored runner stuff and our sandboxing made this go one helluva wonky with setting a StringIO as the output in the sandboxed config, something thats not even necessary.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 21, 2013

Member

Build failure is also present on master, so is unrelated. Think Travis upgraded Java/JRuby.

Member

JonRowe commented Nov 21, 2013

Build failure is also present on master, so is unrelated. Think Travis upgraded Java/JRuby.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Nov 22, 2013

Member

Ping @myronmarston I think this is ready to merge.

Member

JonRowe commented Nov 22, 2013

Ping @myronmarston I think this is ready to merge.

+ When I run `rspec`
+ Then the output should contain "1 example, 0 failures"
+
+ Scenario: when exposing globally is disabled top level dsl no longer works

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Since DSL is an acronym should this be DSL instead of dsl?

@myronmarston

myronmarston Nov 22, 2013

Member

Since DSL is an acronym should this be DSL instead of dsl?

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Done.

@JonRowe

JonRowe Nov 22, 2013

Member

Done.

lib/rspec/core/dsl.rb
@@ -1,26 +1,64 @@
module RSpec
+
+ # Generates a subclass of {ExampleGroup}

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

It's RSpec::Core::ExampleGroup, and you're in the RSpec module here (but not RSpec::Core), so does YARD link this correctly?

@myronmarston

myronmarston Nov 22, 2013

Member

It's RSpec::Core::ExampleGroup, and you're in the RSpec module here (but not RSpec::Core), so does YARD link this correctly?

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Also, to me, the generating of the subclass is an implementation detail. I would say:

Defines a named context for one or more examples.  The given block
is evaluated in the context of a generated subclass of {RSpec::Core::ExampleGroup}
@myronmarston

myronmarston Nov 22, 2013

Member

Also, to me, the generating of the subclass is an implementation detail. I would say:

Defines a named context for one or more examples.  The given block
is evaluated in the context of a generated subclass of {RSpec::Core::ExampleGroup}

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

How can I check? :S

@JonRowe

JonRowe Nov 22, 2013

Member

How can I check? :S

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Done.

@JonRowe

JonRowe Nov 22, 2013

Member

Done.

+
+ # @private
+ def self.exposed_globally?
+ @exposed_globally ||= false

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

||= has funny semantics with false. It doesn't memoize like truthy values do. Should this just be @exposed_globally?

@myronmarston

myronmarston Nov 22, 2013

Member

||= has funny semantics with false. It doesn't memoize like truthy values do. Should this just be @exposed_globally?

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

It's just preventing a warning being issued.

@JonRowe

JonRowe Nov 22, 2013

Member

It's just preventing a warning being issued.

lib/rspec/core/shared_example_group.rb
+ alias :shared_context :shared_examples
+ alias :share_examples_for :shared_examples
+ alias :shared_examples_for :shared_examples
+ puts RSpec::CallerFilter.first_non_rspec_line if defined?(shared_example_groups)

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Is this supposed to be here?

@myronmarston

myronmarston Nov 22, 2013

Member

Is this supposed to be here?

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Nope.

@JonRowe

JonRowe Nov 22, 2013

Member

Nope.

@@ -0,0 +1,38 @@
+Feature: Global namespace DSL
+
+ Tell RSpec to expose the DSL via the global namespace

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

I think this could use a fuller description. For a new comer to RSpec, this sentence wouldn't mean much. How about this:

RSpec has a few top-level constructs that allow you to begin describing behavior:

  • RSpec.describe: define a named context for a group of examples
  • RSpec.shared_examples_for: define a set of shared examples that can later be included in an example group
  • RSpec.shared_context: define some common context (using before, let`, helper methods, etc) that can later be included in an example group

Historically, these constructs have been available directly off of the main object, so that you could use these at the start of a file without the RSpec. prefix. They have also been available off of any class or module so that you can scope your examples within a particular constant namespace.

RSpec 3 now provides an option to disable this global monkey patching: config.expose_globally = false. For backwards compatibility it defaults to true.

@myronmarston

myronmarston Nov 22, 2013

Member

I think this could use a fuller description. For a new comer to RSpec, this sentence wouldn't mean much. How about this:

RSpec has a few top-level constructs that allow you to begin describing behavior:

  • RSpec.describe: define a named context for a group of examples
  • RSpec.shared_examples_for: define a set of shared examples that can later be included in an example group
  • RSpec.shared_context: define some common context (using before, let`, helper methods, etc) that can later be included in an example group

Historically, these constructs have been available directly off of the main object, so that you could use these at the start of a file without the RSpec. prefix. They have also been available off of any class or module so that you can scope your examples within a particular constant namespace.

RSpec 3 now provides an option to disable this global monkey patching: config.expose_globally = false. For backwards compatibility it defaults to true.

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Updated.

@JonRowe

JonRowe Nov 22, 2013

Member

Updated.

+ undef shared_context
+ undef share_examples_for
+ undef shared_examples_for
+ undef shared_example_groups

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

I didn't realize we had so many methods here. In particular, I didn't realize shared_example_groups is exposed at the top level. Why would it need to be? Also, do we really need share_examples_for and shared_examples_for and shared_examples?

I feel like 3.0 is a good time to consider cleaning up having so many aliases for the same thing. Honestly, I'm not sure on the history of how they all came about...

Thoughts?

@myronmarston

myronmarston Nov 22, 2013

Member

I didn't realize we had so many methods here. In particular, I didn't realize shared_example_groups is exposed at the top level. Why would it need to be? Also, do we really need share_examples_for and shared_examples_for and shared_examples?

I feel like 3.0 is a good time to consider cleaning up having so many aliases for the same thing. Honestly, I'm not sure on the history of how they all came about...

Thoughts?

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

BTW, I definitely don't want to hold up this PR for this -- mostly I was just thinking aloud. If you like, we can open an issue for this and come back to it later.

@myronmarston

myronmarston Nov 22, 2013

Member

BTW, I definitely don't want to hold up this PR for this -- mostly I was just thinking aloud. If you like, we can open an issue for this and come back to it later.

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Thats the current public api, which also all do the same thing incidentally.

@JonRowe

JonRowe Nov 22, 2013

Member

Thats the current public api, which also all do the same thing incidentally.

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

I know that's the current public API. My point is that it seems excessive to have so many methods that do the same thing.

However, shared_example_groups doesn't do the same thing, and I'm not sure why it's exposed at the top level at all.

Regardless, as you said, it's what we currently have so I'm fine punting on this for another day as long as we open an issue so we don't forget about it.

@myronmarston

myronmarston Nov 22, 2013

Member

I know that's the current public API. My point is that it seems excessive to have so many methods that do the same thing.

However, shared_example_groups doesn't do the same thing, and I'm not sure why it's exposed at the top level at all.

Regardless, as you said, it's what we currently have so I'm fine punting on this for another day as long as we open an issue so we don't forget about it.

+ end
+ alias :shared_context :shared_examples
+ alias :share_examples_for :shared_examples
+ alias :shared_examples_for :shared_examples

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Huh, I never knew you could pass symbols to alias. I've always used bare words with alias (e.g. alias shared_examples_for shared_examples), and symbols with alias_method (e.g. alias_method :shared_examples_for, :shared_examples).

Nothing needs to be changed here...I just found it interesting :).

@myronmarston

myronmarston Nov 22, 2013

Member

Huh, I never knew you could pass symbols to alias. I've always used bare words with alias (e.g. alias shared_examples_for shared_examples), and symbols with alias_method (e.g. alias_method :shared_examples_for, :shared_examples).

Nothing needs to be changed here...I just found it interesting :).

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

FYI the difference is the bare name will error if the method doesn't exist. I just got used to using symbols...

@JonRowe

JonRowe Nov 22, 2013

Member

FYI the difference is the bare name will error if the method doesn't exist. I just got used to using symbols...

spec/command_line/order_spec.rb
@@ -206,6 +206,7 @@ def split_in_half(array)
def run_command(cmd)
in_current_dir do
RSpec::Core::Runner.run(cmd.split, stderr, stdout)
+ RSpec::configuration.expose_globally = false

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

FWIW, I probably wouldn't have bothered to go through and set this everywhere in our spec suite. But now that you've taken that effort we can definitely keep it :).

@myronmarston

myronmarston Nov 22, 2013

Member

FWIW, I probably wouldn't have bothered to go through and set this everywhere in our spec suite. But now that you've taken that effort we can definitely keep it :).

+ end
+ it "is added to the RSpec DSL" do
+ expect(::RSpec).to respond_to(method_name)
+ end
it "is not added to every object in the system" do

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Not merge blocker, but I always like blank lines between examples (except when they are one-liners).

@myronmarston

myronmarston Nov 22, 2013

Member

Not merge blocker, but I always like blank lines between examples (except when they are one-liners).

spec/rspec/core/dsl_spec.rb
@@ -13,9 +16,17 @@
methods.each do |method_name|
describe "##{method_name}" do
+ it "is added to the main object and Module when expose_globally is enabled" do
+ in_sub_process do

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

It's unclear to me why this needs to be a in a sub process...can you explain?

@myronmarston

myronmarston Nov 22, 2013

Member

It's unclear to me why this needs to be a in a sub process...can you explain?

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Prevents contamination that's all

@JonRowe

JonRowe Nov 22, 2013

Member

Prevents contamination that's all

lib/rspec/core/configuration.rb
+ Core::DSL.remove_globally!
+ Core::SharedExampleGroup::TopLevelDSL.remove_globally!
+ end
+ @expose_globally = value

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Given that there can be multiple configuration instances (e.g. in our rspec-core specs), the @expose_globally state could get out of sync for a particular configuration instance, right? Is there a way that we can have it check whether or not it's globally exposed by looking at Module.method_defined?(:describe) (or something like that) rather than having to maintain an instance variable?

@myronmarston

myronmarston Nov 22, 2013

Member

Given that there can be multiple configuration instances (e.g. in our rspec-core specs), the @expose_globally state could get out of sync for a particular configuration instance, right? Is there a way that we can have it check whether or not it's globally exposed by looking at Module.method_defined?(:describe) (or something like that) rather than having to maintain an instance variable?

This comment has been minimized.

@JonRowe

JonRowe Nov 22, 2013

Member

Hmm maybe, but if we're not monkey patching it could be a legitimate case that someone else defines those methods...

@JonRowe

JonRowe Nov 22, 2013

Member

Hmm maybe, but if we're not monkey patching it could be a legitimate case that someone else defines those methods...

This comment has been minimized.

@myronmarston

myronmarston Nov 22, 2013

Member

Hmm maybe, but if we're not monkey patching it could be a legitimate case that someone else defines those methods...

I'm not too concerned with that. (Consider that we've been defining describe for years and have never received any reports about problems). You could use something less likely to be defined by someone else, though...such as shared_examples_for. Or you could get the method object for one of those and check the owner or source_location to see if it's actually RSpec that defined it.

Actually, thinking about this issue some more, I've realized that there's another oddity here that could result from the fact that Configuration#initialize calls expose_globally = true. Consider code like this:

RSpec.configure do |c|
  c.expose_globally = false
end

RSpec::Core::Configuration.new

In this case, the top level DSL methods will be exposed globally even though the user has configured it not to. Further more, RSpec.configuration.exposed_globally? will return false (which is a lie). That seems like a nasty bug to me. How is this for an alternate solution?

  • Have the methods be exposed globally at load time (e.g. when one of the files related to this is loaded) rather than in Configuration#initialize. That way, it happens once, and the instantiation of new configuration instances doesn't affect it.
  • Make the expose_globally! and remove_globally! methods idempotent. Then expose_globally= can be defined more simply as:
def expose_globally=(value)
  if value
    Core::DSL.expose_globally!
    Core::SharedExampleGroup::TopLevelDSL.expose_globally!
  else
    Core::DSL.remove_globally!
    Core::SharedExampleGroup::TopLevelDSL.remove_globally!
  end
end

Then if you want to define a expose_globally? predicate (which is more idiomatic than the expose_globally method you have now), it can simply be:

def expose_globally?
  Module.method_defined?(:describe)
end

In this case I think it's safe to not worry about if someone else has defined describe on Module; we're not basing any logic on expose_globally? but instead just providing it as a query method for end users to be able see which way it's set, so it's far less of a concern if users have done things to confuse it.

FWIW, this is how rspec-expectations and rspec-mocks manage their syntax config and it's always worked pretty well.

@myronmarston

myronmarston Nov 22, 2013

Member

Hmm maybe, but if we're not monkey patching it could be a legitimate case that someone else defines those methods...

I'm not too concerned with that. (Consider that we've been defining describe for years and have never received any reports about problems). You could use something less likely to be defined by someone else, though...such as shared_examples_for. Or you could get the method object for one of those and check the owner or source_location to see if it's actually RSpec that defined it.

Actually, thinking about this issue some more, I've realized that there's another oddity here that could result from the fact that Configuration#initialize calls expose_globally = true. Consider code like this:

RSpec.configure do |c|
  c.expose_globally = false
end

RSpec::Core::Configuration.new

In this case, the top level DSL methods will be exposed globally even though the user has configured it not to. Further more, RSpec.configuration.exposed_globally? will return false (which is a lie). That seems like a nasty bug to me. How is this for an alternate solution?

  • Have the methods be exposed globally at load time (e.g. when one of the files related to this is loaded) rather than in Configuration#initialize. That way, it happens once, and the instantiation of new configuration instances doesn't affect it.
  • Make the expose_globally! and remove_globally! methods idempotent. Then expose_globally= can be defined more simply as:
def expose_globally=(value)
  if value
    Core::DSL.expose_globally!
    Core::SharedExampleGroup::TopLevelDSL.expose_globally!
  else
    Core::DSL.remove_globally!
    Core::SharedExampleGroup::TopLevelDSL.remove_globally!
  end
end

Then if you want to define a expose_globally? predicate (which is more idiomatic than the expose_globally method you have now), it can simply be:

def expose_globally?
  Module.method_defined?(:describe)
end

In this case I think it's safe to not worry about if someone else has defined describe on Module; we're not basing any logic on expose_globally? but instead just providing it as a query method for end users to be able see which way it's set, so it's far less of a concern if users have done things to confuse it.

FWIW, this is how rspec-expectations and rspec-mocks manage their syntax config and it's always worked pretty well.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Nov 22, 2013

Member

Do you think that expose_globally is clear about what is being exposed? If a user sees config.expose_globally = true, I'm concerned it won't be clear what that means. Would config.expose_dsl_globally be more clear?

Anyhow, that's the end of my review. Nice work, @JonRowe!

Member

myronmarston commented Nov 22, 2013

Do you think that expose_globally is clear about what is being exposed? If a user sees config.expose_globally = true, I'm concerned it won't be clear what that means. Would config.expose_dsl_globally be more clear?

Anyhow, that's the end of my review. Nice work, @JonRowe!

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Dec 2, 2013

Member

FYI the methods are idempotent just Ruby issues a warning... additionally I've actually noticed the methods protect against this themselves making the configuration check a non issue. I've also moved the initial call to the only place we initialised Configuration. So by default it'll be set but for every other usage of Configuration.new it won't.

I personally read expose_globally as RSpec.expose_globally so it's clear to me, willing for other voices? @samphippen @xaviershay @soulcutter

Member

JonRowe commented Dec 2, 2013

FYI the methods are idempotent just Ruby issues a warning... additionally I've actually noticed the methods protect against this themselves making the configuration check a non issue. I've also moved the initial call to the only place we initialised Configuration. So by default it'll be set but for every other usage of Configuration.new it won't.

I personally read expose_globally as RSpec.expose_globally so it's clear to me, willing for other voices? @samphippen @xaviershay @soulcutter

spec/rspec/core/configuration_spec.rb
- config_1 = Configuration.new
- config_2 = Configuration.new
+ config_1 = Configuration.new.tap { |c| c.expose_globally = false }
+ config_2 = Configuration.new.tap { |c| c.expose_globally = false }

This comment has been minimized.

@myronmarston

myronmarston Dec 2, 2013

Member

Given that new Configuration instances no longer set expose_globally = true, I think you no longer have to set expose_globally = false here (or in any of the other specs), right?

@myronmarston

myronmarston Dec 2, 2013

Member

Given that new Configuration instances no longer set expose_globally = true, I think you no longer have to set expose_globally = false here (or in any of the other specs), right?

This comment has been minimized.

@JonRowe

JonRowe Dec 2, 2013

Member

I think you're right, shall we drop them?

@JonRowe

JonRowe Dec 2, 2013

Member

I think you're right, shall we drop them?

This comment has been minimized.

@myronmarston

myronmarston Dec 2, 2013

Member

Please do. It's noise and it suggests that it's necessary when it's not, which leads to confusion.

@myronmarston

myronmarston Dec 2, 2013

Member

Please do. It's noise and it suggests that it's necessary when it's not, which leads to confusion.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Dec 2, 2013

Member

FYI the methods are idempotent just Ruby issues a warning

Since we want rspec to be warning-free, can you fix it so ruby doesn't issue a warning?

I've also moved the initial call to the only place we initialised Configuration. So by default it'll be set but for every other usage of Configuration.new it won't.

👍

I personally read expose_globally as RSpec.expose_globally so it's clear to me, willing for other voices?

That doesn't seem any clearer to me: RSpec.expose_globally is telling RSpec to expose something globally but it doesn't say what should be exposed.

If others think it's perfectly clear I'm happy to go with what you have, though.

Member

myronmarston commented Dec 2, 2013

FYI the methods are idempotent just Ruby issues a warning

Since we want rspec to be warning-free, can you fix it so ruby doesn't issue a warning?

I've also moved the initial call to the only place we initialised Configuration. So by default it'll be set but for every other usage of Configuration.new it won't.

👍

I personally read expose_globally as RSpec.expose_globally so it's clear to me, willing for other voices?

That doesn't seem any clearer to me: RSpec.expose_globally is telling RSpec to expose something globally but it doesn't say what should be exposed.

If others think it's perfectly clear I'm happy to go with what you have, though.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Dec 2, 2013

Member

FYI the methods are idempotent just Ruby issues a warning

Since we want rspec to be warning-free, can you fix it so ruby doesn't issue a warning?

They are warning free, thats what the extra protective code is doing, I removed the var check from Configuration but it seems I'm also doing it in DSL so it's all fine.

Member

JonRowe commented Dec 2, 2013

FYI the methods are idempotent just Ruby issues a warning

Since we want rspec to be warning-free, can you fix it so ruby doesn't issue a warning?

They are warning free, thats what the extra protective code is doing, I removed the var check from Configuration but it seems I'm also doing it in DSL so it's all fine.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Dec 2, 2013

Member

They are warning free, thats what the extra protective code is doing, I removed the var check from Configuration but it seems I'm also doing it in DSL so it's all fine.

👍 I guess I misread your comment then. Thanks!

Anyhow, this is looking good to me. My concern about the expose_globally name remains but that'll really come down to what others think (@xaviershay? @alindeman? @samphippen? @soulcutter?). This also needs a changelog, then merge away.

Member

myronmarston commented Dec 2, 2013

They are warning free, thats what the extra protective code is doing, I removed the var check from Configuration but it seems I'm also doing it in DSL so it's all fine.

👍 I guess I misread your comment then. Thanks!

Anyhow, this is looking good to me. My concern about the expose_globally name remains but that'll really come down to what others think (@xaviershay? @alindeman? @samphippen? @soulcutter?). This also needs a changelog, then merge away.

@myronmarston

This comment has been minimized.

Show comment
Hide comment
@myronmarston

myronmarston Dec 2, 2013

Member

Oh yeah, one other thing: the expose_globally config option could use some YARD doc comments explaining what it does. (But we can always address that in a separate PR; don't let that hold up the merge here).

Member

myronmarston commented Dec 2, 2013

Oh yeah, one other thing: the expose_globally config option could use some YARD doc comments explaining what it does. (But we can always address that in a separate PR; don't let that hold up the merge here).

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Dec 2, 2013

Member

I added an additional line to the YARD explaining a bit more, I also changed my mind about the name of the config var.

Member

JonRowe commented Dec 2, 2013

I added an additional line to the YARD explaining a bit more, I also changed my mind about the name of the config var.

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Dec 2, 2013

Member

I've moved the config line back to the sandboxing. My reasoning is that this is the easiest way to ensure consistent state across the spec run with regards to this setting. To me it makes much more sense to protect against this one setting here rather than run anything that touches RSpec.reset or RSpec.run in a sub process... WDYT @myronmarston?

Member

JonRowe commented Dec 2, 2013

I've moved the config line back to the sandboxing. My reasoning is that this is the easiest way to ensure consistent state across the spec run with regards to this setting. To me it makes much more sense to protect against this one setting here rather than run anything that touches RSpec.reset or RSpec.run in a sub process... WDYT @myronmarston?

JonRowe added a commit that referenced this pull request Dec 3, 2013

@JonRowe JonRowe merged commit 9a1deb3 into master Dec 3, 2013

1 check passed

default The Travis CI build passed
Details

@JonRowe JonRowe deleted the no_monkey_patch branch Dec 3, 2013

@JonRowe

This comment has been minimized.

Show comment
Hide comment
@JonRowe

JonRowe Dec 3, 2013

Member

I decided if we need to refactor the specs thwn we can do that later.

Member

JonRowe commented Dec 3, 2013

I decided if we need to refactor the specs thwn we can do that later.

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