Merge remote-tracking branch 'origin/master' into release/0.10

This commit is contained in:
Michael Miller 2021-07-11 00:16:25 -06:00
commit 6c55301d0b
No known key found for this signature in database
GPG key ID: F9A0C5C65B162436
11 changed files with 140 additions and 12 deletions

View file

@ -98,6 +98,7 @@ module Spectator
elapsed = Time.measure do
error = catch { yield }
end
error = nil if error.is_a?(SystemExit) && mocks.exit_handled?
{elapsed, error}
end

View file

@ -1,4 +1,5 @@
require "./mocks/*"
require "./system_exit"
module Spectator
# Functionality for mocking existing types.
@ -11,3 +12,13 @@ module Spectator
end
end
end
# Add default stub to `exit` method.
# 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
stub self.exit(code) { raise ::Spectator::SystemExit.new }
end

View file

@ -20,6 +20,8 @@ module Spectator::Mocks
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

View file

@ -41,6 +41,10 @@ module Spectator::Mocks
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

View file

@ -35,6 +35,10 @@ module Spectator::Mocks
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
@ -53,6 +57,14 @@ module Spectator::Mocks
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)

View file

@ -62,7 +62,7 @@ module Spectator::Mocks
elsif t.has_method?(name)
:previous_def
else
"::#{name}"
name
end.id
%}
@ -82,11 +82,7 @@ module Spectator::Mocks
%call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args)
%harness.mocks.record_call(self, %call)
if (%stub = %harness.mocks.find_stub(self, %call))
if typeof({{original}}) == NoReturn
return %stub.call!(%args) { nil }
else
return %stub.call!(%args) { {{original}} }
end
return %stub.call!(%args) { {{original}} }
end
{% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %}
@ -105,11 +101,7 @@ module Spectator::Mocks
%call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args)
%harness.mocks.record_call(self, %call)
if (%stub = %harness.mocks.find_stub(self, %call))
if typeof({{original}}) == NoReturn
return %stub.call!(%args) { nil }
else
return %stub.call!(%args) { {{original}} { |*%ya| yield *%ya } }
end
return %stub.call!(%args) { {{original}} { |*%ya| yield *%ya } }
end
{% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %}

View file

@ -0,0 +1,5 @@
module Spectator
# Exception raised when `exit` is called and intercepted from a stub.
class SystemExit < Exception
end
end