From d6112bd2ddcfcd03b58eee45d7214e4f5e6c6d28 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 26 Jan 2022 12:41:41 -0700 Subject: [PATCH] Rip out mocks --- src/spectator/dsl/mocks.cr | 183 ------------------ src/spectator/mocks.cr | 14 +- src/spectator/mocks/allow.cr | 18 -- src/spectator/mocks/allow_any_instance.cr | 15 -- src/spectator/mocks/anonymous_double.cr | 26 --- src/spectator/mocks/anonymous_null_double.cr | 17 -- src/spectator/mocks/arguments.cr | 4 - src/spectator/mocks/double.cr | 130 ------------- src/spectator/mocks/exception_method_stub.cr | 14 -- src/spectator/mocks/expect_any_instance.cr | 23 --- src/spectator/mocks/generic_arguments.cr | 41 ---- src/spectator/mocks/generic_method_stub.cr | 26 --- src/spectator/mocks/method_call.cr | 13 -- src/spectator/mocks/method_stub.cr | 34 ---- .../mocks/multi_value_method_stub.cr | 19 -- src/spectator/mocks/nil_method_stub.cr | 52 ----- src/spectator/mocks/no_arguments.cr | 21 -- src/spectator/mocks/original_method_stub.cr | 10 - src/spectator/mocks/proc_method_stub.cr | 18 -- src/spectator/mocks/reflection.cr | 26 --- src/spectator/mocks/registry.cr | 110 ----------- src/spectator/mocks/stubs.cr | 127 ------------ src/spectator/mocks/type_registry.cr | 25 --- .../mocks/unexpected_message_error.cr | 4 - src/spectator/mocks/value_method_stub.cr | 14 -- src/spectator/mocks/verifying_double.cr | 106 ---------- 26 files changed, 4 insertions(+), 1086 deletions(-) delete mode 100644 src/spectator/dsl/mocks.cr delete mode 100644 src/spectator/mocks/allow.cr delete mode 100644 src/spectator/mocks/allow_any_instance.cr delete mode 100644 src/spectator/mocks/anonymous_double.cr delete mode 100644 src/spectator/mocks/anonymous_null_double.cr delete mode 100644 src/spectator/mocks/arguments.cr delete mode 100644 src/spectator/mocks/double.cr delete mode 100644 src/spectator/mocks/exception_method_stub.cr delete mode 100644 src/spectator/mocks/expect_any_instance.cr delete mode 100644 src/spectator/mocks/generic_arguments.cr delete mode 100644 src/spectator/mocks/generic_method_stub.cr delete mode 100644 src/spectator/mocks/method_call.cr delete mode 100644 src/spectator/mocks/method_stub.cr delete mode 100644 src/spectator/mocks/multi_value_method_stub.cr delete mode 100644 src/spectator/mocks/nil_method_stub.cr delete mode 100644 src/spectator/mocks/no_arguments.cr delete mode 100644 src/spectator/mocks/original_method_stub.cr delete mode 100644 src/spectator/mocks/proc_method_stub.cr delete mode 100644 src/spectator/mocks/reflection.cr delete mode 100644 src/spectator/mocks/registry.cr delete mode 100644 src/spectator/mocks/stubs.cr delete mode 100644 src/spectator/mocks/type_registry.cr delete mode 100644 src/spectator/mocks/unexpected_message_error.cr delete mode 100644 src/spectator/mocks/value_method_stub.cr delete mode 100644 src/spectator/mocks/verifying_double.cr diff --git a/src/spectator/dsl/mocks.cr b/src/spectator/dsl/mocks.cr deleted file mode 100644 index 7e75738..0000000 --- a/src/spectator/dsl/mocks.cr +++ /dev/null @@ -1,183 +0,0 @@ -require "../mocks" - -module Spectator::DSL - module Mocks - macro double(name = "Anonymous", **stubs, &block) - {% if name.is_a?(StringLiteral) || name.is_a?(StringInterpolation) %} - anonymous_double({{name}}, {{stubs.double_splat}}) - {% else %} - {% - safe_name = name.id.symbolize.gsub(/\W/, "_").id - type_name = "Double#{safe_name}".id - %} - - {% if block %} - define_double({{type_name}}, {{name}}, {{stubs.double_splat}}) {{block}} - {% else %} - create_double({{type_name}}, {{name}}, {{stubs.double_splat}}) - {% end %} - {% end %} - end - - macro create_double(type_name, name, **stubs) - {% if type_name.resolve? %} - {{type_name}}.new.tap do |%double| - {% for name, value in stubs %} - allow(%double).to receive({{name.id}}).and_return({{value}}) - {% end %} - end - {% elsif @def %} - anonymous_double({{name ? name.stringify : "Anonymous"}}, {{stubs.double_splat}}) - {% else %} - {% raise "Block required for double definition" %} - {% end %} - end - - macro define_double(type_name, name, **stubs, &block) - {% begin %} - {% if (name.is_a?(Path) || name.is_a?(Generic)) && (resolved = name.resolve?) %} - verify_double({{name}}) - class {{type_name}} < ::Spectator::Mocks::VerifyingDouble(::{{resolved.id}}) - {% else %} - class {{type_name}} < ::Spectator::Mocks::Double - def initialize(null = false) - super({{name.id.stringify}}, null) - end - {% end %} - - def as_null_object - {{type_name}}.new(true) - end - - # TODO: Do something with **stubs? - - {{block.body}} - end - {% end %} - end - - def anonymous_double(name = "Anonymous", **stubs) - ::Spectator::Mocks::AnonymousDouble.new(name, stubs) - end - - macro null_double(name, **stubs, &block) - {% if name.is_a?(StringLiteral) || name.is_a?(StringInterpolation) %} - anonymous_null_double({{name}}, {{stubs.double_splat}}) - {% else %} - {% - safe_name = name.id.symbolize.gsub(/\W/, "_").id - type_name = "Double#{safe_name}".id - %} - - {% if block.is_a?(Nop) %} - create_null_double({{type_name}}, {{name}}, {{stubs.double_splat}}) - {% else %} - define_null_double({{type_name}}, {{name}}, {{stubs.double_splat}}) {{block}} - {% end %} - {% end %} - end - - macro create_null_double(type_name, name, **stubs) - {% type_name.resolve? || raise("Could not find a double labeled #{name}") %} - - {{type_name}}.new(true).tap do |%double| - {% for name, value in stubs %} - allow(%double).to receive({{name.id}}).and_return({{value}}) - {% end %} - end - end - - macro define_null_double(type_name, name, **stubs, &block) - class {{type_name}} < ::Spectator::Mocks::Double - def initialize(null = true) - super({{name.id.stringify}}, null) - end - - def as_null_object - {{type_name}}.new(true) - end - - # TODO: Do something with **stubs? - - {{block.body}} - end - end - - def anonymous_null_double(name = "Anonymous", **stubs) - ::Spectator::Mocks::AnonymousNullDouble.new(name, stubs) - end - - macro mock(name, &block) - {% resolved = name.resolve - type = if resolved < Reference - :class - elsif resolved < Value - :struct - else - :module - end %} - {% begin %} - {{type.id}} ::{{resolved.id}} - include ::Spectator::Mocks::Stubs - - {{block.body}} - end - {% end %} - end - - macro verify_double(name, &block) - {% resolved = name.resolve - type = if resolved < Reference - :class - elsif resolved < Value - :struct - else - :module - end %} - {% begin %} - {{type.id}} ::{{resolved.id}} - include ::Spectator::Mocks::Reflection - - macro finished - _spectator_reflect - end - end - {% end %} - end - - def allow(thing) - ::Spectator::Mocks::Allow.new(thing) - end - - def allow_any_instance_of(type : T.class) forall T - ::Spectator::Mocks::AllowAnyInstance(T).new - end - - macro expect_any_instance_of(type, _source_file = __FILE__, _source_line = __LINE__) - %location = ::Spectator::Location.new({{_source_file}}, {{_source_line}}) - ::Spectator::Mocks::ExpectAnyInstance({{type}}).new(%location) - end - - macro receive(method_name, _source_file = __FILE__, _source_line = __LINE__, &block) - %location = ::Spectator::Location.new({{_source_file}}, {{_source_line}}) - {% if block %} - ::Spectator::Mocks::ProcMethodStub.create({{method_name.id.symbolize}}, %location) { {{block.body}} } - {% else %} - ::Spectator::Mocks::NilMethodStub.new({{method_name.id.symbolize}}, %location) - {% end %} - end - - macro receive_messages(_source_file = __FILE__, _source_line = __LINE__, **stubs) - %location = ::Spectator::Location.new({{_source_file}}, {{_source_line}}) - %stubs = [] of ::Spectator::Mocks::MethodStub - {% for name, value in stubs %} - %stubs << ::Spectator::Mocks::ValueMethodStub.new({{name.id.symbolize}}, %location, {{value}}) - {% end %} - %stubs - end - - def no_args - ::Spectator::Mocks::NoArguments.new - end - end -end diff --git a/src/spectator/mocks.cr b/src/spectator/mocks.cr index 50330f7..b72ffea 100644 --- a/src/spectator/mocks.cr +++ b/src/spectator/mocks.cr @@ -4,12 +4,6 @@ require "./system_exit" module Spectator # Functionality for mocking existing types. module Mocks - def self.run(context : TestContext) - Registry.prepare(context) - yield - ensure - Registry.reset - end end end @@ -17,8 +11,8 @@ end # This captures *most* (technically not all) attempts to exit the process. # This stub only takes effect in example code. # It intercepts `exit` calls and raises `Spectator::SystemExit` to prevent killing the test. -class ::Process - include ::Spectator::Mocks::Stubs +# class ::Process +# include ::Spectator::Mocks::Stubs - stub self.exit(code) { raise ::Spectator::SystemExit.new } -end +# stub self.exit(code) { raise ::Spectator::SystemExit.new } +# end diff --git a/src/spectator/mocks/allow.cr b/src/spectator/mocks/allow.cr deleted file mode 100644 index fb6b86f..0000000 --- a/src/spectator/mocks/allow.cr +++ /dev/null @@ -1,18 +0,0 @@ -require "./registry" - -module Spectator::Mocks - struct Allow(T) - def initialize(@mock : T) - end - - def to(stub : MethodStub) : Nil - Harness.current.mocks.add_stub(@mock, stub) - end - - def to(stubs : Enumerable(MethodStub)) : Nil - stubs.each do |stub| - Harness.current.mocks.add_stub(@mock, stub) - end - end - end -end diff --git a/src/spectator/mocks/allow_any_instance.cr b/src/spectator/mocks/allow_any_instance.cr deleted file mode 100644 index b652940..0000000 --- a/src/spectator/mocks/allow_any_instance.cr +++ /dev/null @@ -1,15 +0,0 @@ -require "./registry" - -module Spectator::Mocks - struct AllowAnyInstance(T) - def to(stub : MethodStub) : Nil - Harness.current.mocks.add_type_stub(T, stub) - end - - def to(stubs : Enumerable(MethodStub)) : Nil - stubs.each do |stub| - Harness.current.mocks.add_type_stub(T, stub) - end - end - end -end diff --git a/src/spectator/mocks/anonymous_double.cr b/src/spectator/mocks/anonymous_double.cr deleted file mode 100644 index 7a2c0c4..0000000 --- a/src/spectator/mocks/anonymous_double.cr +++ /dev/null @@ -1,26 +0,0 @@ -module Spectator::Mocks - class AnonymousDouble(T) - def initialize(@name : String, @values : T) - end - - def as_null_object - AnonymousNullDouble.new(@name, @values) - end - - macro method_missing(call) - args = ::Spectator::Mocks::GenericArguments.create({{call.args.splat}}) - call = ::Spectator::Mocks::MethodCall.new({{call.name.symbolize}}, args) - ::Spectator::Harness.current.mocks.record_call(self, call) - if (stub = ::Spectator::Harness.current.mocks.find_stub(self, call)) - stub.call!(args) do - @values.fetch({{call.name.symbolize}}) { raise "Consistency error - method stubbed with no implementation"; nil } - end - else - @values.fetch({{call.name.symbolize}}) do - return nil if ::Spectator::Harness.current.mocks.expected?(self, call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{call.name}}") - end - end - end - end -end diff --git a/src/spectator/mocks/anonymous_null_double.cr b/src/spectator/mocks/anonymous_null_double.cr deleted file mode 100644 index 441eafa..0000000 --- a/src/spectator/mocks/anonymous_null_double.cr +++ /dev/null @@ -1,17 +0,0 @@ -module Spectator::Mocks - class AnonymousNullDouble(T) - def initialize(@name : String, @values : T) - end - - macro method_missing(call) - args = ::Spectator::Mocks::GenericArguments.create({{call.args.splat}}) - call = ::Spectator::Mocks::MethodCall.new({{call.name.symbolize}}, args) - ::Spectator::Harness.current.mocks.record_call(self, call) - if (stub = ::Spectator::Harness.current.mocks.find_stub(self, call)) - stub.call!(args) { @values.fetch({{call.name.symbolize}}) { self } } - else - @values.fetch({{call.name.symbolize}}) { self } - end - end - end -end diff --git a/src/spectator/mocks/arguments.cr b/src/spectator/mocks/arguments.cr deleted file mode 100644 index e909235..0000000 --- a/src/spectator/mocks/arguments.cr +++ /dev/null @@ -1,4 +0,0 @@ -module Spectator::Mocks - abstract class Arguments - end -end diff --git a/src/spectator/mocks/double.cr b/src/spectator/mocks/double.cr deleted file mode 100644 index 6768c49..0000000 --- a/src/spectator/mocks/double.cr +++ /dev/null @@ -1,130 +0,0 @@ -require "./generic_method_stub" -require "./method_call" -require "./unexpected_message_error" - -module Spectator::Mocks - abstract class Double - def initialize(@spectator_double_name : String, @null = false) - end - - private macro stub(definition, *types, return_type = :undefined, &block) - {% - name = nil - params = nil - args = nil - body = nil - if definition.is_a?(Call) # stub foo { :bar } - named = false - name = definition.name.id - params = definition.args - - # Possibly a weird compiler bug, but syntax like this: - # stub instance.==(other) { true } - # Results in `other` being the call `other { true }`. - # This works around the issue by pulling out the block - # and setting the parameter to just the name. - if params.last.is_a?(Call) - body = params.last.block - params[-1] = params.last.name - end - - args = params.map do |p| - n = p.is_a?(TypeDeclaration) ? p.var : p.id - r = named ? "#{n}: #{n}".id : n - named = true if n.starts_with?('*') - r - end - - # The unless is here because `||=` can't be used in macros @_@ - unless body - body = definition.block.is_a?(Nop) ? block : definition.block - end - elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol - name = definition.var - params = [] of MacroId - args = [] of MacroId - body = block - elsif definition.is_a?(SymbolLiteral) # stub :foo, arg : Int32 - name = definition.id - named = false - params = types - if params.last.is_a?(Call) - body = params.last.block - params[-1] = params.last.name - end - args = params.map do |p| - n = p.is_a?(TypeDeclaration) ? p.var : p.id - r = named ? "#{n}: #{n}".id : n - named = true if n.starts_with?('*') - r - end - body = block unless body - else - raise "Unrecognized stub format" - end - %} - - def {{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - ::Spectator::Harness.current.mocks.record_call(self, %call) - if (%stub = ::Spectator::Harness.current.mocks.find_stub(self, %call)) - %stub.call!(%args) { %method({{args.splat}}) } - else - %method({{args.splat}}) - end - end - - def {{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - ::Spectator::Harness.current.mocks.record_call(self, %call) - if (%stub = ::Spectator::Harness.current.mocks.find_stub(self, %call)) - %stub.call!(%args) { %method({{args.splat}}) { |*%ya| yield *%ya } } - else - %method({{args.splat}}) do |*%yield_args| - yield *%yield_args - end - end - end - - def %method({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - {% if body && !body.is_a?(Nop) %} - {{body.body}} - {% elsif return_type.is_a?(ArrayLiteral) %} - {{return_type.splat}} - {% else %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - unless ::Spectator::Harness.current.mocks.expected?(self, %call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{name}}") - end - - # This code shouldn't be reached, but makes the compiler happy to have a matching return type. - {% if return_type != :undefined %} - %x = uninitialized {{return_type}} - {% elsif definition.is_a?(TypeDeclaration) %} - %x = uninitialized {{definition.type}} - {% else %} - nil - {% end %} - {% end %} - end - end - - macro method_missing(call) - args = ::Spectator::Mocks::GenericArguments.create({{call.args.splat}}) - call = ::Spectator::Mocks::MethodCall.new({{call.name.symbolize}}, args) - ::Spectator::Harness.current.mocks.record_call(self, call) - - return self if @null - return self if ::Spectator::Harness.current.mocks.expected?(self, call) - - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{call.name}}") - end - - def to_s(io) - io << "Double(" << @spectator_double_name << ')' - end - end -end diff --git a/src/spectator/mocks/exception_method_stub.cr b/src/spectator/mocks/exception_method_stub.cr deleted file mode 100644 index 2f223fd..0000000 --- a/src/spectator/mocks/exception_method_stub.cr +++ /dev/null @@ -1,14 +0,0 @@ -require "./generic_arguments" -require "./generic_method_stub" - -module Spectator::Mocks - class ExceptionMethodStub(ExceptionType) < GenericMethodStub(Nil) - def initialize(name, location, @exception : ExceptionType, args = nil) - super(name, location, args) - end - - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - raise @exception - end - end -end diff --git a/src/spectator/mocks/expect_any_instance.cr b/src/spectator/mocks/expect_any_instance.cr deleted file mode 100644 index fcf7eec..0000000 --- a/src/spectator/mocks/expect_any_instance.cr +++ /dev/null @@ -1,23 +0,0 @@ -require "./registry" - -module Spectator::Mocks - struct ExpectAnyInstance(T) - def initialize(@location : Location) - end - - def to(stub : MethodStub) : Nil - actual = Value.new(T) - Harness.current.mocks.expect(T, stub) - value = Value.new(stub.name, stub.to_s) - matcher = Matchers::ReceiveTypeMatcher.new(value, stub.arguments?) - target = Expectation::Target.new(actual, @location) - target.to_eventually(matcher) - end - - def to(stubs : Enumerable(MethodStub)) : Nil - stubs.each do |stub| - to(stub) - end - end - end -end diff --git a/src/spectator/mocks/generic_arguments.cr b/src/spectator/mocks/generic_arguments.cr deleted file mode 100644 index 156ef27..0000000 --- a/src/spectator/mocks/generic_arguments.cr +++ /dev/null @@ -1,41 +0,0 @@ -require "./arguments" - -module Spectator::Mocks - class GenericArguments(T, NT) < Arguments - protected getter args - protected getter opts - - def initialize(@args : T, @opts : NT) - end - - def self.create(*args, **opts) - GenericArguments.new(args, opts) - end - - def pass_to(dispatcher) - dispatcher.call(*@args, **@opts) - end - - def ===(other) : Bool - return false unless @args === other.args - return false unless @opts.size === other.opts.size - - @opts.keys.all? do |key| - other.opts.has_key?(key) && @opts[key] === other.opts[key] - end - end - - def to_s(io) - @args.each_with_index do |arg, i| - arg.inspect(io) - io << ", " if i < @args.size - 1 - end - io << ", " unless @args.empty? || @opts.empty? - @opts.each_with_index do |key, value, i| - io << key << ": " - value.inspect(io) - io << ", " if i < @opts.size - 1 - end - end - end -end diff --git a/src/spectator/mocks/generic_method_stub.cr b/src/spectator/mocks/generic_method_stub.cr deleted file mode 100644 index 15ef9b4..0000000 --- a/src/spectator/mocks/generic_method_stub.cr +++ /dev/null @@ -1,26 +0,0 @@ -require "./arguments" -require "./generic_arguments" -require "./method_call" -require "./method_stub" - -module Spectator::Mocks - abstract class GenericMethodStub(ReturnType) < MethodStub - getter! arguments : Arguments - - def initialize(name, location, @args : Arguments? = nil) - super(name, location) - end - - def callable?(call : MethodCall) : Bool - super && (!@args || @args === call.args) - end - - def to_s(io) - super(io) - if @args - io << '(' << @args << ')' - end - io << " : " << ReturnType << " at " << @location - end - end -end diff --git a/src/spectator/mocks/method_call.cr b/src/spectator/mocks/method_call.cr deleted file mode 100644 index 79b3a8b..0000000 --- a/src/spectator/mocks/method_call.cr +++ /dev/null @@ -1,13 +0,0 @@ -module Spectator::Mocks - class MethodCall - getter name : Symbol - getter args : Arguments - - def initialize(@name : Symbol, @args : Arguments) - end - - def to_s(io) - io << '#' << @name - end - end -end diff --git a/src/spectator/mocks/method_stub.cr b/src/spectator/mocks/method_stub.cr deleted file mode 100644 index 9ad78a3..0000000 --- a/src/spectator/mocks/method_stub.cr +++ /dev/null @@ -1,34 +0,0 @@ -require "../location" -require "./method_call" - -module Spectator::Mocks - abstract class MethodStub - getter name : Symbol - - getter location : Location - - def initialize(@name, @location) - end - - def callable?(call : MethodCall) : Bool - @name == call.name - end - - abstract def call(args : GenericArguments(T, NT), &original : -> RT) forall T, NT, RT - - def call!(args : GenericArguments(T, NT), &original : -> RT) : RT forall T, NT, RT - value = call(args) { |*ya| yield *ya } - if value.is_a?(RT) - value.as(RT) - elsif value.nil? && RT == NoReturn - raise SystemExit.new - else - raise TypeCastError.new("The return type of stub #{self} doesn't match the expected type #{RT}") - end - end - - def to_s(io) - io << '#' << @name - end - end -end diff --git a/src/spectator/mocks/multi_value_method_stub.cr b/src/spectator/mocks/multi_value_method_stub.cr deleted file mode 100644 index f025271..0000000 --- a/src/spectator/mocks/multi_value_method_stub.cr +++ /dev/null @@ -1,19 +0,0 @@ -require "./generic_arguments" -require "./generic_method_stub" - -module Spectator::Mocks - class MultiValueMethodStub(ReturnType) < GenericMethodStub(ReturnType) - @index = 0 - - def initialize(name, location, @values : ReturnType, args = nil) - super(name, location, args) - raise ArgumentError.new("Values must have at least one item") if @values.size < 1 - end - - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - value = @values[@index] - @index += 1 if @index < @values.size - 1 - value - end - end -end diff --git a/src/spectator/mocks/nil_method_stub.cr b/src/spectator/mocks/nil_method_stub.cr deleted file mode 100644 index 1e87db6..0000000 --- a/src/spectator/mocks/nil_method_stub.cr +++ /dev/null @@ -1,52 +0,0 @@ -require "./generic_arguments" -require "./generic_method_stub" -require "./value_method_stub" - -module Spectator::Mocks - class NilMethodStub < GenericMethodStub(Nil) - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - nil - end - - def and_return - self - end - - def and_return(value) - ValueMethodStub.new(@name, @location, value, @args) - end - - def and_return(*values) - MultiValueMethodStub.new(@name, @location, values.to_a, @args) - end - - def and_raise(exception_type : Exception.class) - ExceptionMethodStub.new(@name, @location, exception_type.new, @args) - end - - def and_raise(exception : Exception) - ExceptionMethodStub.new(@name, @location, exception, @args) - end - - def and_raise(message : String) - ExceptionMethodStub.new(@name, @location, Exception.new(message), @args) - end - - def and_raise(exception_type : Exception.class, *args) forall T - ExceptionMethodStub.new(@name, @location, exception_type.new(*args), @args) - end - - def with(*args : *T, **opts : **NT) forall T, NT - args = GenericArguments.new(args, opts) - NilMethodStub.new(@name, @location, args) - end - - def with(args : Arguments) - NilMethodStub.new(@name, @location, @args) - end - - def and_call_original - OriginalMethodStub.new(@name, @location, @args) - end - end -end diff --git a/src/spectator/mocks/no_arguments.cr b/src/spectator/mocks/no_arguments.cr deleted file mode 100644 index d99f2bf..0000000 --- a/src/spectator/mocks/no_arguments.cr +++ /dev/null @@ -1,21 +0,0 @@ -require "./arguments" - -module Spectator::Mocks - class NoArguments < Arguments - def args - Tuple.new - end - - def opts - NamedTuple.new - end - - def ===(other : Arguments) : Bool - other.args.empty? && other.opts.empty? - end - - def ===(other) : Bool - false - end - end -end diff --git a/src/spectator/mocks/original_method_stub.cr b/src/spectator/mocks/original_method_stub.cr deleted file mode 100644 index 2097eb6..0000000 --- a/src/spectator/mocks/original_method_stub.cr +++ /dev/null @@ -1,10 +0,0 @@ -require "./generic_arguments" -require "./generic_method_stub" - -module Spectator::Mocks - class OriginalMethodStub < GenericMethodStub(Nil) - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - yield - end - end -end diff --git a/src/spectator/mocks/proc_method_stub.cr b/src/spectator/mocks/proc_method_stub.cr deleted file mode 100644 index de3a68d..0000000 --- a/src/spectator/mocks/proc_method_stub.cr +++ /dev/null @@ -1,18 +0,0 @@ -require "./arguments" -require "./generic_method_stub" - -module Spectator::Mocks - class ProcMethodStub(ReturnType) < GenericMethodStub(ReturnType) - def initialize(name, location, @proc : -> ReturnType, args = nil) - super(name, location, args) - end - - def self.create(name, location, args = nil, &block : -> T) forall T - ProcMethodStub.new(name, location, block, args) - end - - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - @proc.call - end - end -end diff --git a/src/spectator/mocks/reflection.cr b/src/spectator/mocks/reflection.cr deleted file mode 100644 index 9a03fd9..0000000 --- a/src/spectator/mocks/reflection.cr +++ /dev/null @@ -1,26 +0,0 @@ -require "../anything" - -module Spectator::Mocks - module Reflection - private macro _spectator_reflect - {% for meth in @type.methods %} - %location = ::Spectator::Location.new({{meth.filename}}, {{meth.line_number}}) - %args = ::Spectator::Mocks::GenericArguments.create( - {% for arg, i in meth.args %} - {% matcher = if arg.restriction - if arg.restriction == :self.id - @type.id - else - arg.restriction - end - else - "::Spectator::Anything.new".id - end %} - {{matcher}}{% if i < meth.args.size %},{% end %} - {% end %} - ) - ::Spectator::Mocks::TypeRegistry.add({{@type.id.stringify}}, {{meth.name.symbolize}}, %location, %args) - {% end %} - end - end -end diff --git a/src/spectator/mocks/registry.cr b/src/spectator/mocks/registry.cr deleted file mode 100644 index 41419e2..0000000 --- a/src/spectator/mocks/registry.cr +++ /dev/null @@ -1,110 +0,0 @@ -module Spectator::Mocks - class Registry - alias Key = Tuple(String, UInt64) - - private struct Entry - getter stubs = Deque(MethodStub).new - getter calls = Deque(MethodCall).new - getter expected = Set(MethodStub).new - end - - @all_instances = {} of String => Entry - @entries = {} of Key => Entry - - def reset : Nil - @entries.clear - end - - def add_stub(object, stub : MethodStub) : Nil - # Stubs are added in reverse order, - # so that later-defined stubs override previously defined ones. - fetch_instance(object).stubs.unshift(stub) - end - - def add_stub(type : T.class, stub : MethodStub) : Nil forall T - add_type_stub(type, stub) - end - - def add_type_stub(type, stub : MethodStub) : Nil - fetch_type(type).stubs.unshift(stub) - end - - def stubbed?(object, method_name : Symbol) : Bool - fetch_instance(object).stubs.any? { |stub| stub.name == method_name } || - fetch_type(object.class).stubs.any? { |stub| stub.name == method_name } - end - - def find_stub(object, call : MethodCall) - fetch_instance(object).stubs.find(&.callable?(call)) || - fetch_type(object.class).stubs.find(&.callable?(call)) - end - - def find_stub(type : T.class, call : MethodCall) forall T - fetch_type(type).stubs.find(&.callable?(call)) - end - - def record_call(object, call : MethodCall) : Nil - fetch_instance(object).calls << call - fetch_type(object.class).calls << call - end - - def calls_for(object, method_name : Symbol) - fetch_instance(object).calls.select { |call| call.name == method_name } - end - - def calls_for_type(type : T.class, method_name : Symbol) forall T - fetch_type(type).calls.select { |call| call.name == method_name } - end - - def expected?(object, call : MethodCall) : Bool - fetch_instance(object).expected.any?(&.callable?(call)) || - fetch_type(object.class).expected.any?(&.callable?(call)) - end - - def exit_handled? : Bool - # Lazily check if an `exit` method was called and it was expected. - # This is okay since an `expect().to receive(:exit)` should check the details of the call. - (@entries.any? { |_key, entry| entry.expected.any? { |stub| stub.name == :exit } } || - @all_instances.any? { |_key, entry| entry.expected.any? { |stub| stub.name == :exit } }) && - @entries.any? { |_key, entry| entry.calls.any? { |call| call.name == :exit } } - end - - def expect(object, stub : MethodStub) : Nil - entry = fetch_instance(object) - entry.expected.add(stub) - entry.stubs.unshift(stub) - end - - def expect(type : T.class, stub : MethodStub) : Nil forall T - entry = fetch_type(type) - entry.expected.add(stub) - entry.stubs.unshift(stub) - end - - private def fetch_instance(object) - key = unique_key(object) - if @entries.has_key?(key) - @entries[key] - else - @entries[key] = Entry.new - end - end - - private def fetch_type(type) - key = type.name - if @all_instances.has_key?(key) - @all_instances[key] - else - @all_instances[key] = Entry.new - end - end - - private def unique_key(reference : ::Reference) - {reference.class.name, reference.object_id} - end - - private def unique_key(value : ::Value) - {value.class.name, value.hash} - end - end -end diff --git a/src/spectator/mocks/stubs.cr b/src/spectator/mocks/stubs.cr deleted file mode 100644 index d35ceab..0000000 --- a/src/spectator/mocks/stubs.cr +++ /dev/null @@ -1,127 +0,0 @@ -module Spectator::Mocks - module Stubs - private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, return_type = :undefined, &block) - {% - receiver = nil - name = nil - params = nil - args = nil - body = nil - if definition.is_a?(Call) # stub foo { :bar } - receiver = definition.receiver.id - named = false - name = definition.name.id - params = definition.args - if params.last.is_a?(Call) - body = params.last.block - params[-1] = params.last.name - end - args = params.map do |p| - n = p.is_a?(TypeDeclaration) ? p.var : p.id - r = named ? "#{n}: #{n}".id : n - named = true if n.starts_with?('*') - r - end - unless body - body = definition.block.is_a?(Nop) ? block : definition.block - end - elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol - name = definition.var - params = [] of MacroId - args = [] of MacroId - body = block - elsif definition.is_a?(SymbolLiteral) # stub :foo, arg : Int32 - name = definition.id - named = false - params = types - if params.last.is_a?(Call) - body = params.last.block - params[-1] = params.last.name - end - args = params.map do |p| - n = p.is_a?(TypeDeclaration) ? p.var : p.id - r = named ? "#{n}: #{n}".id : n - named = true if n.starts_with?('*') - r - end - body = block unless body - else - raise "Unrecognized stub format" - end - - t = @type - receiver = if receiver == :self.id - t = t.class - "self." - else - "" - end.id - original = if (name == :new.id && receiver == "self.".id) || - (t.superclass && t.superclass.has_method?(name) && !t.overrides?(t.superclass, name)) - :super - elsif t.has_method?(name) - :previous_def - else - name - end.id - fallback = if original == :super.id || original == :previous_def.id - original - else - "::#{original}(#{args.splat})".id - end - %} - - {% if body && !body.is_a?(Nop) %} - def {{receiver}}%method({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - {{body.body}} - end - {% elsif return_type.is_a?(ArrayLiteral) %} - def {{receiver}}%method({{params.splat}}) : {{return_type.type}} - {{return_type.splat}} - end - {% end %} - - def {{receiver}}{{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - if (%harness = ::Spectator::Harness.current?) - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - %harness.mocks.record_call(self, %call) - if (%stub = %harness.mocks.find_stub(self, %call)) - return %stub.call!(%args) { {{fallback}} } - end - - {% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %} - %method({{args.splat}}) - {% else %} - {{fallback}} - {% end %} - else - {{fallback}} - end - end - - def {{receiver}}{{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - if (%harness = ::Spectator::Harness.current?) - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - %harness.mocks.record_call(self, %call) - if (%stub = %harness.mocks.find_stub(self, %call)) - return %stub.call!(%args) { {{fallback}} { |*%ya| yield *%ya } } - end - - {% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %} - %method({{args.splat}}) { |*%ya| yield *%ya } - {% else %} - {{fallback}} do |*%yield_args| - yield *%yield_args - end - {% end %} - else - {{fallback}} do |*%yield_args| - yield *%yield_args - end - end - end - end - end -end diff --git a/src/spectator/mocks/type_registry.cr b/src/spectator/mocks/type_registry.cr deleted file mode 100644 index 631fec9..0000000 --- a/src/spectator/mocks/type_registry.cr +++ /dev/null @@ -1,25 +0,0 @@ -module Spectator::Mocks - module TypeRegistry - extend self - - alias Key = Tuple(String, Symbol) - - @@entries = {} of Key => Deque(MethodStub) - - def add(type_name : String, method_name : Symbol, location : Location, args : Arguments) : Nil - key = {type_name, method_name} - list = if @@entries.has_key?(key) - @@entries[key] - else - @@entries[key] = Deque(MethodStub).new - end - list << NilMethodStub.new(method_name, location, args) - end - - def exists?(type_name : String, call : MethodCall) : Bool - key = {type_name, call.name} - list = @@entries.fetch(key) { return false } - list.any?(&.callable?(call)) - end - end -end diff --git a/src/spectator/mocks/unexpected_message_error.cr b/src/spectator/mocks/unexpected_message_error.cr deleted file mode 100644 index 9207846..0000000 --- a/src/spectator/mocks/unexpected_message_error.cr +++ /dev/null @@ -1,4 +0,0 @@ -module Spectator::Mocks - class UnexpectedMessageError < Exception - end -end diff --git a/src/spectator/mocks/value_method_stub.cr b/src/spectator/mocks/value_method_stub.cr deleted file mode 100644 index d3e7f5e..0000000 --- a/src/spectator/mocks/value_method_stub.cr +++ /dev/null @@ -1,14 +0,0 @@ -require "./generic_arguments" -require "./generic_method_stub" - -module Spectator::Mocks - class ValueMethodStub(ReturnType) < GenericMethodStub(ReturnType) - def initialize(name, location, @value : ReturnType, args = nil) - super(name, location, args) - end - - def call(_args : GenericArguments(T, NT), &_original : -> RT) forall T, NT, RT - @value - end - end -end diff --git a/src/spectator/mocks/verifying_double.cr b/src/spectator/mocks/verifying_double.cr deleted file mode 100644 index f7be6e3..0000000 --- a/src/spectator/mocks/verifying_double.cr +++ /dev/null @@ -1,106 +0,0 @@ -module Spectator::Mocks - abstract class VerifyingDouble(T) - def initialize(@null = false) - end - - private macro stub(definition, &block) - {% - name = nil - params = nil - args = nil - body = nil - if definition.is_a?(Call) # stub foo { :bar } - named = false - name = definition.name.id - params = definition.args - args = params.map do |p| - n = p.is_a?(TypeDeclaration) ? p.var : p.id - r = named ? "#{n}: #{n}".id : n - named = true if n.starts_with?('*') - r - end - body = definition.block.is_a?(Nop) ? block : definition.block - elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol - name = definition.var - params = [] of MacroId - args = [] of MacroId - body = block - else - raise "Unrecognized stub format" - end - %} - - def {{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - ::Spectator::Harness.current.mocks.record_call(self, %call) - - unless ::Spectator::Mocks::TypeRegistry.exists?(T.to_s, %call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{name}} - #{T} does not respond to #{%call}") - end - - if (%stub = ::Spectator::Harness.current.mocks.find_stub(self, %call)) - %stub.call!(%args) { %method({{args.splat}}) } - else - %method({{args.splat}}) - end - end - - def {{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - ::Spectator::Harness.current.mocks.record_call(self, %call) - - unless ::Spectator::Mocks::TypeRegistry.exists?(T.to_s, %call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{name}} - #{T} does not respond to #{%call}") - end - - if (%stub = ::Spectator::Harness.current.mocks.find_stub(self, %call)) - %stub.call!(%args) { %method({{args.splat}}) { |*%ya| yield *%ya } } - else - %method({{args.splat}}) do |*%yield_args| - yield *%yield_args - end - end - end - - def %method({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} - {% if body && !body.is_a?(Nop) %} - {{body.body}} - {% else %} - %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) - %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) - unless ::Spectator::Harness.current.mocks.expected?(self, %call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{name}}") - end - - # This code shouldn't be reached, but makes the compiler happy to have a matching return type. - {% if definition.is_a?(TypeDeclaration) %} - %x = uninitialized {{definition.type}} - {% else %} - nil - {% end %} - {% end %} - end - end - - macro method_missing(call) - args = ::Spectator::Mocks::GenericArguments.create({{call.args.splat}}) - call = ::Spectator::Mocks::MethodCall.new({{call.name.symbolize}}, args) - ::Spectator::Harness.current.mocks.record_call(self, call) - - unless ::Spectator::Mocks::TypeRegistry.exists?(T.to_s, call) - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{call.name}} - #{T} does not respond to #{call}") - end - - return self if @null - return self if ::Spectator::Harness.current.mocks.expected?(self, call) - - raise ::Spectator::Mocks::UnexpectedMessageError.new("#{self} received unexpected message {{call.name}}") - end - - def to_s(io) - io << "Double(" << T << ')' - end - end -end