fangorn/ex_git_objectstore
public
ref:main
# Copyright 2026 Cole Christensen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
defmodule ExGitObjectstore.Test.TrackingMemory do
@moduledoc """
A wrapper around Memory storage that tracks which storage operations are called.
Used to verify that the ObjectResolver only downloads pack data when needed.
"""
@behaviour ExGitObjectstore.Storage
alias ExGitObjectstore.Storage.Memory
@doc """
Start a new tracking memory store. Returns `{:ok, pid}` with memory pid
and a separate agent for tracking calls.
"""
def start_link do
{:ok, mem_pid} = Memory.start_link()
{:ok, tracker_pid} = Agent.start_link(fn -> [] end)
{:ok, {mem_pid, tracker_pid}}
end
def config({mem_pid, tracker_pid}) do
%{pid: mem_pid, tracker_pid: tracker_pid}
end
@doc """
Get the list of tracked calls (most recent first).
"""
def get_calls(%{tracker_pid: tracker_pid}) do
Agent.get(tracker_pid, & &1)
end
defp track(%{tracker_pid: tracker_pid}, call) do
Agent.update(tracker_pid, fn calls -> [call | calls] end)
end
defp mem_config(%{pid: pid}), do: %{pid: pid}
# -- Delegated operations with tracking --
@impl true
def get_object(config, prefix, sha) do
track(config, {:get_object, prefix, sha})
Memory.get_object(mem_config(config), prefix, sha)
end
@impl true
def put_object(config, prefix, sha, data) do
track(config, {:put_object, prefix, sha})
Memory.put_object(mem_config(config), prefix, sha, data)
end
@impl true
def object_exists?(config, prefix, sha) do
track(config, {:object_exists?, prefix, sha})
Memory.object_exists?(mem_config(config), prefix, sha)
end
@impl true
def list_packs(config, prefix) do
track(config, {:list_packs, prefix})
Memory.list_packs(mem_config(config), prefix)
end
@impl true
def get_pack(config, prefix, pack_sha) do
track(config, {:get_pack, prefix, pack_sha})
Memory.get_pack(mem_config(config), prefix, pack_sha)
end
@impl true
def get_pack_index(config, prefix, pack_sha) do
track(config, {:get_pack_index, prefix, pack_sha})
Memory.get_pack_index(mem_config(config), prefix, pack_sha)
end
@impl true
def put_pack(config, prefix, pack_sha, pack_data, idx_data) do
track(config, {:put_pack, prefix, pack_sha})
Memory.put_pack(mem_config(config), prefix, pack_sha, pack_data, idx_data)
end
@impl true
def stream_pack(config, prefix, pack_sha) do
track(config, {:stream_pack, prefix, pack_sha})
Memory.stream_pack(mem_config(config), prefix, pack_sha)
end
@impl true
def get_ref(config, prefix, ref_path) do
track(config, {:get_ref, prefix, ref_path})
Memory.get_ref(mem_config(config), prefix, ref_path)
end
@impl true
def put_ref(config, prefix, ref_path, new_sha, old_sha) do
track(config, {:put_ref, prefix, ref_path})
Memory.put_ref(mem_config(config), prefix, ref_path, new_sha, old_sha)
end
@impl true
def delete_ref(config, prefix, ref_path) do
track(config, {:delete_ref, prefix, ref_path})
Memory.delete_ref(mem_config(config), prefix, ref_path)
end
@impl true
def list_refs(config, prefix, ref_prefix) do
track(config, {:list_refs, prefix, ref_prefix})
Memory.list_refs(mem_config(config), prefix, ref_prefix)
end
@impl true
def get_head(config, prefix) do
track(config, {:get_head, prefix})
Memory.get_head(mem_config(config), prefix)
end
@impl true
def put_head(config, prefix, target) do
track(config, {:put_head, prefix})
Memory.put_head(mem_config(config), prefix, target)
end
@impl true
def list_objects(config, prefix) do
track(config, {:list_objects, prefix})
Memory.list_objects(mem_config(config), prefix)
end
@impl true
def get_blob(config, prefix, blob_key) do
track(config, {:get_blob, prefix, blob_key})
Memory.get_blob(mem_config(config), prefix, blob_key)
end
@impl true
def put_blob(config, prefix, blob_key, data) do
track(config, {:put_blob, prefix, blob_key})
Memory.put_blob(mem_config(config), prefix, blob_key, data)
end
@impl true
def delete_blob(config, prefix, blob_key) do
track(config, {:delete_blob, prefix, blob_key})
Memory.delete_blob(mem_config(config), prefix, blob_key)
end
@impl true
def blob_exists?(config, prefix, blob_key) do
track(config, {:blob_exists?, prefix, blob_key})
Memory.blob_exists?(mem_config(config), prefix, blob_key)
end
end