mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Rework stubbing type hierarchy
No longer need to "inject" stubs for new methods. No weird lookup of super/previous_def. Handle visibility modifier of def.
This commit is contained in:
parent
8f5f3becb4
commit
307c679609
3 changed files with 72 additions and 69 deletions
|
@ -50,24 +50,16 @@ module Spectator
|
||||||
{% if name %}@[::Spectator::StubbedName({{name}})]{% end %}
|
{% if name %}@[::Spectator::StubbedName({{name}})]{% end %}
|
||||||
class {{type_name.id}} < {{@type.name}}
|
class {{type_name.id}} < {{@type.name}}
|
||||||
{% for key, value in value_methods %}
|
{% for key, value in value_methods %}
|
||||||
inject_stub def {{key.id}}(*%args, **%kwargs)
|
default_stub def {{key.id}}(*%args, **%kwargs)
|
||||||
{{value}}
|
{{value}}
|
||||||
end
|
end
|
||||||
|
|
||||||
inject_stub def {{key.id}}(*%args, **%kwargs, &)
|
default_stub def {{key.id}}(*%args, **%kwargs, &)
|
||||||
{{key.id}}
|
{{key.id}}
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% if block %}
|
{% if block %}{{block.body}}{% end %}
|
||||||
{% for expr in block.body.is_a?(Expressions) ? block.body.expressions : [block.body] %}
|
|
||||||
{% if expr.is_a?(Call) && expr.name == :stub.id %}
|
|
||||||
inject_{{expr}}
|
|
||||||
{% else %}
|
|
||||||
{{expr}}
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,7 +140,7 @@ module Spectator
|
||||||
|
|
||||||
# "Hide" existing methods and methods from ancestors by overriding them.
|
# "Hide" existing methods and methods from ancestors by overriding them.
|
||||||
macro finished
|
macro finished
|
||||||
stub_all {{@type.name(generic_args: false)}}
|
stub_hierarchy {{@type.name(generic_args: false)}}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle all methods but only respond to configured messages.
|
# Handle all methods but only respond to configured messages.
|
||||||
|
|
|
@ -41,7 +41,7 @@ module Spectator
|
||||||
end
|
end
|
||||||
|
|
||||||
macro finished
|
macro finished
|
||||||
stub_all {{mocked_type.id}}
|
stub_hierarchy {{mocked_type.id}}
|
||||||
end
|
end
|
||||||
|
|
||||||
{% if block %}{{block.body}}{% end %}
|
{% if block %}{{block.body}}{% end %}
|
||||||
|
|
|
@ -85,21 +85,34 @@ module Spectator
|
||||||
# The block provided to `#_spectator_stub_fallback` will invoke the default response.
|
# The block provided to `#_spectator_stub_fallback` will invoke the default response.
|
||||||
# In other words, `#_spectator_stub_fallback` should yield if it's appropriate to return the default response.
|
# In other words, `#_spectator_stub_fallback` should yield if it's appropriate to return the default response.
|
||||||
private macro default_stub(method)
|
private macro default_stub(method)
|
||||||
|
{% if method.is_a?(Def)
|
||||||
|
visibility = method.visibility
|
||||||
|
elsif method.is_a?(VisibilityModifier) && method.exp.is_a?(Def)
|
||||||
|
visibility = method.visibility
|
||||||
|
method = method.exp
|
||||||
|
else
|
||||||
|
raise "`default_stub` requires a method definition"
|
||||||
|
end %}
|
||||||
{% raise "Cannot define a stub inside a method" if @def %}
|
{% raise "Cannot define a stub inside a method" if @def %}
|
||||||
{% raise "`default_stub` requires a method definition" unless method.is_a?(Def) %}
|
|
||||||
{% raise "Default stub cannot be an abstract method" if method.abstract? %}
|
{% raise "Default stub cannot be an abstract method" if method.abstract? %}
|
||||||
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if method.name.starts_with?("_spectator") || ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if method.name.starts_with?("_spectator") || ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
||||||
|
|
||||||
{{method}}
|
{{visibility.id if visibility != :public}} def {{method.receiver}}{{method.name}}(
|
||||||
|
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
||||||
|
{% 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 %}
|
||||||
|
{{method.body}}
|
||||||
|
end
|
||||||
|
|
||||||
{% original = "previous_def#{" { |*_spectator_yargs| yield *_spectator_yargs }" if method.accepts_block?}".id %}
|
{% original = "previous_def#{" { |*_spectator_yargs| yield *_spectator_yargs }".id if method.accepts_block?}".id %}
|
||||||
|
|
||||||
{% # Reconstruct the method signature.
|
{% # Reconstruct the method signature.
|
||||||
# I wish there was a better way of doing this, but there isn't (at least not that I'm aware of).
|
# I wish there was a better way of doing this, but there isn't (at least not that I'm aware of).
|
||||||
# This chunk of code must reconstruct the method signature exactly as it was originally.
|
# This chunk of code must reconstruct the method signature exactly as it was originally.
|
||||||
# If it doesn't match, it doesn't override the method and the stubbing won't work.
|
# If it doesn't match, it doesn't override the method and the stubbing won't work.
|
||||||
%}
|
%}
|
||||||
{% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}(
|
{{visibility.id if visibility != :public}} def {{method.receiver}}{{method.name}}(
|
||||||
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
||||||
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
|
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
|
||||||
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
|
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
|
||||||
|
@ -153,8 +166,15 @@ module Spectator
|
||||||
# Stubbed methods will call `#_spectator_find_stub` with the method call information.
|
# Stubbed methods will call `#_spectator_find_stub` with the method call information.
|
||||||
# If no stub is found, then `#_spectator_stub_fallback` or `#_spectator_abstract_stub_fallback` is called.
|
# If no stub is found, then `#_spectator_stub_fallback` or `#_spectator_abstract_stub_fallback` is called.
|
||||||
private macro abstract_stub(method)
|
private macro abstract_stub(method)
|
||||||
|
{% if method.is_a?(Def)
|
||||||
|
visibility = method.visibility
|
||||||
|
elsif method.is_a?(VisibilityModifier) && method.exp.is_a?(Def)
|
||||||
|
visibility = method.visibility
|
||||||
|
method = method.exp
|
||||||
|
else
|
||||||
|
raise "`abstract_stub` requires a method definition"
|
||||||
|
end %}
|
||||||
{% raise "Cannot define a stub inside a method" if @def %}
|
{% raise "Cannot define a stub inside a method" if @def %}
|
||||||
{% raise "abstract_stub requires a method definition" if !method.is_a?(Def) %}
|
|
||||||
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if method.name.starts_with?("_spectator") || ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if method.name.starts_with?("_spectator") || ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
||||||
|
|
||||||
{% # The logic in this macro follows mostly the same logic from `#default_stub`.
|
{% # The logic in this macro follows mostly the same logic from `#default_stub`.
|
||||||
|
@ -164,47 +184,24 @@ module Spectator
|
||||||
# For all intents and purposes, this macro defines logic that doesn't depend on an existing method.
|
# For all intents and purposes, this macro defines logic that doesn't depend on an existing method.
|
||||||
%}
|
%}
|
||||||
|
|
||||||
{% if method.abstract? %}
|
{% unless method.abstract? %}
|
||||||
{% original = if @type.methods.includes?(method)
|
{{visibility.id if visibility != :public}} def {{method.receiver}}{{method.name}}(
|
||||||
:previous_def
|
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
||||||
elsif @type.ancestors.any? &.methods.includes?(method)
|
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
|
||||||
:super
|
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
|
||||||
# sigh... sometimes the method won't match with a simple check.
|
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
|
||||||
# It seems to be from a difference with the body attribute.
|
{{method.body}}
|
||||||
# Manually check most attributes.
|
end
|
||||||
elsif @type.ancestors.any? do |ancestor|
|
|
||||||
ancestor.methods.any? do |meth|
|
|
||||||
meth.name == method.name &&
|
|
||||||
meth.args == method.args &&
|
|
||||||
meth.accepts_block? == method.accepts_block? &&
|
|
||||||
meth.block_arg == method.block_arg &&
|
|
||||||
meth.double_splat == method.double_splat &&
|
|
||||||
meth.free_vars == method.free_vars &&
|
|
||||||
meth.receiver == method.receiver &&
|
|
||||||
meth.return_type == method.return_type &&
|
|
||||||
meth.splat_index == method.splat_index &&
|
|
||||||
meth.visibility == method.visibility
|
|
||||||
end
|
|
||||||
end
|
|
||||||
:super
|
|
||||||
else
|
|
||||||
:previous_def # raise "Could not find original implementation of `#{method.name}` for stubbing"
|
|
||||||
end.id
|
|
||||||
if method.accepts_block?
|
|
||||||
original = "#{original} { |*_spectator_yargs| yield *_spectator_yargs }".id
|
|
||||||
end %}
|
|
||||||
{% else %}
|
|
||||||
{{method}}
|
|
||||||
|
|
||||||
{% original = "previous_def#{" { |*_spectator_yargs| yield *_spectator_yargs }" if method.accepts_block?}".id %}
|
{% original = "previous_def#{" { |*_spectator_yargs| yield *_spectator_yargs }".id if method.accepts_block?}".id %}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% # Reconstruct the method signature.
|
{% # Reconstruct the method signature.
|
||||||
# I wish there was a better way of doing this, but there isn't (at least not that I'm aware of).
|
# I wish there was a better way of doing this, but there isn't (at least not that I'm aware of).
|
||||||
# This chunk of code must reconstruct the method signature exactly as it was originally.
|
# This chunk of code must reconstruct the method signature exactly as it was originally.
|
||||||
# If it doesn't match, it doesn't override the method and the stubbing won't work.
|
# If it doesn't match, it doesn't override the method and the stubbing won't work.
|
||||||
%}
|
%}
|
||||||
{% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}(
|
{{visibility.id if visibility != :public}} def {{method.receiver}}{{method.name}}(
|
||||||
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
||||||
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
|
{% if method.double_splat %}**{{method.double_splat}}, {% end %}
|
||||||
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
|
{% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %}
|
||||||
|
@ -237,17 +234,14 @@ module Spectator
|
||||||
{% end %}
|
{% end %}
|
||||||
else
|
else
|
||||||
# A stub wasn't found, invoke the type-specific fallback logic.
|
# A stub wasn't found, invoke the type-specific fallback logic.
|
||||||
{% if method.return_type && method.abstract? %}
|
{% if method.return_type %}
|
||||||
# Pass along just the return type annotation.
|
# Pass along just the return type annotation.
|
||||||
_spectator_abstract_stub_fallback(%call, {{method.return_type}})
|
_spectator_abstract_stub_fallback(%call, {{method.return_type}})
|
||||||
{% elsif method.abstract? %}
|
{% elsif !method.abstract? %}
|
||||||
|
_spectator_abstract_stub_fallback(%call, typeof({{original}}))
|
||||||
|
{% else %}
|
||||||
# Stubbed method is abstract and there's no type annotation.
|
# Stubbed method is abstract and there's no type annotation.
|
||||||
_spectator_abstract_stub_fallback(%call)
|
_spectator_abstract_stub_fallback(%call)
|
||||||
{% else %}
|
|
||||||
# Pass along the type of the original method and a block to invoke it.
|
|
||||||
_spectator_stub_fallback(%call, typeof({{original}})) do
|
|
||||||
{{original}}
|
|
||||||
end
|
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -257,8 +251,13 @@ module Spectator
|
||||||
{% raise "Cannot define a stub inside a method" if @def %}
|
{% raise "Cannot define a stub inside a method" if @def %}
|
||||||
|
|
||||||
{% if method.is_a?(Def) %}
|
{% if method.is_a?(Def) %}
|
||||||
|
{% if method.abstract? %}abstract_stub{% else %}default_stub{% end %} {{method}}
|
||||||
|
{% elsif method.is_a?(VisibilityModifier) && method.exp.is_a?(Def) %}
|
||||||
|
{% if method.exp.abstract? %}abstract_stub{% else %}default_stub{% end %} {{method}}
|
||||||
|
{% elsif method.is_a?(Call) %}
|
||||||
|
{% raise "Stub on `Call` unsupported." %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% raise "Unrecognized syntax for `stub` - #{method}" %}
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -302,21 +301,33 @@ module Spectator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Redefines all methods on a type to conditionally respond to messages.
|
# Redefines all methods and ones inherited from its parents and mixins to support stubs.
|
||||||
# Methods will raise `UnexpectedMessage` if they're called when they shouldn't be.
|
private macro stub_hierarchy(type_name = @type)
|
||||||
# Otherwise, they'll return the configured response.
|
{% type = type_name.resolve
|
||||||
private macro stub_all(type_name, *, with style = :stub)
|
# Reverse order of ancestors (there's currently no reverse method for ArrayLiteral).
|
||||||
{% type = type_name.resolve %}
|
count = type.ancestors.size
|
||||||
{% if type.superclass %}
|
ancestors = type.ancestors.map_with_index { |_, i| type.ancestors[count - i - 1] } %}
|
||||||
stub_all({{type.superclass}}, with: {{style}})
|
{% for ancestor in ancestors %}
|
||||||
|
stub_type {{ancestor}}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
stub_type {{type_name}}
|
||||||
|
end
|
||||||
|
|
||||||
|
private macro stub_type(type_name = @type)
|
||||||
|
{% type = type_name.resolve %}
|
||||||
{% for method in type.methods.reject do |meth|
|
{% for method in type.methods.reject do |meth|
|
||||||
meth.name.starts_with?("_spectator") ||
|
meth.name.starts_with?("_spectator") ||
|
||||||
::Spectator::DSL::RESERVED_KEYWORDS.includes?(meth.name.symbolize)
|
::Spectator::DSL::RESERVED_KEYWORDS.includes?(meth.name.symbolize)
|
||||||
end %}
|
end %}
|
||||||
{{style.id}} {{method}}
|
{{(method.abstract? ? :abstract_stub : :default_stub).id}} {{method.visibility.id if method.visibility != :public}} def {{method.receiver}}{{method.name}}(
|
||||||
{% end %}
|
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %}
|
||||||
|
{% 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 %}
|
||||||
|
{{ type == @type ? :previous_def.id : :super.id }}{{ " { |*_spectator_yargs| yield *_spectator_yargs }".id if method.accepts_block? }}
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue