Skip to content

Commit

Permalink
Merge pull request #852 from rspec/fix_sandboxing
Browse files Browse the repository at this point in the history
Fix sandboxing of rspec-core's own specs.
  • Loading branch information
myronmarston committed Mar 31, 2013
2 parents faf02d9 + e40d30d commit f4cca6f
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 29 deletions.
1 change: 1 addition & 0 deletions lib/rspec/core/pending.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def pending(*args)
result = begin
yield
example.example_group_instance.instance_eval { verify_mocks_for_rspec }
true
end
example.metadata[:pending] = false
rescue Exception => e
Expand Down
58 changes: 29 additions & 29 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,37 @@ def method_missing(method, *args, &block)
end
end

def sandboxed(&block)
@orig_config = RSpec.configuration
@orig_world = RSpec.world
new_config = RSpec::Core::Configuration.new
new_world = RSpec::Core::World.new(new_config)
RSpec.instance_variable_set(:@configuration, new_config)
RSpec.instance_variable_set(:@world, new_world)
object = Object.new
object.extend(RSpec::Core::SharedExampleGroup)

(class << RSpec::Core::ExampleGroup; self; end).class_eval do
alias_method :orig_run, :run
def run(reporter=nil)
@orig_mock_space = RSpec::Mocks::space
RSpec::Mocks::space = RSpec::Mocks::Space.new
orig_run(reporter || NullObject.new)
ensure
RSpec::Mocks::space = @orig_mock_space
module Sandboxing
def self.sandboxed(&block)
@orig_config = RSpec.configuration
@orig_world = RSpec.world
new_config = RSpec::Core::Configuration.new
new_world = RSpec::Core::World.new(new_config)
RSpec.instance_variable_set(:@configuration, new_config)
RSpec.instance_variable_set(:@world, new_world)
object = Object.new
object.extend(RSpec::Core::SharedExampleGroup)

(class << RSpec::Core::ExampleGroup; self; end).class_eval do
alias_method :orig_run, :run
def run(reporter=nil)
orig_run(reporter || NullObject.new)
end
end
end

object.instance_eval(&block)
ensure
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
remove_method :run
alias_method :run, :orig_run
remove_method :orig_run
end
RSpec::Core::SandboxedMockSpace.sandboxed do
object.instance_eval(&block)
end
ensure
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
remove_method :run
alias_method :run, :orig_run
remove_method :orig_run
end

RSpec.instance_variable_set(:@configuration, @orig_config)
RSpec.instance_variable_set(:@world, @orig_world)
RSpec.instance_variable_set(:@configuration, @orig_config)
RSpec.instance_variable_set(:@world, @orig_world)
end
end

def in_editor?
Expand Down Expand Up @@ -99,7 +99,7 @@ def without_env_vars(*vars)
RSpec.configure do |c|
# structural
c.alias_it_behaves_like_to 'it_has_behavior'
c.around {|example| sandboxed { example.run }}
c.around {|example| Sandboxing.sandboxed { example.run }}
c.include(RSpecHelpers)
c.include Aruba::Api, :example_group => {
:file_path => /spec\/command_line/
Expand Down
100 changes: 100 additions & 0 deletions spec/support/sandboxed_mock_space.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require 'rspec/mocks'

module RSpec
module Core
# Because rspec-core dog-foods itself, rspec-core's spec suite has
# examples that define example groups and examples and run them. The
# usual lifetime of an RSpec::Mocks::Proxy is for one example
# (the proxy cache gets cleared between each example), but since the
# specs in rspec-core's suite sometimes create test doubles and pass
# them to examples a spec defines and runs, the test double's proxy
# must live beyond the inner example: it must live for the scope
# of wherever it got defined. Here we implement the necessary semantics
# for rspec-core's specs:
#
# - #verify_all and #reset_all affect only mocks that were created
# within the current scope.
# - Mock proxies live for the duration of the scope in which they are
# created.
#
# Thus, mock proxies created in an inner example live for only that
# example, but mock proxies created in an outer example can be used
# in an inner example but will only be reset/verified when the outer
# example completes.
class SandboxedMockSpace < ::RSpec::Mocks::Space
def self.sandboxed
orig_space = RSpec::Mocks.space
RSpec::Mocks.space = RSpec::Core::SandboxedMockSpace.new

RSpec::Core::Example.class_eval do
alias_method :orig_run, :run
def run(*args)
RSpec::Mocks.space.sandboxed do
orig_run(*args)
end
end
end

yield
ensure
RSpec::Core::Example.class_eval do
remove_method :run
alias_method :run, :orig_run
remove_method :orig_run
end

RSpec::Mocks.space = orig_space
end

class Sandbox
attr_reader :proxies

def initialize
@proxies = Set.new
end

def verify_all
@proxies.each { |p| p.verify }
end

def reset_all
@proxies.each { |p| p.reset }
end
end

def initialize
@sandbox_stack = []
super
end

def sandboxed
@sandbox_stack << Sandbox.new
yield
ensure
@sandbox_stack.pop
end

def verify_all
return super unless sandbox = @sandbox_stack.last
sandbox.verify_all
end

def reset_all
return super unless sandbox = @sandbox_stack.last
sandbox.reset_all
end

def proxy_for(object)
new_proxy = !proxies.has_key?(object.__id__)
proxy = super

if new_proxy && sandbox = @sandbox_stack.last
sandbox.proxies << proxy
end

proxy
end
end
end
end

0 comments on commit f4cca6f

Please sign in to comment.