Move method redefinition for stub to Stubable

Require stub or raise UnexpectedMessage for all double methods.
Expose abstract_stub macro to require a stub.
This commit is contained in:
Michael Miller 2022-03-12 11:11:22 -07:00
parent 9b94245bd8
commit 12eeb02a8d
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
2 changed files with 34 additions and 28 deletions

View file

@ -25,7 +25,6 @@ module Spectator
{% end %}
{% if block %}{{block.body}}{% end %}
end
{% debug %}
end
# Stores responses to messages (method calls).
@ -47,37 +46,14 @@ module Spectator
# Redefines all methods on a type to conditionally respond to messages.
# Methods will raise `UnexpectedMessage` if they're called when they shouldn't be.
# Otherwise, they'll return the configured response.
# TODO: Better error for type mismatch
private macro _spectator_mask_methods(type_name)
{% type = type_name.resolve %}
{% if type.superclass %}
_spectator_mask_methods({{type.superclass}})
{% end %}
{% for meth in type.methods %}
{% if !meth.abstract? && !DSL::RESERVED_KEYWORDS.includes?(meth.name.symbolize) %}
{% if meth.visibility != :public %}{{meth.visibility.id}}{% end %} def {{meth.receiver}}{{meth.name}}(
{{meth.args.splat(",")}}
{% if meth.double_splat %}**{{meth.double_splat}}, {% end %}
{% if meth.block_arg %}&{{meth.block_arg}}{% elsif meth.accepts_block? %}&{% end %}
){% if meth.return_type %} : {{meth.return_type}}{% end %}{% if !meth.free_vars.empty? %} forall {{meth.free_vars.splat}}{% end %}
# Capture call information.
%args = Arguments.capture(
{{meth.args.map(&.internal_name).splat}}{% if !meth.args.empty? %}, {% end %}
{% if meth.double_splat %}**{{meth.double_splat}}, {% end %}
)
%call = MethodCall.new({{meth.name.symbolize}}, %args)
# Find a suitable stub.
if %stub = @stubs.find &.===(%call)
# Return configured response.
%stub.value
else
# Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with #{%args}")
end
end
{% end %}
{% for meth in type.methods.reject { |m| DSL::RESERVED_KEYWORDS.includes?(m.name.symbolize) } %}
abstract_stub {{meth}}
{% end %}
end
@ -92,7 +68,6 @@ module Spectator
arguments = ::Spectator::Arguments.capture({{call.args.splat(", ")}}{% if call.named_args %}{{call.named_args.splat}}{% end %})
call = ::Spectator::MethodCall.new({{call.name.symbolize}}, arguments)
raise ::Spectator::UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{call.name}} with #{arguments}")
{% debug %}
end
end
end

View file

@ -40,7 +40,38 @@ module Spectator
{% end %}
end
end
{% debug %}
end
private macro abstract_stub(method)
{% raise "abstract_stub requires a method definition" if !method.is_a?(Def) %}
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
{% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}(
{{method.args.splat(",")}}
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
%args = ::Spectator::Arguments.capture(
{{method.args.map(&.internal_name).splat(",")}}
{% if method.double_splat %}**{{method.double_splat}}{% end %}
)
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
if %stub = _spectator_find_stub(%call)
{% if method.return_type %}
if %cast = %stub.as?(::Spectator::ValueStub({{method.return_type}}))
%cast.value
else
%stub.value.as({{method.return_type}})
end
{% else %}
%stub.value
{% end %}
else
# Response not configured for this method/message.
raise ::Spectator::UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{method.name}} with #{%args}")
end
end
end
end
end