diff --git a/Changelog.md b/Changelog.md index 74b73e86c..a24d4dc83 100644 --- a/Changelog.md +++ b/Changelog.md @@ -18,6 +18,9 @@ Bug Fixes: `and_yield`, `and_raise`, `and_return` or `and_throw`. This got fixed in 2.13.1 but failed to get merged into master for the 2.14.0.rc1 release (Myron Marston). +* `Marshal.dump` does not unnecessarily duplicate objects when rspec-mocks has + not been fully initialized. This could cause errors when using `spork` or + similar preloading gems (Andy Lindeman). ### 2.14.0.rc1 / 2013-05-27 [full changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.14.0.rc1) diff --git a/lib/rspec/mocks/extensions/marshal.rb b/lib/rspec/mocks/extensions/marshal.rb index 7698c1887..b8cf7b5df 100644 --- a/lib/rspec/mocks/extensions/marshal.rb +++ b/lib/rspec/mocks/extensions/marshal.rb @@ -1,13 +1,13 @@ module Marshal class << self - def dump_with_mocks(*args) - object = args.shift - - if ( ::RSpec::Mocks.space && !::RSpec::Mocks.space.registered?(object) ) || NilClass === object - return dump_without_mocks(*args.unshift(object)) + # Duplicates any mock objects before serialization. Otherwise, + # serialization will fail because methods exist on the singleton class. + def dump_with_mocks(object, *rest) + if ::RSpec::Mocks.space.nil? || !::RSpec::Mocks.space.registered?(object) || NilClass === object + dump_without_mocks(object, *rest) + else + dump_without_mocks(object.dup, *rest) end - - dump_without_mocks(*args.unshift(object.dup)) end alias_method :dump_without_mocks, :dump diff --git a/spec/rspec/mocks/extensions/marshal_spec.rb b/spec/rspec/mocks/extensions/marshal_spec.rb new file mode 100644 index 000000000..46f436b34 --- /dev/null +++ b/spec/rspec/mocks/extensions/marshal_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Marshal, 'extensions' do + # An object that raises when code attempts to dup it. + # + # Because we manipulate the internals of RSpec::Mocks.space below, we need + # an object that simply blows up when #dup is called without using any + # partial mocking or stubbing from rspec-mocks itself. + class UndupableObject + def dup + raise NotImplementedError + end + end + + describe '#dump' do + context 'when rspec-mocks has not been fully initialized' do + def without_space + stashed_space, RSpec::Mocks.space = RSpec::Mocks.space, nil + yield + ensure + RSpec::Mocks.space = stashed_space + end + + it 'does not duplicate the object before serialization' do + obj = UndupableObject.new + without_space do + serialized = Marshal.dump(obj) + expect(Marshal.load(serialized)).to be_an(UndupableObject) + end + end + end + + context 'when rspec-mocks has been fully initialized' do + it 'duplicates objects with stubbed or mocked implementations before serialization' do + obj = double(:foo => "bar") + + serialized = Marshal.dump(obj) + expect(Marshal.load(serialized)).to be_an(obj.class) + end + + it 'does not duplicate other objects before serialization' do + obj = UndupableObject.new + + serialized = Marshal.dump(obj) + expect(Marshal.load(serialized)).to be_an(UndupableObject) + end + + it 'does not duplicate nil before serialization' do + serialized = Marshal.dump(nil) + expect(Marshal.load(serialized)).to be_nil + end + end + end +end