#include #include #include #include #include #include "os.h" #include "tlrucache.h" // Test basic LRU cache initialization with different shard configurations TEST(lrucacheTest, init_with_shard_bits) { // Test with auto-calculated shards (-1) SLRUCache* cache1 = taosLRUCacheInit(10 * 1024 * 1024, -1, 0.5); ASSERT_NE(cache1, nullptr); int32_t numShards1 = taosLRUCacheGetNumShards(cache1); EXPECT_GT(numShards1, 0); std::cout << "Auto-calculated shards for 10MB: " << numShards1 << std::endl; taosLRUCacheCleanup(cache1); // Test with explicit shard bits (0 = 1 shard) SLRUCache* cache2 = taosLRUCacheInit(10 * 1024 * 1024, 0, 0.5); ASSERT_NE(cache2, nullptr); int32_t numShards2 = taosLRUCacheGetNumShards(cache2); EXPECT_EQ(numShards2, 1); std::cout << "Explicit 0 bits (1 shard): " << numShards2 << std::endl; taosLRUCacheCleanup(cache2); // Test with 2 shard bits (4 shards) SLRUCache* cache3 = taosLRUCacheInit(10 * 1024 * 1024, 2, 0.5); ASSERT_NE(cache3, nullptr); int32_t numShards3 = taosLRUCacheGetNumShards(cache3); EXPECT_EQ(numShards3, 4); std::cout << "Explicit 2 bits (4 shards): " << numShards3 << std::endl; taosLRUCacheCleanup(cache3); // Test with 4 shard bits (16 shards) SLRUCache* cache4 = taosLRUCacheInit(10 * 1024 * 1024, 4, 0.5); ASSERT_NE(cache4, nullptr); int32_t numShards4 = taosLRUCacheGetNumShards(cache4); EXPECT_EQ(numShards4, 16); std::cout << "Explicit 4 bits (16 shards): " << numShards4 << std::endl; taosLRUCacheCleanup(cache4); } // Test invalid shard bits TEST(lrucacheTest, invalid_shard_bits) { // Test with too many shard bits (>= 20) SLRUCache* cache1 = taosLRUCacheInit(10 * 1024 * 1024, 20, 0.5); EXPECT_EQ(cache1, nullptr); SLRUCache* cache2 = taosLRUCacheInit(10 * 1024 * 1024, 25, 0.5); EXPECT_EQ(cache2, nullptr); } // Test NULL cache handling TEST(lrucacheTest, null_cache_get_shards) { int32_t numShards = taosLRUCacheGetNumShards(nullptr); EXPECT_EQ(numShards, 0); } // Test cache operations with different shard counts TEST(lrucacheTest, operations_with_different_shards) { const int32_t numItems = 1000; // Test with 1 shard SLRUCache* cache1 = taosLRUCacheInit(1 * 1024 * 1024, 0, 0.5); ASSERT_NE(cache1, nullptr); for (int32_t i = 0; i < numItems; i++) { char key[64]; snprintf(key, sizeof(key), "key_%d", i); int32_t value = i; LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache1, key, strlen(key), &value, sizeof(value), nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { ASSERT_NE(handle, nullptr); taosLRUCacheRelease(cache1, handle, false); } } // Verify some items can be retrieved for (int32_t i = 0; i < 10; i++) { char key[64]; snprintf(key, sizeof(key), "key_%d", i); LRUHandle* handle = taosLRUCacheLookup(cache1, key, strlen(key)); if (handle) { taosLRUCacheRelease(cache1, handle, false); } } taosLRUCacheCleanup(cache1); // Test with 16 shards SLRUCache* cache2 = taosLRUCacheInit(1 * 1024 * 1024, 4, 0.5); ASSERT_NE(cache2, nullptr); EXPECT_EQ(taosLRUCacheGetNumShards(cache2), 16); for (int32_t i = 0; i < numItems; i++) { char key[64]; snprintf(key, sizeof(key), "key_%d", i); int32_t value = i; LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache2, key, strlen(key), &value, sizeof(value), nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { ASSERT_NE(handle, nullptr); taosLRUCacheRelease(cache2, handle, false); } } taosLRUCacheCleanup(cache2); } // Test concurrent access with multiple shards TEST(lrucacheTest, concurrent_access_multi_shard) { const int32_t numThreads = 8; const int32_t opsPerThread = 500; SLRUCache* cache = taosLRUCacheInit(10 * 1024 * 1024, 3, 0.5); // 8 shards ASSERT_NE(cache, nullptr); EXPECT_EQ(taosLRUCacheGetNumShards(cache), 8); std::atomic successCount{0}; std::vector threads; for (int32_t t = 0; t < numThreads; t++) { threads.emplace_back([&, t]() { for (int32_t i = 0; i < opsPerThread; i++) { char key[64]; snprintf(key, sizeof(key), "thread_%d_key_%d", t, i); int32_t value = t * 10000 + i; // Insert LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache, key, strlen(key), &value, sizeof(value), nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { if (handle) { taosLRUCacheRelease(cache, handle, false); // Lookup handle = taosLRUCacheLookup(cache, key, strlen(key)); if (handle) { int32_t* pValue = (int32_t*)taosLRUCacheValue(cache, handle); if (pValue && *pValue == value) { successCount++; } taosLRUCacheRelease(cache, handle, false); } } } } }); } for (auto& thread : threads) { thread.join(); } std::cout << "Concurrent operations succeeded: " << successCount.load() << " / " << (numThreads * opsPerThread) << std::endl; EXPECT_GT(successCount.load(), 0); taosLRUCacheCleanup(cache); } // Test capacity and eviction with different shard counts TEST(lrucacheTest, capacity_eviction_with_shards) { const size_t capacity = 1024; // Small capacity to trigger eviction const size_t itemSize = 64; const int32_t numItems = 50; // Test with 1 shard SLRUCache* cache1 = taosLRUCacheInit(capacity, 0, 0.5); ASSERT_NE(cache1, nullptr); int insertCount1 = 0; for (int32_t i = 0; i < numItems; i++) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); char value[64]; memset(value, 'A' + (i % 26), itemSize); LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache1, key, strlen(key), value, itemSize, nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { if (handle) { insertCount1++; taosLRUCacheRelease(cache1, handle, false); } } } size_t usage1 = taosLRUCacheGetUsage(cache1); std::cout << "Single shard - Inserted: " << insertCount1 << ", Usage: " << usage1 << " / " << capacity << std::endl; EXPECT_LE(usage1, capacity); taosLRUCacheCleanup(cache1); // Test with 4 shards SLRUCache* cache2 = taosLRUCacheInit(capacity, 2, 0.5); ASSERT_NE(cache2, nullptr); EXPECT_EQ(taosLRUCacheGetNumShards(cache2), 4); int insertCount2 = 0; for (int32_t i = 0; i < numItems; i++) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); char value[64]; memset(value, 'A' + (i % 26), itemSize); LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache2, key, strlen(key), value, itemSize, nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { if (handle) { insertCount2++; taosLRUCacheRelease(cache2, handle, false); } } } size_t usage2 = taosLRUCacheGetUsage(cache2); std::cout << "4 shards - Inserted: " << insertCount2 << ", Usage: " << usage2 << " / " << capacity << std::endl; EXPECT_LE(usage2, capacity); taosLRUCacheCleanup(cache2); } // Test auto-calculation of shards based on capacity TEST(lrucacheTest, auto_shard_calculation) { struct TestCase { size_t capacity; int32_t expectedMinShards; int32_t expectedMaxShards; }; std::vector testCases = { {256 * 1024, 0, 1}, // 256KB -> 0-1 shards {1 * 1024 * 1024, 1, 2}, // 1MB -> 1-2 shards {10 * 1024 * 1024, 2, 32}, // 10MB -> 2-32 shards {100 * 1024 * 1024, 8, 64}, // 100MB -> 8-64 shards }; for (const auto& tc : testCases) { SLRUCache* cache = taosLRUCacheInit(tc.capacity, -1, 0.5); ASSERT_NE(cache, nullptr); int32_t numShards = taosLRUCacheGetNumShards(cache); std::cout << "Capacity: " << tc.capacity << " bytes -> " << numShards << " shards" << std::endl; EXPECT_GE(numShards, tc.expectedMinShards); EXPECT_LE(numShards, tc.expectedMaxShards); taosLRUCacheCleanup(cache); } } // Test cache erase with multiple shards TEST(lrucacheTest, erase_with_shards) { SLRUCache* cache = taosLRUCacheInit(1 * 1024 * 1024, 2, 0.5); // 4 shards ASSERT_NE(cache, nullptr); const int32_t numItems = 100; // Insert items for (int32_t i = 0; i < numItems; i++) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); int32_t value = i; LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache, key, strlen(key), &value, sizeof(value), nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { ASSERT_NE(handle, nullptr); taosLRUCacheRelease(cache, handle, false); } } // Erase every other item for (int32_t i = 0; i < numItems; i += 2) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); taosLRUCacheErase(cache, key, strlen(key)); } // Verify erased items are gone and others remain int foundCount = 0; for (int32_t i = 0; i < numItems; i++) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); LRUHandle* handle = taosLRUCacheLookup(cache, key, strlen(key)); if (i % 2 == 0) { // Should be erased EXPECT_EQ(handle, nullptr); } else { // Should still exist if (handle) { foundCount++; taosLRUCacheRelease(cache, handle, false); } } } std::cout << "Found " << foundCount << " items after erasing half" << std::endl; taosLRUCacheCleanup(cache); } // Test strict capacity mode with shards TEST(lrucacheTest, strict_capacity_with_shards) { const size_t capacity = 2048; SLRUCache* cache = taosLRUCacheInit(capacity, 2, 0.5); // 4 shards ASSERT_NE(cache, nullptr); taosLRUCacheSetStrictCapacity(cache, true); EXPECT_TRUE(taosLRUCacheIsStrictCapacity(cache)); // Try to insert items const size_t itemSize = 256; int successCount = 0; for (int32_t i = 0; i < 20; i++) { char key[32]; snprintf(key, sizeof(key), "key_%d", i); char value[256]; memset(value, 'X', itemSize); LRUHandle* handle = nullptr; LRUStatus status = taosLRUCacheInsert(cache, key, strlen(key), value, itemSize, nullptr, nullptr, &handle, TAOS_LRU_PRIORITY_LOW, nullptr); if (status == TAOS_LRU_STATUS_OK || status == TAOS_LRU_STATUS_OK_OVERWRITTEN) { if (handle) { successCount++; taosLRUCacheRelease(cache, handle, false); } } } size_t usage = taosLRUCacheGetUsage(cache); std::cout << "Strict mode - Inserted: " << successCount << ", Usage: " << usage << " / " << capacity << std::endl; // In strict mode, usage should not exceed capacity EXPECT_LE(usage, capacity); taosLRUCacheCleanup(cache); }