blob: 09aebc2d4bc191a22306e9cb54ce7ff9dfd554b3 [file] [log] [blame] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/CAS/UnifiedOnDiskCache.h"
#include "CASTestConfig.h"
#include "OnDiskCommonUtils.h"
#include "llvm/Testing/Support/Error.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::cas;
using namespace llvm::cas::ondisk;
using namespace llvm::unittest::cas;
/// Visits all the files of a directory recursively and returns the sum of their
/// sizes.
static Expected<size_t> countFileSizes(StringRef Path) {
size_t TotalSize = 0;
std::error_code EC;
for (sys::fs::directory_iterator DirI(Path, EC), DirE; !EC && DirI != DirE;
DirI.increment(EC)) {
if (DirI->type() == sys::fs::file_type::directory_file) {
Expected<size_t> Subsize = countFileSizes(DirI->path());
if (!Subsize)
return Subsize.takeError();
TotalSize += *Subsize;
continue;
}
ErrorOr<sys::fs::basic_file_status> Stat = DirI->status();
if (!Stat)
return createFileError(DirI->path(), Stat.getError());
TotalSize += Stat->getSize();
}
if (EC)
return createFileError(Path, EC);
return TotalSize;
}
TEST_F(OnDiskCASTest, UnifiedOnDiskCacheTest) {
unittest::TempDir Temp("ondisk-unified", /*Unique=*/true);
std::unique_ptr<UnifiedOnDiskCache> UniDB;
const uint64_t SizeLimit = 1024ull * 64;
auto reopenDB = [&]() {
UniDB.reset();
ASSERT_THAT_ERROR(UnifiedOnDiskCache::open(Temp.path(), SizeLimit, "blake3",
sizeof(HashType))
.moveInto(UniDB),
Succeeded());
};
reopenDB();
HashType RootHash;
HashType OtherHash;
HashType Key1Hash;
HashType Key2Hash;
{
OnDiskGraphDB &DB = UniDB->getGraphDB();
std::optional<ObjectID> ID1;
ASSERT_THAT_ERROR(store(DB, "1", {}).moveInto(ID1), Succeeded());
std::optional<ObjectID> ID2;
ASSERT_THAT_ERROR(store(DB, "2", {}).moveInto(ID2), Succeeded());
std::optional<ObjectID> IDRoot;
ASSERT_THAT_ERROR(store(DB, "root", {*ID1, *ID2}).moveInto(IDRoot),
Succeeded());
ArrayRef<uint8_t> Digest = DB.getDigest(*IDRoot);
ASSERT_EQ(Digest.size(), RootHash.size());
llvm::copy(Digest, RootHash.data());
std::optional<ObjectID> IDOther;
ASSERT_THAT_ERROR(store(DB, "other", {}).moveInto(IDOther), Succeeded());
Digest = DB.getDigest(*IDOther);
ASSERT_EQ(Digest.size(), OtherHash.size());
llvm::copy(Digest, OtherHash.data());
Key1Hash = digest("key1");
std::optional<ObjectID> Val;
ASSERT_THAT_ERROR(
cachePut(UniDB->getKeyValueDB(), Key1Hash, *IDRoot).moveInto(Val),
Succeeded());
EXPECT_EQ(IDRoot, Val);
Key2Hash = digest("key2");
std::optional<ObjectID> KeyID;
ASSERT_THAT_ERROR(DB.getReference(Key2Hash).moveInto(KeyID), Succeeded());
ASSERT_THAT_ERROR(cachePut(UniDB->getKeyValueDB(),
UniDB->getGraphDB().getDigest(*KeyID), *ID1)
.moveInto(Val),
Succeeded());
}
auto checkTree = [&](const HashType &Digest, StringRef ExpectedTree) {
OnDiskGraphDB &DB = UniDB->getGraphDB();
std::optional<ObjectID> ID;
ASSERT_THAT_ERROR(DB.getReference(Digest).moveInto(ID), Succeeded());
std::string PrintedTree;
raw_string_ostream OS(PrintedTree);
ASSERT_THAT_ERROR(printTree(DB, *ID, OS), Succeeded());
EXPECT_EQ(PrintedTree, ExpectedTree);
};
auto checkRootTree = [&]() {
return checkTree(RootHash, "root\n 1\n 2\n");
};
auto checkKey = [&](const HashType &Key, StringRef ExpectedData) {
OnDiskGraphDB &DB = UniDB->getGraphDB();
std::optional<ObjectID> Val;
ASSERT_THAT_ERROR(cacheGet(UniDB->getKeyValueDB(), Key).moveInto(Val),
Succeeded());
ASSERT_TRUE(Val.has_value());
std::optional<ondisk::ObjectHandle> Obj;
ASSERT_THAT_ERROR(DB.load(*Val).moveInto(Obj), Succeeded());
EXPECT_EQ(toStringRef(DB.getObjectData(*Obj)), ExpectedData);
};
checkRootTree();
checkTree(OtherHash, "other\n");
checkKey(Key1Hash, "root");
checkKey(Key2Hash, "1");
auto storeBigObject = [&](unsigned Index) {
SmallString<1000> Buf;
Buf.append(970, 'a');
raw_svector_ostream(Buf) << Index;
std::optional<ObjectID> ID;
ASSERT_THAT_ERROR(store(UniDB->getGraphDB(), Buf, {}).moveInto(ID),
Succeeded());
};
uint64_t PrevStoreSize = UniDB->getStorageSize();
unsigned Index = 0;
while (!UniDB->hasExceededSizeLimit()) {
storeBigObject(Index++);
}
EXPECT_GT(UniDB->getStorageSize(), PrevStoreSize);
UniDB->setSizeLimit(SizeLimit * 2);
EXPECT_FALSE(UniDB->hasExceededSizeLimit());
UniDB->setSizeLimit(SizeLimit);
EXPECT_TRUE(UniDB->hasExceededSizeLimit());
reopenDB();
EXPECT_FALSE(UniDB->hasExceededSizeLimit());
EXPECT_FALSE(UniDB->needsGarbageCollection());
checkRootTree();
checkKey(Key1Hash, "root");
while (!UniDB->hasExceededSizeLimit()) {
storeBigObject(Index++);
}
PrevStoreSize = UniDB->getStorageSize();
ASSERT_THAT_ERROR(UniDB->close(), Succeeded());
EXPECT_TRUE(UniDB->needsGarbageCollection());
reopenDB();
EXPECT_TRUE(UniDB->needsGarbageCollection());
std::optional<size_t> DirSizeBefore;
ASSERT_THAT_ERROR(countFileSizes(Temp.path()).moveInto(DirSizeBefore),
Succeeded());
ASSERT_THAT_ERROR(UnifiedOnDiskCache::collectGarbage(Temp.path()),
Succeeded());
std::optional<size_t> DirSizeAfter;
ASSERT_THAT_ERROR(countFileSizes(Temp.path()).moveInto(DirSizeAfter),
Succeeded());
EXPECT_LT(*DirSizeAfter, *DirSizeBefore);
reopenDB();
EXPECT_FALSE(UniDB->needsGarbageCollection());
checkRootTree();
checkKey(Key1Hash, "root");
EXPECT_LT(UniDB->getStorageSize(), PrevStoreSize);
// 'Other' tree and 'Key2' got garbage-collected.
{
OnDiskGraphDB &DB = UniDB->getGraphDB();
std::optional<ObjectID> ID;
ASSERT_THAT_ERROR(DB.getReference(OtherHash).moveInto(ID), Succeeded());
EXPECT_FALSE(DB.containsObject(*ID));
std::optional<ObjectID> Val;
ASSERT_THAT_ERROR(cacheGet(UniDB->getKeyValueDB(), Key2Hash).moveInto(Val),
Succeeded());
EXPECT_FALSE(Val.has_value());
}
}