/usr/lib/ruby/vendor_ruby/rspec/mocks/method_double.rb is in ruby-rspec-mocks 3.4.0c3e0m1s1-1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | module RSpec
module Mocks
# @private
class MethodDouble
# @private
attr_reader :method_name, :object, :expectations, :stubs, :method_stasher
# @private
def initialize(object, method_name, proxy)
@method_name = method_name
@object = object
@proxy = proxy
@original_visibility = nil
@method_stasher = InstanceMethodStasher.new(object, method_name)
@method_is_proxied = false
@expectations = []
@stubs = []
end
def original_implementation_callable
# If original method is not present, uses the `method_missing`
# handler of the object. This accounts for cases where the user has not
# correctly defined `respond_to?`, and also 1.8 which does not provide
# method handles for missing methods even if `respond_to?` is correct.
@original_implementation_callable ||= original_method ||
Proc.new do |*args, &block|
@object.__send__(:method_missing, @method_name, *args, &block)
end
end
alias_method :save_original_implementation_callable!, :original_implementation_callable
def original_method
@original_method ||=
@method_stasher.original_method ||
@proxy.original_method_handle_for(method_name)
end
# @private
def visibility
@proxy.visibility_for(@method_name)
end
# @private
def object_singleton_class
class << @object; self; end
end
# @private
def configure_method
@original_visibility = visibility
@method_stasher.stash unless @method_is_proxied
define_proxy_method
end
# @private
def define_proxy_method
return if @method_is_proxied
save_original_implementation_callable!
definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
define_method(method_name) do |*args, &block|
method_double.proxy_method_invoked(self, *args, &block)
end
__send__(visibility, method_name)
end
@method_is_proxied = true
end
# The implementation of the proxied method. Subclasses may override this
# method to perform additional operations.
#
# @private
def proxy_method_invoked(_obj, *args, &block)
@proxy.message_received method_name, *args, &block
end
# @private
def restore_original_method
return show_frozen_warning if object_singleton_class.frozen?
return unless @method_is_proxied
remove_method_from_definition_target
@method_stasher.restore if @method_stasher.method_is_stashed?
restore_original_visibility
@method_is_proxied = false
end
# @private
def show_frozen_warning
RSpec.warn_with(
"WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
"method on #{@object.inspect} because it has been frozen. If you reuse this " \
"object, `#{@method_name}` will continue to respond with its stub implementation.",
:call_site => nil,
:use_spec_location_as_call_site => true
)
end
# @private
def restore_original_visibility
return unless @original_visibility &&
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
object_singleton_class.__send__(@original_visibility, method_name)
end
# @private
def verify
expectations.each { |e| e.verify_messages_received }
end
# @private
def reset
restore_original_method
clear
end
# @private
def clear
expectations.clear
stubs.clear
end
# The type of message expectation to create has been extracted to its own
# method so that subclasses can override it.
#
# @private
def message_expectation_class
MessageExpectation
end
# @private
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
configure_method
expectation = message_expectation_class.new(error_generator, expectation_ordering,
expected_from, self, :expectation, opts, &implementation)
expectations << expectation
expectation
end
# @private
def build_expectation(error_generator, expectation_ordering)
expected_from = IGNORED_BACKTRACE_LINE
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
end
# @private
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
configure_method
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
self, :stub, opts, &implementation)
stubs.unshift stub
stub
end
# A simple stub can only return a concrete value for a message, and
# cannot match on arguments. It is used as an optimization over
# `add_stub` / `add_expectation` where it is known in advance that this
# is all that will be required of a stub, such as when passing attributes
# to the `double` example method. They do not stash or restore existing method
# definitions.
#
# @private
def add_simple_stub(method_name, response)
setup_simple_method_double method_name, response, stubs
end
# @private
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
end
# @private
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
define_proxy_method
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
collection.unshift me
me
end
# @private
def add_default_stub(*args, &implementation)
return if stubs.any?
add_stub(*args, &implementation)
end
# @private
def remove_stub
raise_method_not_stubbed_error if stubs.empty?
remove_stub_if_present
end
# @private
def remove_stub_if_present
expectations.empty? ? reset : stubs.clear
end
# @private
def raise_method_not_stubbed_error
RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
end
# In Ruby 2.0.0 and above prepend will alter the method lookup chain.
# We use an object's singleton class to define method doubles upon,
# however if the object has had it's singleton class (as opposed to
# it's actual class) prepended too then the the method lookup chain
# will look in the prepended module first, **before** the singleton
# class.
#
# This code works around that by providing a mock definition target
# that is either the singleton class, or if necessary, a prepended module
# of our own.
#
if Support::RubyFeatures.module_prepends_supported?
private
# We subclass `Module` in order to be able to easily detect our prepended module.
RSpecPrependedModule = Class.new(Module)
def definition_target
@definition_target ||= usable_rspec_prepended_module || object_singleton_class
end
def usable_rspec_prepended_module
@proxy.prepended_modules_of_singleton_class.each do |mod|
# If we have one of our modules prepended before one of the user's
# modules that defines the method, use that, since our module's
# definition will take precedence.
return mod if RSpecPrependedModule === mod
# If we hit a user module with the method defined first,
# we must create a new prepend module, even if one exists later,
# because ours will only take precedence if it comes first.
return new_rspec_prepended_module if mod.method_defined?(method_name)
end
nil
end
def new_rspec_prepended_module
RSpecPrependedModule.new.tap do |mod|
object_singleton_class.__send__ :prepend, mod
end
end
else
private
def definition_target
object_singleton_class
end
end
private
def remove_method_from_definition_target
definition_target.__send__(:remove_method, @method_name)
rescue NameError
# This can happen when the method has been monkeyed with by
# something outside RSpec. This happens, for example, when
# `file.write` has been stubbed, and then `file.reopen(other_io)`
# is later called, as `File#reopen` appears to redefine `write`.
#
# Note: we could avoid rescuing this by checking
# `definition_target.instance_method(@method_name).owner == definition_target`,
# saving us from the cost of the expensive exception, but this error is
# extremely rare (it was discovered on 2014-12-30, only happens on
# RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
# so we'd rather avoid the cost of that check for every method double,
# and risk the rare situation where this exception will get raised.
RSpec.warn_with(
"WARNING: RSpec could not fully restore #{@object.inspect}." \
"#{@method_name}, possibly because the method has been redefined " \
"by something outside of RSpec."
)
end
end
end
end
|