* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "test_file.h"
#include <iostream>
#include <vector>
#include "binary/query_binary.h"
#include "include/bss_types.h"
#include "include/config.h"
using namespace ock::bss;
const std::vector<std::string> compressionLevelPolicyForTestFile = { "lz4", "lz4", "lz4" };
const std::string lsmStoreCompressionPolicyForTestFile = "lz4";
std::vector<std::pair<SliceKey, Value>> TestFile::GenerateKeyValue(uint64_t size, int32_t seed)
{
uint64_t currentSize = 0;
std::vector<std::pair<SliceKey, Value>> result;
std::set<uint32_t> prKeyHash;
while (currentSize < size) {
uint16_t stateId = VALUE << NO_13;
SliceKey sglKey = mGenerator->GenerateSglKey(NO_10, stateId);
Value value = mGenerator->GenerateValue(NO_1024, mSeqGenerator->Next());
result.emplace_back(std::pair<SliceKey, Value>(sglKey, value));
currentSize++;
}
return result;
}
std::vector<std::pair<SliceKey, Value>> TestFile::GenerateKeyMapValue(uint64_t size, std::vector<uint32_t> &priKeyHash,
KeyVectorValueMap &prefixEntries, int32_t seed)
{
uint64_t currentSize = 0;
std::vector<std::pair<SliceKey, Value>> result;
while (currentSize < size) {
SliceKey dualKey = mGenerator->GenerateDualKey(NO_10, NO_20);
Value value = mGenerator->GenerateValue(NO_1024, mSeqGenerator->Next());
result.emplace_back(std::pair<SliceKey, Value>(dualKey, value));
currentSize++;
}
return result;
}
std::vector<std::pair<SliceKey, Value>> TestFile::GenerateListKeyValue(uint64_t size, ValueType valueType, int32_t seed)
{
uint64_t currentSize = 0;
std::set<uint32_t> prKeyHash;
std::vector<std::pair<SliceKey, Value>> result;
while (currentSize < size) {
uint16_t stateId = LIST << NO_13;
SliceKey sglKey = mGenerator->GenerateSglKey(NO_10, stateId);
Value value = mGenerator->GenerateValue(NO_1024, mSeqGenerator->Next());
result.emplace_back(sglKey, value);
currentSize++;
}
return result;
}
Value TestFile::GenerateDeleteValue()
{
Value value;
value.Init(DELETE, 0, nullptr, mSeqGenerator->Next(), nullptr);
return value;
}
TEST_F(TestFile, test_write_read_single_file_return_ok)
{
InitEnv(std::make_shared<Config>(NO_0, NO_15, NO_16));
std::vector<std::pair<SliceKey, Value>> queryEntries = GenerateKeyValue(IO_SIZE_16K);
std::vector<std::pair<SliceKey, Value>> putEntries;
std::vector<uint32_t> existPriHashCode;
for (auto &entry : queryEntries) {
uint32_t hash = entry.first.KeyHashCode();
if (std::find(existPriHashCode.begin(), existPriHashCode.end(), hash) != existPriHashCode.end()) {
continue;
}
existPriHashCode.emplace_back(hash);
putEntries.emplace_back(entry);
}
FlushLevel0Table(putEntries);
ASSERT_EQ(mMetric->GetFileFlush(), 1);
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &entry : queryEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second);
}
}
TEST_F(TestFile, test_compact_2_files_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreL0NumTrigger(NO_2);
InitEnv(config);
std::set<uint32_t> priKeyHash;
std::vector<std::pair<SliceKey, Value>> queryEntries;
uint32_t fileCount = NO_2;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putPair;
std::vector<std::pair<SliceKey, Value>> pair = GenerateKeyValue(2048, 10 * i + 1);
for (auto &item : pair) {
uint32_t hash = item.first.KeyHashCode();
if (std::find(priKeyHash.begin(), priKeyHash.end(), hash) != priKeyHash.end()) {
continue;
}
priKeyHash.emplace(hash);
putPair.emplace_back(item);
queryEntries.emplace_back(item);
}
FlushLevel0Table(queryEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &entry : queryEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second);
}
}
TEST_F(TestFile, test_compact_3_files_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreL0NumTrigger(NO_2);
InitEnv(config);
std::vector<std::pair<SliceKey, Value>> queryEntries;
KeyVectorValueMap prefixEntries;
uint32_t fileCount = NO_3;
std::vector<uint32_t> priKeyHash;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
std::vector<std::pair<SliceKey, Value>> pair = GenerateKeyMapValue(NO_1024, priKeyHash, prefixEntries);
for (auto &item : pair) {
putEntries.emplace_back(item);
queryEntries.emplace_back(item);
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &entry : prefixEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second);
}
}
TEST_F(TestFile, test_value_state_with_deleted_value_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreL0NumTrigger(NO_2);
InitEnv(config);
std::vector<std::pair<SliceKey, Value>> queryEntries;
std::vector<SliceKey> allDeletedKeys;
KeyVectorValueMap prefixEntries;
uint32_t fileCount = NO_3;
std::vector<SliceKey> deletedKeys;
std::map<uint32_t, uint32_t> deleteHashCodes;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
std::vector<uint32_t> priKeyHash;
std::vector<std::pair<SliceKey, Value>> newEntries = GenerateKeyMapValue(NO_1024, priKeyHash, prefixEntries,
i * NO_10000);
for (auto &deletedKey : deletedKeys) {
Value deletedValue = GenerateDeleteValue();
putEntries.emplace_back(deletedKey, deletedValue);
}
deletedKeys.clear();
uint32_t deleteFlag = 0;
for (auto &entry : newEntries) {
putEntries.emplace_back(entry);
queryEntries.emplace_back(entry);
if (++deleteFlag % NO_10 == 0 && i != (fileCount - 1)) {
auto it = deleteHashCodes.find(entry.first.KeyHashCode());
if (it == deleteHashCodes.end()) {
deletedKeys.emplace_back(entry.first);
allDeletedKeys.emplace_back(entry.first);
deleteHashCodes.emplace(entry.first.KeyHashCode(), entry.first.KeyHashCode());
}
}
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second, allDeletedKeys);
}
for (auto &entry : prefixEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second, allDeletedKeys);
}
}
TEST_F(TestFile, test_list_state_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreL0NumTrigger(NO_2);
config->SetTotalDBSize(IO_SIZE_2G);
InitEnv(config);
uint32_t fileCount = NO_3;
std::vector<std::pair<SliceKey, Value>> queryEntries;
std::set<uint32_t> priKeyHash;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
std::vector<std::pair<SliceKey, Value>> newEntries = GenerateListKeyValue(NO_1024, PUT, i * 10000);
for (auto &entry : newEntries) {
uint32_t hash = entry.first.KeyHashCode();
if (std::find(priKeyHash.begin(), priKeyHash.end(), hash) != priKeyHash.end()) {
continue;
}
priKeyHash.emplace(hash);
putEntries.emplace_back(entry);
queryEntries.emplace_back(entry);
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &item : queryEntries) {
GetAndCheckPrefixIterator(item.first, item.second);
}
}
TEST_F(TestFile, test_value_and_list_state_with_covered_value_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
InitEnv(config);
uint32_t fileCount = NO_3;
SliceKVMap queryEntries;
std::vector<SliceKey> coverKeys;
std::set<uint32_t> priKeyHash;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
for (auto &key : coverKeys) {
Value value = mGenerator->GenerateValue(NO_1024, mSeqGenerator->Next());
putEntries.emplace_back(key, value);
queryEntries[key] = value;
}
coverKeys.clear();
uint32_t count = 0;
std::vector<std::pair<SliceKey, Value>> listPair = GenerateListKeyValue(NO_1024, PUT);
std::vector<std::pair<SliceKey, Value>> valuePair = GenerateKeyValue(NO_512);
valuePair.insert(valuePair.end(), listPair.begin(), listPair.end());
for (auto &item : valuePair) {
uint32_t hash = item.first.KeyHashCode();
if (std::find(priKeyHash.begin(), priKeyHash.end(), hash) != priKeyHash.end()) {
continue;
}
priKeyHash.emplace(hash);
count++;
putEntries.emplace_back(item);
queryEntries.emplace(item.first, item.second);
if (count % NO_10 == 0 && (fileCount - 1) != i) {
coverKeys.emplace_back(item.first);
}
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &item : queryEntries) {
GetAndCheckPrefixIterator(item.first, item.second);
}
}
TEST_F(TestFile, test_write_level4_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreFileBaseSize(NO_8 * NO_1024);
config->SetFileStoreMaxBaseLevelBytes(NO_3 * NO_1024);
config->SetFileStoreMultiple(NO_2);
InitEnv(config);
uint32_t fileCount = NO_10;
SliceKVMap queryEntries;
std::vector<uint32_t> priKeyHash;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
std::vector<std::pair<SliceKey, Value>> newEntries = GenerateKeyValue(NO_512);
for (auto &entry : newEntries) {
uint32_t hash = entry.first.KeyHashCode();
if (std::find(priKeyHash.begin(), priKeyHash.end(), hash) != priKeyHash.end()) {
continue;
}
priKeyHash.emplace_back(hash);
putEntries.emplace_back(entry);
queryEntries.emplace(entry.first, entry.second);
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second);
}
for (auto &entry : queryEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second);
}
}
TEST_F(TestFile, test_value_and_list_state_with_deleted_value_return_ok)
{
ConfigRef config = std::make_shared<Config>();
config->Init(NO_0, NO_15, NO_16);
config->SetLsmStoreCompactionSwitch(1);
config->SetLsmStoreCompressionPolicy(lsmStoreCompressionPolicyForTestFile);
config->SetCompressionLevelPolicy(compressionLevelPolicyForTestFile);
config->SetFileStoreL0NumTrigger(NO_2);
InitEnv(config);
uint32_t fileCount = NO_3;
SliceKVMap queryEntries;
std::vector<SliceKey> allDeletedKeys;
std::vector<SliceKey> tempDeletedKeys;
std::set<uint32_t> priKeyHash;
for (uint32_t i = 0; i < fileCount; i++) {
std::vector<std::pair<SliceKey, Value>> putEntries;
for (auto &deletedKey : tempDeletedKeys) {
Value deletedValue = GenerateDeleteValue();
putEntries.emplace_back(deletedKey, deletedValue);
}
tempDeletedKeys.clear();
uint32_t count = 0;
std::vector<std::pair<SliceKey, Value>> listPair = GenerateListKeyValue(NO_32, PUT);
std::vector<std::pair<SliceKey, Value>> valuePair = GenerateKeyValue(NO_32);
valuePair.insert(valuePair.end(), listPair.begin(), listPair.end());
for (auto &item : valuePair) {
uint32_t hash = item.first.KeyHashCode();
if (priKeyHash.find(hash) != priKeyHash.end()) {
continue;
}
priKeyHash.emplace(hash);
count++;
putEntries.emplace_back(item);
queryEntries.emplace(item.first, item.second);
if (count % NO_10 == 0 && (fileCount - 1) != i) {
tempDeletedKeys.emplace_back(item.first);
allDeletedKeys.emplace_back(item.first);
}
}
FlushLevel0Table(putEntries);
}
while (!mLsmStore->CheckCompactionCompleted()) {
sleep(1);
}
for (auto &entry : queryEntries) {
GetAndCheckValue(entry.first, entry.second, allDeletedKeys);
}
for (auto &entry : queryEntries) {
GetAndCheckPrefixIterator(entry.first, entry.second, allDeletedKeys);
}
}
TEST_F(TestFile, test_build_filter_block_return_ok)
{
std::random_device rd;
std::mt19937 gen(rd());
uint32_t byteBegin = NO_1;
uint32_t byteEnd = UINT32_MAX;
std::uniform_int_distribution<uint32_t> byteDist(byteBegin, byteEnd);
uint32_t count = 10000;
std::set<uint32_t> vector;
std::vector<uint32_t> testVector;
for (uint32_t j = 0; j < count; j++) {
auto hash = byteDist(gen);
vector.emplace(hash);
testVector.emplace_back(hash + NO_MAX_INT32_VALUE_2G);
}
uint32_t match;
std::vector<uint8_t> array = BuildFilterBlock(vector);
for (uint32_t j = 0; j < testVector.size(); j++) {
bool ret = KeyMatch(array, testVector[j]);
if (ret) {
match++;
}
}
printf("test count:%zu,match :%u \n", testVector.size(), match);
}
std::vector<uint8_t> TestFile::BuildFilterBlock(std::set<uint32_t> hashVec)
{
uint32_t bits = static_cast<double>(-(hashVec.size() * log(0.01) / log(2) / log(2)));
uint32_t k = ceil(bits / hashVec.size() * log(2));
if (bits < NO_64) {
bits = NO_64;
}
const uint32_t bytes = (bits + NO_7) / NO_8;
bits = bytes * NO_8;
std::vector<uint8_t> array(bytes + 1);
array[bytes] = k;
for (auto hash : hashVec) {
uint32_t delta = (hash >> NO_17) | (hash << NO_15);
for (uint32_t j = 0; j < k; j++) {
uint32_t bitPosition = static_cast<uint32_t>((static_cast<uint64_t>(hash) % bits));
uint32_t i = bitPosition / NO_8;
array[i] = static_cast<uint8_t>(array[i] | (1 << (bitPosition % NO_8)));
hash += delta;
}
}
return array;
}
bool TestFile::KeyMatch(std::vector<uint8_t> array, uint32_t hash)
{
uint32_t len = array.size();
uint32_t bits = (len - 1) * NO_8;
uint32_t k = array[len - 1];
if (k > NO_30) {
return true;
}
uint32_t delta = hash >> NO_17 | hash << NO_15;
for (uint32_t i = 0; i < k; i++) {
auto bitPosition = static_cast<uint32_t>((static_cast<uint64_t>(hash) % bits));
uint8_t value = array[bitPosition / NO_8];
if ((value & (1 << (bitPosition % NO_8))) == 0) {
return false;
}
hash += delta;
}
return true;
}