Skip to content

Lifecycle/space fixups #519

Merged
merged 22 commits into from Jan 9, 2014

3 participants

@myronmarston
RSpec member

This is the start of addressing a bunch of issues.

  • Fixes #165 -- stop resetting mock proxies twice at the end of each example
  • Fixes #352 -- make test doubles self-destruct after they are torn down.
  • Fixes #240 -- adds with_temporary_scope.

Ready for review.

myronmarston added some commits Jan 3, 2014
@myronmarston myronmarston Remove test we no longer need.
The logic that required this test was removed in cd81094.
acd9a45
@myronmarston myronmarston Stop double-resetting mock proxies.
We don't need to reset the mock proxy while verifying it.
That'll happen later during the teardown phase.

It looks like the main reason we were doing this was
to assist with rspec-mocks testing itself; we would
verify the middle of an example to assert a mock expectation
failure, and a `reset` is needed so it doesn't fail again
when rspec-core calls `verify` as well.

The solution is to use spec helper methods that perform
the reset when we verify in the middle of an example.

I don't have any evidence this improves perf, but it
seems reasonable to assume that removing an extra method
call per example can only make it faster (however slight
the improvement may be).

Fixes #165.
c439349
@myronmarston myronmarston Make test doubles self-destruct after the example completes.
Test doubles are not designed to be used outside of
the example they were created in.
f83810b
@myronmarston myronmarston Update spec to only specify the behavior we are keeping.
We're going to change the behavior of test doubles
that have been reset (so that they "expire" and can
no longer be used). This spec was originally added in
847c66d where the
point was to address an error while resetting a test
double (which, in turn caused that method double
to stick around into later examples and continue to
fail).
6489742
@myronmarston myronmarston Split spec into two.
The second part of this spec was added in
9357e4d. We are
changing test doubles so that they self-destruct
after being reset. Currently, `verify` also
resets, so verifying a test double twice
will no longer be allowed.
358fbdb
@myronmarston myronmarston Update teardown spec.
We're going to change how pure test doubles behave
after teardown, so update this to focus on partial
doubles (which will not be changed).
0df4884
@myronmarston myronmarston No need for ProxyForNil to have its own file. 3138bba
@myronmarston myronmarston Centralize error classes in error_generator.rb.
Also, document some of them as public since
users may want to rescue them, or specify
a block will raise one of these errors
(e.g. in an rspec-mocks extension gem).
b639f7e
@myronmarston myronmarston Move error_space.rb into space.rb
There's no need for a separate file and there's
going to be a bit of interaction between multiple
spaces at various layers in a stack so having it
in one file will make it easier to see how it relates.
83e56b0
@myronmarston myronmarston Remove an unnecessary layer of indirection.
* This should be faster (but almost certainly not
  noticably so).
* It was odd to expose these methods off of `RSpec::Mocks`
  given that they are not intended for usage outside
  rspec-mocks.
* We weren't getting any benefit from these except
  for not having to type `.space`.
e685867
@samphippen
RSpec member

@myronmarston I took a look through this. Looks good :)

myronmarston added some commits Jan 3, 2014
@myronmarston myronmarston Refactor: store const mutators in mock space.
This unifies global storage of modified things in one place.
9da93fd
@myronmarston myronmarston Re-work spec file a bit.
- Make it more general (it's not just about stubbing).
- Remove unused lines.
- Improve doc output (`include_examples` does not
  create a nested group).
7c94a6f
@myronmarston myronmarston Don't allow const stubbing/hiding from before(:all). d89e680
@myronmarston myronmarston Having a private `pretty_print` method causes problems.
When rspec-expectations tries to diff the object in
a failure message, it blows up when the `pp` library
tries to print it.
513bdb1
@myronmarston myronmarston Refactor space tracking so it uses a stack and allows nesting. c28b379
@myronmarston myronmarston Add RSpec::Mocks.with_temporary_scope.
Closes #240.
3dcef6d
@myronmarston myronmarston Clean up expectation ordering.
- It was duplicated in Proxy.
- Makes more sense to initialize it in Space init.
98f065e
@myronmarston myronmarston Simplify spec.
rspec-mocks lacks the necessary sandboxing to safely
define and run examples from within examples.
rspec-core has and uses this but it's not exposed
for use here.

Instead, we can just trigger the teardown/setup
that happens between examples.

This fixes some rspec-mocks space leakage
that was happening.
d2b335e
@myronmarston myronmarston Move mock_space_spec specs into space_spec.rb
Not sure how we wound up with two separate files, anyway.
90cf550
@myronmarston myronmarston Remove unnecessary collection clearing.
Since we now use a new space instance per example,
we don't need to clear its collections when resetting.
Before this was necessary because we kept a space instance
that we would keep using for the lifetime of the process.

Constant mutators weren't being reset idempotently, so I
had to tweak them a bit.
fab5210
@myronmarston myronmarston Add changelog entry for test-double self-destruction.
[ci skip]
3ab7925
@JonRowe JonRowe commented on the diff Jan 7, 2014
lib/rspec/mocks.rb
@@ -37,7 +25,8 @@ def self.verify
# each example, even if an error was raised during the example.
def self.teardown
space.reset_all
- self.space = ERROR_SPACE
+ @space_stack.pop
@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

You could combine these two lines to @space = @space_stack.pop || @root_space no?

@myronmarston
RSpec member
myronmarston added a note Jan 7, 2014

Nope. @space should be set to @space_stack.last after popping.

@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

Ahh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@JonRowe JonRowe commented on the diff Jan 7, 2014
lib/rspec/mocks.rb
# Performs per-test/example setup. This should be called before
# an test or example begins.
def self.setup
- self.space = MOCK_SPACE
+ @space_stack << (@space = space.new_scope)
@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

Why don't we just access @space_stack.last everywhere?

@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

Or define space to be that?

@myronmarston
RSpec member
myronmarston added a note Jan 7, 2014

I initially planned on doing that, but then was noticing that we access RSpec::Mocks.space all over the place from the rest of the rspec-mocks code (e.g. to call proxy_for on it), and while Array#last shouldn't be a slow method call, my instinct is that it can only be faster to have space already available for direct access. The cost of doing this is only the extra memory need for the @space ivar, but who cares about that?

Anyhow, putting it in the ivar also allows us to use attr_reader, which is apparently faster than any normal method definition could be:

rails/arel@ea1d050

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@JonRowe JonRowe commented on the diff Jan 7, 2014
lib/rspec/mocks/error_generator.rb
@@ -63,6 +81,14 @@ def raise_arity_error(calculator, actual)
]
end
+ def raise_expired_test_double_error
+ raise ExpiredTestDoubleError,
+ "#{intro} was originally created in one example but has leaked into " +
+ "another example and can no longer be used. rspec-mocks' doubles are " +
+ "designed to only last for one example, and you need to create a new " +
+ "one in each example you wish to use it for."
+ end
@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

Would this be raised for a double created in a temporary scope then reused? Could that be confusing?

@myronmarston
RSpec member
myronmarston added a note Jan 7, 2014

Yep. I tried to rephrase this to work for the temporary scope case but I couldn't come up with a phrasing I liked. On top of that, I'd consider using with_temporary_scope to be an "advanced" feature of rspec-mocks. I think a newcomer (who has no idea what "an rspec-mocks scope" is) is more likely to be confused by a message worded in those terms than someone using with_temporary_scope being confused by this message.

Anyhow, I'm open to alternate phrasings if you have an idea. I was also thinking it could be good to mention with_temporary_scope in the messages raised from RootSpace so people know that's available but I couldn't come up with a good way to include that in the message. Any ideas?

@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

How about passing a variable to this method that defaults to example but when raised from with_temporary_scope sets it to context, then it'd be slightly less confusing in that scenario?

"#{intro} was originally created in one example but has leaked into " +
"another context and can no longer be used. rspec-mocks' doubles are " +
"designed to only last for one context, and you need to create a new " +
"one in each context you wish to use it for."
@myronmarston
RSpec member
myronmarston added a note Jan 9, 2014

I've tried to apply your suggestion, but applying it gets really complicated for all the different possibilities:

  • Created in a temporary scope, used in a later example.
  • Created in a temporary scope, used in a later temporary scope.
  • Created in an example, used in a later example.
  • Created in an example, used in a temporary scope after the example.

Doing this properly will require a bunch of extra state, for what is an edge case. I'm not convinced it's worth it...but maybe you want to take a stab at it after this is merged?

@JonRowe
RSpec member
JonRowe added a note Jan 9, 2014

maybe ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@JonRowe JonRowe commented on an outdated diff Jan 7, 2014
lib/rspec/mocks/proxy.rb
+ def add_message_expectation(location, method_name, opts={}, &block)
+ warn(method_name) if warn_about_expectations?
+ super
+ end
+
+ def add_negative_message_expectation(location, method_name, &implementation)
+ warn(method_name) if warn_about_expectations?
+ super
+ end
+
+ def add_stub(location, method_name, opts={}, &implementation)
+ warn(method_name) if warn_about_expectations?
+ super
+ end
+
+ private
@JonRowe
RSpec member
JonRowe added a note Jan 7, 2014

This should be dedented two spaces no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@JonRowe
RSpec member
JonRowe commented Jan 7, 2014

Looking good so far but I left a few notes :)

@myronmarston myronmarston merged commit 8615dc2 into master Jan 9, 2014

1 check passed

Details default The Travis CI build passed
@myronmarston myronmarston deleted the lifecycle-space-fixups branch Jan 9, 2014
This was referenced Jan 20, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.