1031 lines
38 KiB
C++
1031 lines
38 KiB
C++
// Copyright (c) 2025 YAMS Contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// Unit tests for ConfigResolver component (PBI-090)
|
|
//
|
|
// Catch2 migration from GTest (yams-3s4 / yams-zns)
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
|
|
|
#include <yams/compat/unistd.h>
|
|
#include <yams/daemon/components/ConfigResolver.h>
|
|
#include <yams/daemon/components/TuneAdvisor.h>
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
#include <yams/vector/document_chunker.h>
|
|
|
|
using namespace yams::daemon;
|
|
using Catch::Matchers::ContainsSubstring;
|
|
|
|
namespace {
|
|
|
|
struct ConfigResolverFixture {
|
|
std::filesystem::path tempDir;
|
|
|
|
ConfigResolverFixture() {
|
|
tempDir = std::filesystem::temp_directory_path() /
|
|
("yams_config_test_" +
|
|
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
|
std::filesystem::create_directories(tempDir);
|
|
}
|
|
|
|
~ConfigResolverFixture() {
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(tempDir, ec);
|
|
}
|
|
|
|
std::filesystem::path writeToml(const std::string& filename, const std::string& content) {
|
|
auto path = tempDir / filename;
|
|
std::ofstream out(path);
|
|
out << content;
|
|
return path;
|
|
}
|
|
};
|
|
|
|
class ProfileGuard {
|
|
yams::daemon::TuneAdvisor::Profile prev_;
|
|
|
|
public:
|
|
explicit ProfileGuard(yams::daemon::TuneAdvisor::Profile profile)
|
|
: prev_(yams::daemon::TuneAdvisor::tuningProfile()) {
|
|
yams::daemon::TuneAdvisor::setTuningProfile(profile);
|
|
}
|
|
~ProfileGuard() { yams::daemon::TuneAdvisor::setTuningProfile(prev_); }
|
|
|
|
ProfileGuard(const ProfileGuard&) = delete;
|
|
ProfileGuard& operator=(const ProfileGuard&) = delete;
|
|
};
|
|
|
|
struct EnvGuard {
|
|
std::string name;
|
|
std::optional<std::string> originalValue;
|
|
|
|
explicit EnvGuard(const std::string& envName, const std::string& newValue) : name(envName) {
|
|
if (const char* orig = std::getenv(name.c_str())) {
|
|
originalValue = orig;
|
|
}
|
|
#ifdef _WIN32
|
|
_putenv_s(name.c_str(), newValue.c_str());
|
|
#else
|
|
setenv(name.c_str(), newValue.c_str(), 1);
|
|
#endif
|
|
}
|
|
|
|
~EnvGuard() {
|
|
if (originalValue) {
|
|
#ifdef _WIN32
|
|
_putenv_s(name.c_str(), originalValue->c_str());
|
|
#else
|
|
setenv(name.c_str(), originalValue->c_str(), 1);
|
|
#endif
|
|
} else {
|
|
#ifdef _WIN32
|
|
_putenv_s(name.c_str(), "");
|
|
#else
|
|
unsetenv(name.c_str());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
EnvGuard(const EnvGuard&) = delete;
|
|
EnvGuard& operator=(const EnvGuard&) = delete;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("ConfigResolver::resolveEmbeddingChunkingPolicy defaults are embedding-safe",
|
|
"[daemon][components][config][chunking][catch2]") {
|
|
// Ensure config file discovery doesn't accidentally pick up a user config during local dev.
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", "");
|
|
|
|
auto policy = ConfigResolver::resolveEmbeddingChunkingPolicy();
|
|
CHECK(policy.strategy == yams::vector::ChunkingStrategy::PARAGRAPH_BASED);
|
|
CHECK_FALSE(policy.config.preserve_sentences);
|
|
CHECK_FALSE(policy.config.use_token_count);
|
|
}
|
|
|
|
TEST_CASE("ConfigResolver::resolveEmbeddingChunkingPolicy supports all embed strategies",
|
|
"[daemon][components][config][chunking][catch2]") {
|
|
struct Case {
|
|
const char* value;
|
|
yams::vector::ChunkingStrategy expected;
|
|
};
|
|
|
|
const std::vector<Case> cases = {
|
|
{"fixed", yams::vector::ChunkingStrategy::FIXED_SIZE},
|
|
{"sentence", yams::vector::ChunkingStrategy::SENTENCE_BASED},
|
|
{"paragraph", yams::vector::ChunkingStrategy::PARAGRAPH_BASED},
|
|
{"recursive", yams::vector::ChunkingStrategy::RECURSIVE},
|
|
{"sliding_window", yams::vector::ChunkingStrategy::SLIDING_WINDOW},
|
|
{"markdown", yams::vector::ChunkingStrategy::MARKDOWN_AWARE},
|
|
};
|
|
|
|
const std::string sample = "# Title\n\n"
|
|
"Intro paragraph with enough text to chunk reasonably. "
|
|
"Second sentence here. Third sentence here.\n\n"
|
|
"## Section\n"
|
|
"- bullet one\n- bullet two\n\n"
|
|
"Final paragraph.";
|
|
|
|
for (const auto& tc : cases) {
|
|
DYNAMIC_SECTION("strategy=" << tc.value) {
|
|
EnvGuard g("YAMS_EMBED_CHUNK_STRATEGY", tc.value);
|
|
|
|
auto policy = ConfigResolver::resolveEmbeddingChunkingPolicy();
|
|
CHECK(policy.strategy == tc.expected);
|
|
|
|
auto chunker = yams::vector::createChunker(policy.strategy, policy.config, nullptr);
|
|
REQUIRE(chunker);
|
|
|
|
auto chunks = chunker->chunkDocument(sample, "hash");
|
|
// Some strategies may return empty and rely on caller fallback, but for this
|
|
// representative input we expect at least one chunk.
|
|
REQUIRE_FALSE(chunks.empty());
|
|
|
|
for (const auto& c : chunks) {
|
|
CHECK(c.strategy_used == tc.expected);
|
|
CHECK_FALSE(c.content.empty());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ConfigResolver::resolveEmbeddingChunkingPolicy reads from config file",
|
|
"[daemon][components][config][chunking][catch2]") {
|
|
ConfigResolverFixture fx;
|
|
|
|
auto configPath = fx.writeToml("config.toml",
|
|
R"TOML(
|
|
[embeddings.chunking]
|
|
strategy = "fixed"
|
|
target = 256
|
|
min = 64
|
|
max = 512
|
|
overlap = 0
|
|
use_tokens = 0
|
|
preserve_sentences = 0
|
|
)TOML");
|
|
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
auto policy = ConfigResolver::resolveEmbeddingChunkingPolicy();
|
|
CHECK(policy.strategy == yams::vector::ChunkingStrategy::FIXED_SIZE);
|
|
CHECK(policy.config.target_chunk_size == 256);
|
|
CHECK(policy.config.min_chunk_size == 64);
|
|
CHECK(policy.config.max_chunk_size == 512);
|
|
CHECK(policy.config.overlap_size == 0);
|
|
CHECK(policy.config.overlap_percentage == 0.0);
|
|
CHECK_FALSE(policy.config.use_token_count);
|
|
CHECK_FALSE(policy.config.preserve_sentences);
|
|
}
|
|
|
|
TEST_CASE("ConfigResolver::envTruthy correctly parses truthy values",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("truthy values") {
|
|
CHECK(ConfigResolver::envTruthy("1"));
|
|
CHECK(ConfigResolver::envTruthy("true"));
|
|
CHECK(ConfigResolver::envTruthy("TRUE"));
|
|
CHECK(ConfigResolver::envTruthy("True"));
|
|
CHECK(ConfigResolver::envTruthy("yes"));
|
|
CHECK(ConfigResolver::envTruthy("YES"));
|
|
CHECK(ConfigResolver::envTruthy("Yes"));
|
|
CHECK(ConfigResolver::envTruthy("on"));
|
|
CHECK(ConfigResolver::envTruthy("ON"));
|
|
CHECK(ConfigResolver::envTruthy("On"));
|
|
}
|
|
|
|
SECTION("falsy values") {
|
|
CHECK_FALSE(ConfigResolver::envTruthy("0"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("false"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("FALSE"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("no"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("NO"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("off"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy("OFF"));
|
|
CHECK_FALSE(ConfigResolver::envTruthy(""));
|
|
CHECK_FALSE(ConfigResolver::envTruthy(nullptr));
|
|
}
|
|
|
|
SECTION("unrecognized values are truthy (not in falsey list)") {
|
|
// The implementation treats anything NOT in {0, false, off, no} as truthy
|
|
CHECK(ConfigResolver::envTruthy("garbage"));
|
|
CHECK(ConfigResolver::envTruthy("maybe"));
|
|
CHECK(ConfigResolver::envTruthy("123"));
|
|
CHECK(ConfigResolver::envTruthy("yep"));
|
|
CHECK(ConfigResolver::envTruthy("nope"));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture, "ConfigResolver parseSimpleTomlFlat parses TOML files",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("basic TOML parsing with sections") {
|
|
auto configPath = writeToml("test.toml", R"(
|
|
[daemon]
|
|
socket_path = "/tmp/test.sock"
|
|
log_level = "debug"
|
|
)");
|
|
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["daemon.socket_path"] == "/tmp/test.sock");
|
|
CHECK(config["daemon.log_level"] == "debug");
|
|
}
|
|
|
|
SECTION("missing file returns empty map") {
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(tempDir / "nonexistent.toml");
|
|
CHECK(config.empty());
|
|
}
|
|
|
|
SECTION("empty file returns empty map") {
|
|
auto configPath = writeToml("empty.toml", "");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config.empty());
|
|
}
|
|
|
|
SECTION("comments are ignored") {
|
|
auto configPath = writeToml("comments.toml", R"(
|
|
# This is a comment
|
|
[section]
|
|
# Another comment
|
|
key = "value"
|
|
)");
|
|
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["section.key"] == "value");
|
|
CHECK(config.size() == 1);
|
|
}
|
|
|
|
SECTION("multiple sections parsed correctly") {
|
|
auto configPath = writeToml("multi.toml", R"(
|
|
[section1]
|
|
key1 = "value1"
|
|
|
|
[section2]
|
|
key2 = "value2"
|
|
)");
|
|
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["section1.key1"] == "value1");
|
|
CHECK(config["section2.key2"] == "value2");
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver resolveDefaultConfigPath searches standard paths",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("YAMS_CONFIG_PATH env var takes precedence") {
|
|
auto configPath = writeToml("custom.toml", "[daemon]\nlog_level = \"trace\"\n");
|
|
EnvGuard guard("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
auto resolved = ConfigResolver::resolveDefaultConfigPath();
|
|
CHECK(resolved == configPath);
|
|
}
|
|
|
|
SECTION("returns empty path if no config found") {
|
|
// Clear env var to test fallback
|
|
EnvGuard guard("YAMS_CONFIG_PATH", "");
|
|
// Note: this test may find system config, so we just check it returns a valid result
|
|
auto resolved = ConfigResolver::resolveDefaultConfigPath();
|
|
// Either found or empty - both are valid outcomes
|
|
CHECK((resolved.empty() || std::filesystem::exists(resolved) || !resolved.empty()));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver resolveEmbeddingBackend defaults to simeon for new installs",
|
|
"[daemon][components][config][catch2]") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "");
|
|
auto configPath = writeToml("embedding_backend_default.toml", R"(
|
|
[core]
|
|
data_dir = "~/.yams/data"
|
|
)");
|
|
EnvGuard configGuard("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "simeon");
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend("auto") == "auto");
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver resolveEmbeddingBackend reads and normalizes configured backend",
|
|
"[daemon][components][config][catch2]") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "");
|
|
|
|
SECTION("reads daemon from config") {
|
|
auto configPath = writeToml("embedding_backend_daemon.toml", R"(
|
|
[embeddings]
|
|
backend = "daemon"
|
|
)");
|
|
EnvGuard configGuard("YAMS_CONFIG_PATH", configPath.string());
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "daemon");
|
|
}
|
|
|
|
SECTION("normalizes uppercase simeon from config") {
|
|
auto configPath = writeToml("embedding_backend_simeon_upper.toml", R"(
|
|
[embeddings]
|
|
backend = "SIMEON"
|
|
)");
|
|
EnvGuard configGuard("YAMS_CONFIG_PATH", configPath.string());
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "simeon");
|
|
}
|
|
|
|
SECTION("maps legacy onnx backend names to onnxruntime") {
|
|
auto configPath = writeToml("embedding_backend_legacy_onnx.toml", R"(
|
|
[embeddings]
|
|
backend = "local_onnx"
|
|
)");
|
|
EnvGuard configGuard("YAMS_CONFIG_PATH", configPath.string());
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "onnxruntime");
|
|
}
|
|
|
|
SECTION("normalizes explicit onnxruntime from config") {
|
|
auto configPath = writeToml("embedding_backend_onnxruntime.toml", R"(
|
|
[embeddings]
|
|
backend = "ONNX-RUNTIME"
|
|
)");
|
|
EnvGuard configGuard("YAMS_CONFIG_PATH", configPath.string());
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "onnxruntime");
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver resolveEmbeddingBackend maps ONNX env aliases to onnxruntime",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("maps onnx env alias") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "onnx");
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "onnxruntime");
|
|
}
|
|
|
|
SECTION("maps local_onnx env alias") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "local_onnx");
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "onnxruntime");
|
|
}
|
|
|
|
SECTION("maps ort env alias") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "ort");
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "onnxruntime");
|
|
}
|
|
|
|
SECTION("keeps daemon env selection distinct") {
|
|
EnvGuard backendGuard("YAMS_EMBED_BACKEND", "daemon");
|
|
CHECK(ConfigResolver::resolveEmbeddingBackend() == "daemon");
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveTopologyTunerPolicy defaults are empty when missing",
|
|
"[daemon][components][config][topology_tuner][catch2]") {
|
|
auto configPath = writeToml("config.toml", R"TOML(
|
|
[daemon]
|
|
socket_path = "/tmp/test.sock"
|
|
)TOML");
|
|
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto policy = ConfigResolver::resolveTopologyTunerPolicy();
|
|
|
|
CHECK_FALSE(policy.enabled.has_value());
|
|
CHECK_FALSE(policy.cooldownMinutes.has_value());
|
|
CHECK_FALSE(policy.docCountDelta.has_value());
|
|
CHECK_FALSE(policy.rewardAlphaSingleton.has_value());
|
|
CHECK_FALSE(policy.rewardBetaGiantCluster.has_value());
|
|
CHECK_FALSE(policy.rewardGammaGiniDeviation.has_value());
|
|
CHECK_FALSE(policy.rewardDeltaIntraEdge.has_value());
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveEmbeddingSelectionPolicy reads semantic graph ingest flag",
|
|
"[daemon][components][config][embeddings][catch2]") {
|
|
auto configPath = writeToml("config.toml", R"TOML(
|
|
[embeddings.selection]
|
|
update_semantic_graph_during_ingest = false
|
|
)TOML");
|
|
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto policy = ConfigResolver::resolveEmbeddingSelectionPolicy();
|
|
|
|
CHECK(policy.updateSemanticGraphDuringIngest == false);
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveTopologyTunerPolicy reads populated TOML block",
|
|
"[daemon][components][config][topology_tuner][catch2]") {
|
|
auto configPath = writeToml("config.toml", R"TOML(
|
|
[topology.tuner]
|
|
enabled = true
|
|
cooldown_minutes = 30
|
|
doc_count_delta = 250
|
|
|
|
[topology.tuner.reward]
|
|
alpha_singleton = 0.5
|
|
beta_giant_cluster = 0.3
|
|
gamma_gini_deviation = 0.15
|
|
delta_intra_edge = 0.05
|
|
)TOML");
|
|
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto policy = ConfigResolver::resolveTopologyTunerPolicy();
|
|
|
|
REQUIRE(policy.enabled.has_value());
|
|
CHECK(*policy.enabled == true);
|
|
REQUIRE(policy.cooldownMinutes.has_value());
|
|
CHECK(*policy.cooldownMinutes == 30u);
|
|
REQUIRE(policy.docCountDelta.has_value());
|
|
CHECK(*policy.docCountDelta == 250u);
|
|
REQUIRE(policy.rewardAlphaSingleton.has_value());
|
|
CHECK(*policy.rewardAlphaSingleton == 0.5);
|
|
REQUIRE(policy.rewardBetaGiantCluster.has_value());
|
|
CHECK(*policy.rewardBetaGiantCluster == 0.3);
|
|
REQUIRE(policy.rewardGammaGiniDeviation.has_value());
|
|
CHECK(*policy.rewardGammaGiniDeviation == 0.15);
|
|
REQUIRE(policy.rewardDeltaIntraEdge.has_value());
|
|
CHECK(*policy.rewardDeltaIntraEdge == 0.05);
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveTopologyTunerPolicy honors disabled flag explicitly",
|
|
"[daemon][components][config][topology_tuner][catch2]") {
|
|
auto configPath = writeToml("config.toml", R"TOML(
|
|
[topology.tuner]
|
|
enabled = false
|
|
)TOML");
|
|
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto policy = ConfigResolver::resolveTopologyTunerPolicy();
|
|
|
|
REQUIRE(policy.enabled.has_value());
|
|
CHECK(*policy.enabled == false);
|
|
}
|
|
|
|
TEST_CASE("ConfigResolver vector sentinel operations",
|
|
"[daemon][components][config][catch2][sentinel]") {
|
|
auto tempDir = std::filesystem::temp_directory_path() /
|
|
("yams_sentinel_test_" +
|
|
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
|
std::filesystem::create_directories(tempDir);
|
|
|
|
SECTION("readVectorSentinelDim returns nullopt for missing file") {
|
|
auto dim = ConfigResolver::readVectorSentinelDim(tempDir);
|
|
CHECK_FALSE(dim.has_value());
|
|
}
|
|
|
|
SECTION("writeVectorSentinel then readVectorSentinelDim round-trips") {
|
|
ConfigResolver::writeVectorSentinel(tempDir, 384, "test_table", 1);
|
|
auto dim = ConfigResolver::readVectorSentinelDim(tempDir);
|
|
REQUIRE(dim.has_value());
|
|
CHECK(dim.value() == 384);
|
|
}
|
|
|
|
SECTION("different dimensions are preserved") {
|
|
for (size_t testDim : {128, 256, 384, 768, 1024}) {
|
|
ConfigResolver::writeVectorSentinel(tempDir, testDim, "test", 1);
|
|
auto dim = ConfigResolver::readVectorSentinelDim(tempDir);
|
|
REQUIRE(dim.has_value());
|
|
CHECK(dim.value() == testDim);
|
|
}
|
|
}
|
|
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(tempDir, ec);
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture, "ConfigResolver parses [tuning] section",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("efficient profile parses correctly") {
|
|
auto configPath = writeToml("efficient.toml", R"(
|
|
[tuning]
|
|
profile = "efficient"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "efficient");
|
|
}
|
|
|
|
SECTION("balanced profile parses correctly") {
|
|
auto configPath = writeToml("balanced.toml", R"(
|
|
[tuning]
|
|
profile = "balanced"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "balanced");
|
|
}
|
|
|
|
SECTION("aggressive profile parses correctly") {
|
|
auto configPath = writeToml("aggressive.toml", R"(
|
|
[tuning]
|
|
profile = "aggressive"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "aggressive");
|
|
}
|
|
|
|
SECTION("conservative alias for efficient") {
|
|
auto configPath = writeToml("conservative.toml", R"(
|
|
[tuning]
|
|
profile = "conservative"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "conservative");
|
|
}
|
|
|
|
SECTION("case-insensitive profile names") {
|
|
auto configPath = writeToml("case_test.toml", R"(
|
|
[tuning]
|
|
profile = "EFFICIENT"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "EFFICIENT");
|
|
}
|
|
|
|
SECTION("missing tuning section returns empty") {
|
|
auto configPath = writeToml("no_tuning.toml", R"(
|
|
[daemon]
|
|
socket_path = "/tmp/test.sock"
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config.find("tuning.profile") == config.end());
|
|
}
|
|
|
|
SECTION("multiple tuning options parsed together") {
|
|
auto configPath = writeToml("multi_tuning.toml", R"(
|
|
[tuning]
|
|
profile = "aggressive"
|
|
pool_cooldown_ms = 250
|
|
worker_poll_ms = 100
|
|
)");
|
|
auto config = ConfigResolver::parseSimpleTomlFlat(configPath);
|
|
CHECK(config["tuning.profile"] == "aggressive");
|
|
CHECK(config["tuning.pool_cooldown_ms"] == "250");
|
|
CHECK(config["tuning.worker_poll_ms"] == "100");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Tuning profile from config affects TuneAdvisor methods",
|
|
"[daemon][components][config][catch2]") {
|
|
SECTION("efficient profile scales post-ingest concurrency down") {
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Efficient);
|
|
EnvGuard maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
EnvGuard postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
yams::daemon::TuneAdvisor::setHardwareConcurrencyForTests(8);
|
|
|
|
// Efficient profile scale is 0.0, old totalBudget = 2
|
|
// With active-stage floor: total = max(2, 6 active stages) = 6
|
|
// Each stage gets 1 slot; postIngestBatchSize = max(1, floor(8.0 * 0.0)) = 1
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postIngestBatchSize() == 1u);
|
|
}
|
|
|
|
SECTION("balanced profile uses medium values") {
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Balanced);
|
|
EnvGuard maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
EnvGuard postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
yams::daemon::TuneAdvisor::setHardwareConcurrencyForTests(8);
|
|
|
|
// Balanced profile scale is 0.5, old totalBudget = 2
|
|
// With active-stage floor: total = max(2, 6 active stages) = 6
|
|
// Each stage gets 1 slot; postIngestBatchSize = max(1, floor(8.0 * 0.5)) = 4
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postIngestBatchSize() == 4u);
|
|
}
|
|
|
|
SECTION("aggressive profile uses maximum values") {
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Aggressive);
|
|
EnvGuard maxThreadsGuard("YAMS_MAX_THREADS", "0");
|
|
EnvGuard postIngestGuard("YAMS_POST_INGEST_TOTAL_CONCURRENT", "0");
|
|
yams::daemon::TuneAdvisor::setHardwareConcurrencyForTests(8);
|
|
|
|
// Aggressive profile scale is 1.0, old totalBudget = 3
|
|
// With active-stage floor: total = max(3, 6 active stages) = 6
|
|
// Each stage gets 1 slot; postIngestBatchSize = max(1, floor(8.0 * 1.0)) = 8
|
|
CHECK(TuneAdvisor::postExtractionConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postKgConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postSymbolConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEntityConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postTitleConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postEmbedConcurrent() == 1u);
|
|
CHECK(TuneAdvisor::postIngestBatchSize() == 8u);
|
|
}
|
|
|
|
SECTION("profile affects cpuBudgetPercent") {
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Efficient);
|
|
CHECK(TuneAdvisor::cpuBudgetPercent() == 40u);
|
|
}
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Balanced);
|
|
CHECK(TuneAdvisor::cpuBudgetPercent() == 50u);
|
|
}
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Aggressive);
|
|
// 40.0 + 1.0 * 20.0 = 60
|
|
CHECK(TuneAdvisor::cpuBudgetPercent() == 60u);
|
|
}
|
|
}
|
|
|
|
SECTION("profile affects poolCooldownMs") {
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Efficient);
|
|
CHECK(TuneAdvisor::poolCooldownMs() == 750u);
|
|
}
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Balanced);
|
|
CHECK(TuneAdvisor::poolCooldownMs() == 500u);
|
|
}
|
|
{
|
|
ProfileGuard guard(yams::daemon::TuneAdvisor::Profile::Aggressive);
|
|
CHECK(TuneAdvisor::poolCooldownMs() == 250u);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("YAMS_TUNING_PROFILE env var overrides config", "[daemon][components][config][catch2]") {
|
|
auto clearProfileOverride = []() {
|
|
yams::daemon::TuneAdvisor::tuningProfileOverride_.store(0, std::memory_order_relaxed);
|
|
};
|
|
|
|
SECTION("efficient profile from env var") {
|
|
clearProfileOverride();
|
|
EnvGuard envGuard("YAMS_TUNING_PROFILE", "efficient");
|
|
|
|
auto profile = yams::daemon::TuneAdvisor::tuningProfile();
|
|
CHECK(profile == yams::daemon::TuneAdvisor::Profile::Efficient);
|
|
}
|
|
|
|
SECTION("aggressive profile from env var") {
|
|
clearProfileOverride();
|
|
EnvGuard envGuard("YAMS_TUNING_PROFILE", "aggressive");
|
|
|
|
auto profile = yams::daemon::TuneAdvisor::tuningProfile();
|
|
CHECK(profile == yams::daemon::TuneAdvisor::Profile::Aggressive);
|
|
}
|
|
|
|
SECTION("conservative alias maps to efficient") {
|
|
clearProfileOverride();
|
|
EnvGuard envGuard("YAMS_TUNING_PROFILE", "conservative");
|
|
|
|
auto profile = yams::daemon::TuneAdvisor::Profile::Efficient;
|
|
CHECK(yams::daemon::TuneAdvisor::tuningProfile() == profile);
|
|
}
|
|
|
|
SECTION("invalid env var falls back to balanced") {
|
|
clearProfileOverride();
|
|
EnvGuard envGuard("YAMS_TUNING_PROFILE", "invalid_profile");
|
|
|
|
auto profile = yams::daemon::TuneAdvisor::tuningProfile();
|
|
CHECK(profile == yams::daemon::TuneAdvisor::Profile::Balanced);
|
|
}
|
|
|
|
SECTION("empty env var falls back to balanced") {
|
|
clearProfileOverride();
|
|
EnvGuard envGuard("YAMS_TUNING_PROFILE", "");
|
|
|
|
auto profile = yams::daemon::TuneAdvisor::tuningProfile();
|
|
CHECK(profile == yams::daemon::TuneAdvisor::Profile::Balanced);
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolvePostIngestCaps reads [tuning.post_ingest]",
|
|
"[daemon][components][config][post_ingest][catch2]") {
|
|
SECTION("missing section returns all nullopt") {
|
|
auto configPath = writeToml("no_post_ingest.toml", R"(
|
|
[tuning]
|
|
profile = "balanced"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto caps = ConfigResolver::resolvePostIngestCaps();
|
|
CHECK_FALSE(caps.totalConcurrent.has_value());
|
|
CHECK_FALSE(caps.embedConcurrent.has_value());
|
|
CHECK_FALSE(caps.extractionConcurrent.has_value());
|
|
CHECK_FALSE(caps.kgConcurrent.has_value());
|
|
CHECK_FALSE(caps.symbolConcurrent.has_value());
|
|
CHECK_FALSE(caps.entityConcurrent.has_value());
|
|
CHECK_FALSE(caps.titleConcurrent.has_value());
|
|
}
|
|
|
|
SECTION("all keys populate the struct") {
|
|
auto configPath = writeToml("post_ingest.toml", R"(
|
|
[tuning.post_ingest]
|
|
total_concurrent = 12
|
|
embed_concurrent = 4
|
|
extraction_concurrent = 5
|
|
kg_concurrent = 6
|
|
symbol_concurrent = 3
|
|
entity_concurrent = 2
|
|
title_concurrent = 2
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto caps = ConfigResolver::resolvePostIngestCaps();
|
|
REQUIRE(caps.totalConcurrent.has_value());
|
|
CHECK(*caps.totalConcurrent == 12u);
|
|
REQUIRE(caps.embedConcurrent.has_value());
|
|
CHECK(*caps.embedConcurrent == 4u);
|
|
REQUIRE(caps.extractionConcurrent.has_value());
|
|
CHECK(*caps.extractionConcurrent == 5u);
|
|
REQUIRE(caps.kgConcurrent.has_value());
|
|
CHECK(*caps.kgConcurrent == 6u);
|
|
REQUIRE(caps.symbolConcurrent.has_value());
|
|
CHECK(*caps.symbolConcurrent == 3u);
|
|
REQUIRE(caps.entityConcurrent.has_value());
|
|
CHECK(*caps.entityConcurrent == 2u);
|
|
REQUIRE(caps.titleConcurrent.has_value());
|
|
CHECK(*caps.titleConcurrent == 2u);
|
|
}
|
|
|
|
SECTION("out-of-range values are dropped (nullopt)") {
|
|
auto configPath = writeToml("oor.toml", R"(
|
|
[tuning.post_ingest]
|
|
total_concurrent = 0
|
|
embed_concurrent = 99
|
|
extraction_concurrent = 500
|
|
entity_concurrent = 17
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto caps = ConfigResolver::resolvePostIngestCaps();
|
|
CHECK_FALSE(caps.totalConcurrent.has_value());
|
|
CHECK_FALSE(caps.embedConcurrent.has_value());
|
|
CHECK_FALSE(caps.extractionConcurrent.has_value());
|
|
CHECK_FALSE(caps.entityConcurrent.has_value());
|
|
}
|
|
|
|
SECTION("partial population — only set keys populate") {
|
|
auto configPath = writeToml("partial.toml", R"(
|
|
[tuning.post_ingest]
|
|
embed_concurrent = 3
|
|
kg_concurrent = 5
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
auto caps = ConfigResolver::resolvePostIngestCaps();
|
|
CHECK_FALSE(caps.totalConcurrent.has_value());
|
|
REQUIRE(caps.embedConcurrent.has_value());
|
|
CHECK(*caps.embedConcurrent == 3u);
|
|
CHECK_FALSE(caps.extractionConcurrent.has_value());
|
|
REQUIRE(caps.kgConcurrent.has_value());
|
|
CHECK(*caps.kgConcurrent == 5u);
|
|
CHECK_FALSE(caps.symbolConcurrent.has_value());
|
|
CHECK_FALSE(caps.entityConcurrent.has_value());
|
|
CHECK_FALSE(caps.titleConcurrent.has_value());
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveSimeonEncoderPolicy reads [embeddings.simeon]",
|
|
"[daemon][components][config][simeon][catch2]") {
|
|
SECTION("missing section returns all nullopt") {
|
|
auto configPath = writeToml("no_simeon.toml", R"(
|
|
[daemon]
|
|
log_level = "info"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
for (auto* env :
|
|
{"YAMS_SIMEON_NGRAM_MODE", "YAMS_SIMEON_NGRAM_MIN", "YAMS_SIMEON_NGRAM_MAX",
|
|
"YAMS_SIMEON_SKETCH_DIM", "YAMS_SIMEON_OUTPUT_DIM", "YAMS_SIMEON_PROJECTION",
|
|
"YAMS_SIMEON_PQ_BYTES"}) {
|
|
unsetenv(env);
|
|
}
|
|
auto policy = ConfigResolver::resolveSimeonEncoderPolicy();
|
|
CHECK_FALSE(policy.ngramMode.has_value());
|
|
CHECK_FALSE(policy.ngramMin.has_value());
|
|
CHECK_FALSE(policy.ngramMax.has_value());
|
|
CHECK_FALSE(policy.sketchDim.has_value());
|
|
CHECK_FALSE(policy.outputDim.has_value());
|
|
CHECK_FALSE(policy.projection.has_value());
|
|
CHECK_FALSE(policy.l2Normalize.has_value());
|
|
CHECK_FALSE(policy.pqBytes.has_value());
|
|
}
|
|
|
|
SECTION("populated TOML section fills every field") {
|
|
auto configPath = writeToml("simeon_encoder.toml", R"(
|
|
[embeddings.simeon]
|
|
ngram_mode = "char_and_word"
|
|
ngram_min = 3
|
|
ngram_max = 5
|
|
sketch_dim = 4096
|
|
output_dim = 384
|
|
projection = "fwht"
|
|
l2_normalize = true
|
|
pq_bytes = 64
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
for (auto* env :
|
|
{"YAMS_SIMEON_NGRAM_MODE", "YAMS_SIMEON_NGRAM_MIN", "YAMS_SIMEON_NGRAM_MAX",
|
|
"YAMS_SIMEON_SKETCH_DIM", "YAMS_SIMEON_OUTPUT_DIM", "YAMS_SIMEON_PROJECTION",
|
|
"YAMS_SIMEON_PQ_BYTES"}) {
|
|
unsetenv(env);
|
|
}
|
|
auto policy = ConfigResolver::resolveSimeonEncoderPolicy();
|
|
REQUIRE(policy.ngramMode.has_value());
|
|
CHECK(*policy.ngramMode == "char_and_word");
|
|
REQUIRE(policy.ngramMin.has_value());
|
|
CHECK(*policy.ngramMin == 3u);
|
|
REQUIRE(policy.ngramMax.has_value());
|
|
CHECK(*policy.ngramMax == 5u);
|
|
REQUIRE(policy.sketchDim.has_value());
|
|
CHECK(*policy.sketchDim == 4096u);
|
|
REQUIRE(policy.outputDim.has_value());
|
|
CHECK(*policy.outputDim == 384u);
|
|
REQUIRE(policy.projection.has_value());
|
|
CHECK(*policy.projection == "fwht");
|
|
REQUIRE(policy.l2Normalize.has_value());
|
|
CHECK(*policy.l2Normalize);
|
|
REQUIRE(policy.pqBytes.has_value());
|
|
CHECK(*policy.pqBytes == 64u);
|
|
}
|
|
|
|
SECTION("env vars override TOML values") {
|
|
auto configPath = writeToml("override_encoder.toml", R"(
|
|
[embeddings.simeon]
|
|
projection = "achlioptas_sparse"
|
|
sketch_dim = 2048
|
|
output_dim = 256
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
EnvGuard proj("YAMS_SIMEON_PROJECTION", "fwht");
|
|
EnvGuard sketch("YAMS_SIMEON_SKETCH_DIM", "8192");
|
|
EnvGuard out("YAMS_SIMEON_OUTPUT_DIM", "1024");
|
|
|
|
auto policy = ConfigResolver::resolveSimeonEncoderPolicy();
|
|
REQUIRE(policy.projection.has_value());
|
|
CHECK(*policy.projection == "fwht");
|
|
REQUIRE(policy.sketchDim.has_value());
|
|
CHECK(*policy.sketchDim == 8192u);
|
|
REQUIRE(policy.outputDim.has_value());
|
|
CHECK(*policy.outputDim == 1024u);
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveSimeonBm25Policy reads [embeddings.simeon.bm25]",
|
|
"[daemon][components][config][simeon][catch2]") {
|
|
SECTION("missing section returns all nullopt") {
|
|
auto configPath = writeToml("no_bm25.toml", R"(
|
|
[daemon]
|
|
log_level = "info"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
for (auto* env : {"YAMS_SIMEON_BM25_ENABLED", "YAMS_SIMEON_BM25_VARIANT",
|
|
"YAMS_SIMEON_BM25_SUBWORD_GAMMA", "YAMS_SIMEON_BM25_MAX_CORPUS_DOCS"}) {
|
|
unsetenv(env);
|
|
}
|
|
auto policy = ConfigResolver::resolveSimeonBm25Policy();
|
|
CHECK_FALSE(policy.enabled.has_value());
|
|
CHECK_FALSE(policy.variant.has_value());
|
|
CHECK_FALSE(policy.subwordGamma.has_value());
|
|
CHECK_FALSE(policy.maxCorpusDocs.has_value());
|
|
}
|
|
|
|
SECTION("populated TOML section fills every field") {
|
|
auto configPath = writeToml("bm25.toml", R"(
|
|
[embeddings.simeon.bm25]
|
|
enabled = true
|
|
variant = "sab_smooth"
|
|
subword_gamma = 5.0
|
|
max_corpus_docs = 200000
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
for (auto* env : {"YAMS_SIMEON_BM25_ENABLED", "YAMS_SIMEON_BM25_VARIANT",
|
|
"YAMS_SIMEON_BM25_SUBWORD_GAMMA", "YAMS_SIMEON_BM25_MAX_CORPUS_DOCS"}) {
|
|
unsetenv(env);
|
|
}
|
|
auto policy = ConfigResolver::resolveSimeonBm25Policy();
|
|
REQUIRE(policy.enabled.has_value());
|
|
CHECK(*policy.enabled);
|
|
REQUIRE(policy.variant.has_value());
|
|
CHECK(*policy.variant == "sab_smooth");
|
|
REQUIRE(policy.subwordGamma.has_value());
|
|
CHECK(*policy.subwordGamma == 5.0f);
|
|
REQUIRE(policy.maxCorpusDocs.has_value());
|
|
CHECK(*policy.maxCorpusDocs == 200000u);
|
|
}
|
|
|
|
SECTION("env vars override TOML values") {
|
|
auto configPath = writeToml("override_bm25.toml", R"(
|
|
[embeddings.simeon.bm25]
|
|
variant = "atire"
|
|
subword_gamma = 3.0
|
|
max_corpus_docs = 50000
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
EnvGuard variantEnv("YAMS_SIMEON_BM25_VARIANT", "sab_smooth");
|
|
EnvGuard gammaEnv("YAMS_SIMEON_BM25_SUBWORD_GAMMA", "7.5");
|
|
EnvGuard capEnv("YAMS_SIMEON_BM25_MAX_CORPUS_DOCS", "123456");
|
|
|
|
auto policy = ConfigResolver::resolveSimeonBm25Policy();
|
|
REQUIRE(policy.variant.has_value());
|
|
CHECK(*policy.variant == "sab_smooth");
|
|
REQUIRE(policy.subwordGamma.has_value());
|
|
CHECK(*policy.subwordGamma == 7.5f);
|
|
REQUIRE(policy.maxCorpusDocs.has_value());
|
|
CHECK(*policy.maxCorpusDocs == 123456u);
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveRerankerBackendPolicy reads [search]",
|
|
"[daemon][components][config][reranker][catch2]") {
|
|
SECTION("returns unset when not configured") {
|
|
auto configPath = writeToml("reranker_default.toml", R"(
|
|
[search]
|
|
default_limit = 10
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
auto policy = ConfigResolver::resolveRerankerBackendPolicy();
|
|
CHECK_FALSE(policy.backend.has_value());
|
|
}
|
|
|
|
SECTION("reads explicit reranker backend") {
|
|
auto configPath = writeToml("reranker_backend.toml", R"(
|
|
[search]
|
|
reranker_backend = "colbert"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
auto policy = ConfigResolver::resolveRerankerBackendPolicy();
|
|
REQUIRE(policy.backend.has_value());
|
|
CHECK(*policy.backend == "colbert");
|
|
}
|
|
|
|
SECTION("normalizes reranker backend to lowercase") {
|
|
auto configPath = writeToml("reranker_backend_upper.toml", R"(
|
|
[search]
|
|
reranker_backend = "ONNX"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
|
|
auto policy = ConfigResolver::resolveRerankerBackendPolicy();
|
|
REQUIRE(policy.backend.has_value());
|
|
CHECK(*policy.backend == "onnx");
|
|
}
|
|
}
|
|
|
|
TEST_CASE_METHOD(ConfigResolverFixture,
|
|
"ConfigResolver::resolveInstrumentationPolicy applies memory profile",
|
|
"[daemon][components][config][instrumentation][catch2]") {
|
|
SECTION("auto profile activates when MallocStackLoggingNoCompact is present") {
|
|
auto configPath = writeToml("instrumentation_auto.toml", R"(
|
|
[daemon]
|
|
log_level = "info"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
EnvGuard msl("MallocStackLoggingNoCompact", "1");
|
|
EnvGuard mslCompact("MallocStackLogging", "0");
|
|
|
|
auto policy = ConfigResolver::resolveInstrumentationPolicy();
|
|
CHECK(policy.profile == "auto");
|
|
CHECK(policy.memoryProfileActive);
|
|
CHECK(policy.suppressAutoRepair);
|
|
CHECK(policy.suppressSimeonLexicalBuild);
|
|
CHECK(policy.suppressVectorIndexBuild);
|
|
}
|
|
|
|
SECTION("normal profile disables auto MSL suppression") {
|
|
auto configPath = writeToml("instrumentation_normal.toml", R"(
|
|
[daemon.instrumentation]
|
|
profile = "normal"
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
EnvGuard msl("MallocStackLoggingNoCompact", "1");
|
|
|
|
auto policy = ConfigResolver::resolveInstrumentationPolicy();
|
|
CHECK(policy.profile == "normal");
|
|
CHECK_FALSE(policy.memoryProfileActive);
|
|
CHECK_FALSE(policy.suppressAutoRepair);
|
|
CHECK_FALSE(policy.suppressSimeonLexicalBuild);
|
|
CHECK_FALSE(policy.suppressVectorIndexBuild);
|
|
}
|
|
|
|
SECTION("memory profile supports explicit overrides and stack-log threshold") {
|
|
auto configPath = writeToml("instrumentation_memory.toml", R"(
|
|
[daemon.instrumentation]
|
|
profile = "memory"
|
|
suppress_auto_repair = false
|
|
suppress_simeon_lexical_build = true
|
|
suppress_vector_index_build = true
|
|
msl_stack_log_warn_mb = 1536
|
|
)");
|
|
EnvGuard cfg("YAMS_CONFIG_PATH", configPath.string());
|
|
EnvGuard msl("MallocStackLogging", "0");
|
|
EnvGuard mslCompact("MallocStackLoggingNoCompact", "0");
|
|
|
|
auto policy = ConfigResolver::resolveInstrumentationPolicy();
|
|
CHECK(policy.profile == "memory");
|
|
CHECK(policy.memoryProfileActive);
|
|
CHECK_FALSE(policy.suppressAutoRepair);
|
|
CHECK(policy.suppressSimeonLexicalBuild);
|
|
CHECK(policy.suppressVectorIndexBuild);
|
|
CHECK(policy.mslStackLogWarnBytes == 1536ULL * 1024ULL * 1024ULL);
|
|
}
|
|
}
|