yams/tests/unit/compression/compression_policy_catch2_test.cpp
trvon b2ede10e54
fix: stabilize CI release gates
Fix DB vacuum, compression gates, fuzzy search, temp dirs.
   Repair release packaging: Bash 3.2, Arch containers,
   package validation defaults, artifact names, matrix metadata.

   Local tests + release act lane pass.
2026-07-03 22:40:05 -04:00

298 lines
12 KiB
C++

// Catch2 tests for Compression Policy
// Migrated from GTest: compression_policy_test.cpp
#include <catch2/catch_test_macros.hpp>
#include <atomic>
#include <chrono>
#include <limits>
#include <thread>
#include <vector>
#include <yams/api/content_metadata.h>
#include <yams/compression/compression_policy.h>
using namespace yams;
using namespace yams::compression;
using namespace yams::api;
namespace {
void disableLiveResourceGates(CompressionPolicy::Rules& rules) {
rules.enforceCpuUsageLimit = false;
rules.enforceConcurrentCompressionLimit = false;
rules.enforceFreeSpaceLimit = false;
}
struct CompressionPolicyFixture {
CompressionPolicyFixture() : policy_(std::make_unique<CompressionPolicy>()) {
auto rules = policy_->rules();
disableLiveResourceGates(rules);
policy_->updateRules(rules);
}
// Helper to create test metadata
ContentMetadata createMetadata(const std::string& name, uint64_t size,
const std::string& mimeType = "text/plain") {
ContentMetadata metadata;
metadata.id = "test-" + name;
metadata.name = name;
metadata.size = size;
metadata.mimeType = mimeType;
metadata.contentHash = std::string("0123456789abcdef");
metadata.createdAt = std::chrono::system_clock::now();
metadata.modifiedAt = metadata.createdAt;
metadata.accessedAt = metadata.createdAt;
return metadata;
}
// Helper to create access pattern
AccessPattern createAccessPattern(std::chrono::hours ageHours, size_t accessCount = 10) {
auto now = std::chrono::system_clock::now();
AccessPattern pattern;
pattern.created = now - ageHours;
pattern.lastAccessed = now - ageHours;
pattern.accessCount = accessCount;
pattern.readCount = accessCount;
return pattern;
}
std::unique_ptr<CompressionPolicy> policy_;
};
} // namespace
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - DefaultRules",
"[compression][policy][catch2]") {
auto rules = policy_->rules();
CHECK((rules.compressAfterAge == std::chrono::hours(24 * 7)));
CHECK((rules.archiveAfterAge == std::chrono::hours(24 * 30)));
CHECK((rules.neverCompressBelow == 4096));
CHECK((rules.alwaysCompressAbove == 10ULL * 1024ULL * 1024ULL));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - TooNewFile",
"[compression][policy][catch2]") {
auto metadata = createMetadata("test.txt", 100000);
auto pattern = createAccessPattern(std::chrono::hours(0)); // Just created
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK_FALSE(decision.shouldCompress);
CHECK((decision.reason.find("File too new") != std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - TooSmallFile",
"[compression][policy][catch2]") {
auto metadata = createMetadata("test.txt", 1000); // < 4KB
auto pattern = createAccessPattern(std::chrono::hours(48));
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK_FALSE(decision.shouldCompress);
CHECK((decision.reason.find("too small") != std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - LargeFileShouldCompress",
"[compression][policy][catch2]") {
auto metadata = createMetadata("large.txt", 20ULL * 1024ULL * 1024ULL); // 20MB
auto pattern = createAccessPattern(std::chrono::hours(48));
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
CHECK((decision.reason.find("Large file") != std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - OldFileShouldUseLZMA",
"[compression][policy][catch2]") {
auto metadata = createMetadata("old.txt", 1024ULL * 1024ULL); // 1MB
auto pattern = createAccessPattern(std::chrono::hours(24 * 35)); // 35 days old
pattern.accessCount = 2; // Rarely accessed
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
CHECK((decision.algorithm == CompressionAlgorithm::LZMA));
CHECK((decision.reason.find("Old file") != std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - ActiveFileShouldUseZstandard",
"[compression][policy][catch2]") {
auto metadata = createMetadata("active.txt", 1024ULL * 1024ULL); // 1MB
auto pattern = createAccessPattern(std::chrono::hours(24 * 3)); // 3 days old
pattern.accessCount = 100; // Frequently accessed
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
CHECK((decision.algorithm == CompressionAlgorithm::Zstandard));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - ExcludedMimeTypes",
"[compression][policy][catch2]") {
auto metadata = createMetadata("photo.jpg", 5ULL * 1024ULL * 1024ULL, "image/jpeg");
auto pattern = createAccessPattern(std::chrono::hours(24 * 10));
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK_FALSE(decision.shouldCompress);
CHECK((decision.reason.find("Content appears to be already compressed") !=
std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - ExcludedExtensions",
"[compression][policy][catch2]") {
auto metadata =
createMetadata("archive.zip", 5ULL * 1024ULL * 1024ULL, "application/octet-stream");
auto pattern = createAccessPattern(std::chrono::hours(24 * 10));
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK_FALSE(decision.shouldCompress);
CHECK((decision.reason.find("Content appears to be already compressed") !=
std::string::npos));
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - CompressibleTypes",
"[compression][policy][catch2]") {
std::vector<std::pair<std::string, std::string>> testCases = {
{"document.txt", "text/plain"}, {"data.json", "application/json"},
{"page.html", "text/html"}, {"style.css", "text/css"},
{"config.xml", "application/xml"}, {"script.js", "application/javascript"}};
for (const auto& [filename, mimeType] : testCases) {
auto metadata = createMetadata(filename, 100ULL * 1024ULL, mimeType);
auto pattern = createAccessPattern(std::chrono::hours(24 * 10));
auto decision = policy_->shouldCompress(metadata, pattern);
INFO("File " << filename << " with type " << mimeType << " should be compressible");
CHECK(decision.shouldCompress);
}
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - FileTemperatureClassification",
"[compression][policy][catch2]") {
auto metadata = createMetadata("test.txt", 1024ULL * 1024ULL);
SECTION("Hot file - many accesses per day") {
auto pattern = createAccessPattern(std::chrono::hours(24 * 5));
pattern.accessCount = 500; // 100 accesses/day
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
CHECK((decision.algorithm == CompressionAlgorithm::Zstandard));
CHECK((decision.level == 1)); // Fast compression for hot files
}
SECTION("Cold file - few accesses") {
auto pattern = createAccessPattern(std::chrono::hours(24 * 20));
pattern.accessCount = 2; // 0.1 accesses/day
auto decision = policy_->shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
CHECK((decision.algorithm == CompressionAlgorithm::LZMA));
}
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - CustomRules",
"[compression][policy][catch2]") {
CompressionPolicy::Rules customRules;
disableLiveResourceGates(customRules);
customRules.compressAfterAge = std::chrono::hours(1);
customRules.neverCompressBelow = 1024; // 1KB
customRules.alwaysCompressAbove = 1024ULL * 1024ULL; // 1MB
customRules.defaultZstdLevel = 5;
customRules.defaultLzmaLevel = 7;
CompressionPolicy customPolicy(customRules);
auto metadata = createMetadata("test.txt", 2048); // 2KB
auto pattern = createAccessPattern(std::chrono::hours(2));
auto decision = customPolicy.shouldCompress(metadata, pattern);
CHECK(decision.shouldCompress);
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - AlgorithmSelection",
"[compression][policy][catch2]") {
auto metadata = createMetadata("test.txt", 100ULL * 1024ULL * 1024ULL); // 100MB
SECTION("Large file with low access should use LZMA") {
auto pattern = createAccessPattern(std::chrono::hours(24 * 100)); // 100 days old
pattern.accessCount = 1; // 0.01 accesses/day (truly cold)
auto algo = policy_->selectAlgorithm(metadata, pattern);
CHECK((algo == CompressionAlgorithm::LZMA));
}
SECTION("Large file with high access should use Zstandard") {
auto pattern = createAccessPattern(std::chrono::hours(24 * 10));
pattern.accessCount = 200; // 20 accesses/day
auto algo = policy_->selectAlgorithm(metadata, pattern);
CHECK((algo == CompressionAlgorithm::Zstandard));
}
}
TEST_CASE_METHOD(CompressionPolicyFixture,
"CompressionPolicy - Resource gates are independently configurable",
"[compression][policy][resources][catch2]") {
CompressionPolicy::Rules rules;
rules.maxCpuUsage = 0.0;
rules.minFreeSpaceBytes = std::numeric_limits<size_t>::max();
rules.maxConcurrentCompressions = 0;
SECTION("default resource gates enforce live limits") {
CompressionPolicy policy(rules);
CHECK_FALSE(policy.hasSystemResources());
}
SECTION("all resource gates can be disabled for intrinsic policy decisions") {
disableLiveResourceGates(rules);
CompressionPolicy policy(rules);
CHECK(policy.hasSystemResources());
}
SECTION("each resource gate remains separate") {
disableLiveResourceGates(rules);
rules.enforceCpuUsageLimit = true;
CompressionPolicy cpuPolicy(rules);
CHECK_FALSE(cpuPolicy.hasSystemResources());
disableLiveResourceGates(rules);
rules.enforceFreeSpaceLimit = true;
CompressionPolicy diskPolicy(rules);
CHECK_FALSE(diskPolicy.hasSystemResources());
disableLiveResourceGates(rules);
rules.enforceConcurrentCompressionLimit = true;
CompressionPolicy concurrencyPolicy(rules);
CHECK_FALSE(concurrencyPolicy.hasSystemResources());
}
}
TEST_CASE_METHOD(CompressionPolicyFixture, "CompressionPolicy - ThreadSafety",
"[compression][policy][concurrent][catch2][.]") {
const int numThreads = 10;
const int numOperations = 1000;
std::vector<std::thread> threads;
std::atomic<int> successCount{0};
auto metadata = createMetadata("test.txt", 1024ULL * 1024ULL);
auto pattern = createAccessPattern(std::chrono::hours(24 * 10));
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back([this, &metadata, &pattern, &successCount]() {
for (int j = 0; j < numOperations; ++j) {
if (j % 10 == 0) {
auto rules = policy_->rules();
rules.defaultZstdLevel = static_cast<uint8_t>((j % 9) + 1);
policy_->updateRules(rules);
} else {
auto decision = policy_->shouldCompress(metadata, pattern);
if (decision.shouldCompress) {
successCount++;
}
}
}
});
}
for (auto& t : threads) {
t.join();
}
CHECK((successCount > 0));
}