Add ValueMockRegistry

Support injecting mock functionality into concrete structs (value types).
This commit is contained in:
Michael Miller 2022-05-15 12:34:50 -06:00
parent 51f133eb61
commit 37c6db250d
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
4 changed files with 153 additions and 0 deletions

View file

@ -3,6 +3,7 @@ require "./mocked"
require "./reference_mock_registry"
require "./stub"
require "./stubbed_name"
require "./value_mock_registry"
require "./value_stub"
module Spectator
@ -54,6 +55,8 @@ module Spectator
{% if type.class? %}
@@_spectator_mock_registry = ::Spectator::ReferenceMockRegistry.new
{% elsif type.struct? %}
@@_spectator_mock_registry = ::Spectator::ValueMockRegistry(self).new
{% else %}
{% raise "Unsupported type for injecting mock" %}
{% end %}

View file

@ -0,0 +1,55 @@
require "string_pool"
require "./stub"
module Spectator
# Stores collections of stubs for mocked value (struct) types.
#
# *T* is the type of value to track.
#
# This type is intended for all mocked struct types that have functionality "injected."
# That is, the type itself has mock functionality bolted on.
# Adding instance members should be avoided, for instance, it could mess up serialization.
# This registry works around that by mapping mocks (via their raw memory content) to a collection of stubs.
# Doing so prevents adding data to the mocked type.
class ValueMockRegistry(T)
@pool = StringPool.new # Used to de-dup values.
@object_stubs : Hash(String, Array(Stub))
# Creates an empty registry.
def initialize
@object_stubs = Hash(String, Array(Stub)).new do |hash, key|
hash[key] = [] of Stub
end
end
# Retrieves all stubs defined for a mocked object.
def [](object : T) : Array(Stub)
key = value_bytes(object)
@object_stubs[key]
end
# Retrieves all stubs defined for a mocked object.
#
# Yields to the block on the first retrieval.
# This allows a mock to populate the registry with initial stubs.
def fetch(object : T, & : -> Array(Stub))
key = value_bytes(object)
@object_stubs.fetch(key) do
@object_stubs[key] = yield
end
end
# Extracts heap-managed bytes for a value.
#
# Strings are used because a string pool is used.
# However, the strings are treated as an array of bytes.
@[AlwaysInline]
private def value_bytes(value : T) : String
# Get slice pointing to the memory used by the value (does not allocate).
bytes = Bytes.new(pointerof(value).as(UInt8*), sizeof(T), read_only: true)
# De-dup the value (may allocate).
@pool.get(bytes)
end
end
end