yams/tests/unit/daemon/daemon_metrics_catch2_test.cpp
Trevon 59d11a1504
fix(daemon): synchronize metrics completion and data-root snapshots (#327)
Publish owning data-directory snapshots under a mutex and route bootstrap
and fallback publications through the same synchronization boundary.

Keep polling completion locked through its final notification so shutdown
cannot destroy the condition variable while the notifier still uses it.
Exercise both publication windows with deterministic event-based tests and
use the repository thread-compatibility wrapper.

Preserve Failed in status rendering instead of reporting initialization.
Reanchor the existing metrics environment-read entry; add no exceptions.

This does not close the separate queued-collector and embedding-borrower
lifetime audits or establish universal bounded callback shutdown.

Signed-off-by: trvon <git@trevon.dev>
2026-10-22 07:12:22 -06:00

171 lines
5.7 KiB
C++

// DaemonMetrics unit tests — focused on testable surface without live daemon
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <yams/compat/thread_stop_compat.h>
#include <yams/core/types.h>
#include <yams/daemon/components/DaemonMetrics.h>
#include <yams/daemon/components/polling_completion.h>
#include <chrono>
#include <cstdint>
#include <future>
namespace yams::daemon::test {
TEST_CASE("MetricsSnapshot default values are zero / empty", "[metrics][snapshot]") {
MetricsSnapshot snap{};
REQUIRE(snap.running == true);
REQUIRE(snap.ready == false);
REQUIRE(snap.version.empty());
REQUIRE(snap.uptimeSeconds == 0);
REQUIRE(snap.requestsProcessed == 0);
REQUIRE(snap.activeConnections == 0);
REQUIRE(snap.memoryUsageMb == 0.0);
REQUIRE(snap.cpuUsagePercent == 0.0);
REQUIRE(snap.fsmTransitions == 0);
REQUIRE(snap.databasePhase.empty());
REQUIRE(snap.vectorDbDim == 0);
}
TEST_CASE("MetricsSnapshot has reasonable default readiness", "[metrics][snapshot]") {
MetricsSnapshot snap{};
REQUIRE(snap.searchActive == 0);
REQUIRE(snap.searchQueued == 0);
REQUIRE(snap.searchExecuted == 0);
REQUIRE(snap.searchCacheHitRate == 0.0);
REQUIRE(snap.searchAvgLatencyUs == 0);
REQUIRE(snap.embeddingAvailable == false);
REQUIRE(snap.workerThreads == 0);
REQUIRE(snap.workerActive == 0);
REQUIRE(snap.workerQueued == 0);
REQUIRE(snap.repairRunning == false);
REQUIRE(snap.repairInProgress == false);
REQUIRE(snap.repairQueueDepth == 0);
REQUIRE(snap.topologyRebuildRunning == false);
REQUIRE(snap.topologyArtifactsFresh == false);
REQUIRE(snap.topologyLastRunSucceeded == false);
REQUIRE(snap.governorPressureLevel == 0);
REQUIRE(snap.governorHeadroomPct == 100);
}
TEST_CASE("MetricsSnapshot fields can be set", "[metrics][snapshot]") {
MetricsSnapshot snap{};
snap.running = false;
snap.ready = true;
snap.version = "0.16.2";
snap.uptimeSeconds = 3600;
snap.requestsProcessed = 1000;
snap.activeConnections = 5;
snap.memoryUsageMb = 512.0;
snap.cpuUsagePercent = 25.5;
REQUIRE(snap.running == false);
REQUIRE(snap.ready == true);
REQUIRE(snap.version == "0.16.2");
REQUIRE(snap.uptimeSeconds == 3600);
REQUIRE(snap.requestsProcessed == 1000);
REQUIRE(snap.activeConnections == 5);
REQUIRE(snap.memoryUsageMb == Catch::Approx(512.0));
REQUIRE(snap.cpuUsagePercent == Catch::Approx(25.5));
}
TEST_CASE("MetricsSnapshot nested structures are default-initialized", "[metrics][snapshot]") {
MetricsSnapshot snap{};
REQUIRE(snap.glExtraction.limit == 0.0);
REQUIRE(snap.glExtraction.smoothedRtt == 0.0);
REQUIRE(snap.glExtraction.gradient == 0.0);
REQUIRE(snap.glExtraction.inFlight == 0);
REQUIRE(snap.glExtraction.acquireCount == 0);
REQUIRE(snap.glExtraction.rejectCount == 0);
REQUIRE(snap.glKg.limit == 0.0);
REQUIRE(snap.glEntity.limit == 0.0);
REQUIRE(snap.glEmbed.limit == 0.0);
REQUIRE(snap.gradientLimitersEnabled == false);
}
TEST_CASE("MetricsSnapshot pipeline counters start at zero", "[metrics][snapshot]") {
MetricsSnapshot snap{};
REQUIRE(snap.extractionInFlight == 0);
REQUIRE(snap.kgQueued == 0);
REQUIRE(snap.kgDropped == 0);
REQUIRE(snap.kgConsumed == 0);
REQUIRE(snap.kgInFlight == 0);
REQUIRE(snap.entityQueued == 0);
REQUIRE(snap.entityDropped == 0);
REQUIRE(snap.embedQueued == 0);
REQUIRE(snap.embedDropped == 0);
REQUIRE(snap.embedInFlight == 0);
}
TEST_CASE("MetricsSnapshot DB pool defaults are sane", "[metrics][snapshot]") {
MetricsSnapshot snap{};
REQUIRE(snap.dbWritePoolAvailable == false);
REQUIRE(snap.dbWritePoolTotalConnections == 0);
REQUIRE(snap.dbWritePoolAvailableConnections == 0);
REQUIRE(snap.dbWritePoolActiveConnections == 0);
REQUIRE(snap.dbWritePoolWaitingRequests == 0);
REQUIRE(snap.dbReadPoolAvailable == false);
REQUIRE(snap.dbReadPoolTotalConnections == 0);
REQUIRE(snap.dbReadPoolAvailableConnections == 0);
REQUIRE(snap.dbReadPoolActiveConnections == 0);
}
TEST_CASE("computePhysicalWalkTtl returns clamping behavior", "[metrics][ttl]") {
// Zero duration → minimum TTL
REQUIRE(computePhysicalWalkTtl(0) == 60'000);
// Very fast walk → minimum TTL
REQUIRE(computePhysicalWalkTtl(1) == 60'000);
// Normal walk → amplified
std::uint64_t ttl = computePhysicalWalkTtl(5'000);
REQUIRE(ttl >= 60'000);
REQUIRE(ttl <= 1'800'000);
// Very slow walk → capped at max
std::uint64_t maxTtl = computePhysicalWalkTtl(1'000'000);
REQUIRE(maxTtl <= 1'800'000);
}
TEST_CASE("Polling completion stays unobservable until its final notifier finishes",
"[metrics][polling-shutdown]") {
detail::PollingCompletion completion;
completion.markStarted();
std::promise<void> beforeNotify;
auto paused = beforeNotify.get_future();
std::promise<void> allowNotify;
auto resume = allowNotify.get_future().share();
yams::compat::jthread publisher([&] {
completion.complete([&] {
beforeNotify.set_value();
resume.wait();
});
});
// The event fixes the exact publication window; the snapshot never waits or sleeps.
// Keep the owner alive and release/join the publisher before any failing assertion.
const bool reachedBoundary =
paused.wait_for(std::chrono::seconds(2)) == std::future_status::ready;
const auto visible = reachedBoundary ? completion.tryStopped() : std::nullopt;
allowNotify.set_value();
publisher.join();
REQUIRE(reachedBoundary);
CHECK_FALSE(visible.value_or(false));
CHECK(completion.isStopped());
completion.wait();
}
} // namespace yams::daemon::test