diff options
Diffstat (limited to 'llvm/unittests/CAS')
| -rw-r--r-- | llvm/unittests/CAS/ActionCacheTest.cpp | 6 | ||||
| -rw-r--r-- | llvm/unittests/CAS/BuiltinUnifiedCASDatabasesTest.cpp | 67 | ||||
| -rw-r--r-- | llvm/unittests/CAS/CASTestConfig.cpp | 17 | ||||
| -rw-r--r-- | llvm/unittests/CAS/CASTestConfig.h | 41 | ||||
| -rw-r--r-- | llvm/unittests/CAS/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | llvm/unittests/CAS/ObjectStoreTest.cpp | 134 | ||||
| -rw-r--r-- | llvm/unittests/CAS/OnDiskCommonUtils.h | 21 | ||||
| -rw-r--r-- | llvm/unittests/CAS/OnDiskGraphDBTest.cpp | 12 | ||||
| -rw-r--r-- | llvm/unittests/CAS/UnifiedOnDiskCacheTest.cpp | 198 |
9 files changed, 470 insertions, 28 deletions
diff --git a/llvm/unittests/CAS/ActionCacheTest.cpp b/llvm/unittests/CAS/ActionCacheTest.cpp index db67e30..692da23 100644 --- a/llvm/unittests/CAS/ActionCacheTest.cpp +++ b/llvm/unittests/CAS/ActionCacheTest.cpp @@ -21,7 +21,7 @@ using namespace llvm; using namespace llvm::cas; TEST_P(CASTest, ActionCacheHit) { - std::shared_ptr<ObjectStore> CAS = createObjectStore(); + std::unique_ptr<ObjectStore> CAS = createObjectStore(); std::unique_ptr<ActionCache> Cache = createActionCache(); std::optional<ObjectProxy> ID; @@ -36,7 +36,7 @@ TEST_P(CASTest, ActionCacheHit) { } TEST_P(CASTest, ActionCacheMiss) { - std::shared_ptr<ObjectStore> CAS = createObjectStore(); + std::unique_ptr<ObjectStore> CAS = createObjectStore(); std::unique_ptr<ActionCache> Cache = createActionCache(); std::optional<ObjectProxy> ID1, ID2; @@ -59,7 +59,7 @@ TEST_P(CASTest, ActionCacheMiss) { } TEST_P(CASTest, ActionCacheRewrite) { - std::shared_ptr<ObjectStore> CAS = createObjectStore(); + std::unique_ptr<ObjectStore> CAS = createObjectStore(); std::unique_ptr<ActionCache> Cache = createActionCache(); std::optional<ObjectProxy> ID1, ID2; diff --git a/llvm/unittests/CAS/BuiltinUnifiedCASDatabasesTest.cpp b/llvm/unittests/CAS/BuiltinUnifiedCASDatabasesTest.cpp new file mode 100644 index 0000000..19522e9 --- /dev/null +++ b/llvm/unittests/CAS/BuiltinUnifiedCASDatabasesTest.cpp @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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/BuiltinUnifiedCASDatabases.h" +#include "CASTestConfig.h" +#include "llvm/CAS/ActionCache.h" +#include "llvm/CAS/ObjectStore.h" +#include "llvm/Testing/Support/Error.h" +#include "llvm/Testing/Support/SupportHelpers.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::cas; + +TEST_F(OnDiskCASTest, UnifiedCASMaterializationCheckPreventsGarbageCollection) { + unittest::TempDir Temp("on-disk-unified-cas", /*Unique=*/true); + + auto WithCAS = [&](llvm::function_ref<void(ObjectStore &)> Action) { + std::pair<std::unique_ptr<ObjectStore>, std::unique_ptr<ActionCache>> DBs; + ASSERT_THAT_ERROR( + createOnDiskUnifiedCASDatabases(Temp.path()).moveInto(DBs), + Succeeded()); + ObjectStore &CAS = *DBs.first; + ASSERT_THAT_ERROR(CAS.setSizeLimit(1), Succeeded()); + Action(CAS); + }; + + std::optional<CASID> ID; + + // Create an object in the CAS. + WithCAS([&ID](ObjectStore &CAS) { + std::optional<ObjectRef> Ref; + ASSERT_THAT_ERROR(CAS.store({}, "blah").moveInto(Ref), Succeeded()); + ASSERT_TRUE(Ref.has_value()); + + ID = CAS.getID(*Ref); + }); + + // Check materialization and prune the storage. + WithCAS([&ID](ObjectStore &CAS) { + std::optional<ObjectRef> Ref = CAS.getReference(*ID); + ASSERT_TRUE(Ref.has_value()); + + std::optional<bool> IsMaterialized; + ASSERT_THAT_ERROR(CAS.isMaterialized(*Ref).moveInto(IsMaterialized), + Succeeded()); + ASSERT_TRUE(IsMaterialized); + + ASSERT_THAT_ERROR(CAS.pruneStorageData(), Succeeded()); + }); + + // Verify that the previous materialization check kept the object in the CAS. + WithCAS([&ID](ObjectStore &CAS) { + std::optional<ObjectRef> Ref = CAS.getReference(*ID); + ASSERT_TRUE(Ref.has_value()); + + std::optional<bool> IsMaterialized; + ASSERT_THAT_ERROR(CAS.isMaterialized(*Ref).moveInto(IsMaterialized), + Succeeded()); + ASSERT_TRUE(IsMaterialized); + }); +} diff --git a/llvm/unittests/CAS/CASTestConfig.cpp b/llvm/unittests/CAS/CASTestConfig.cpp index 10e4b68..08cbf1d 100644 --- a/llvm/unittests/CAS/CASTestConfig.cpp +++ b/llvm/unittests/CAS/CASTestConfig.cpp @@ -8,6 +8,7 @@ #include "CASTestConfig.h" #include "llvm/CAS/ObjectStore.h" +#include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" #include <mutex> @@ -15,7 +16,8 @@ using namespace llvm; using namespace llvm::cas; static CASTestingEnv createInMemory(int I) { - return CASTestingEnv{createInMemoryCAS(), createInMemoryActionCache()}; + return CASTestingEnv{createInMemoryCAS(), createInMemoryActionCache(), + std::nullopt}; } INSTANTIATE_TEST_SUITE_P(InMemoryCAS, CASTest, @@ -23,7 +25,7 @@ INSTANTIATE_TEST_SUITE_P(InMemoryCAS, CASTest, #if LLVM_ENABLE_ONDISK_CAS namespace llvm::cas::ondisk { -extern void setMaxMappingSize(uint64_t Size); +void setMaxMappingSize(uint64_t Size); } // namespace llvm::cas::ondisk void setMaxOnDiskCASMappingSize() { @@ -31,6 +33,17 @@ void setMaxOnDiskCASMappingSize() { std::call_once( Flag, [] { llvm::cas::ondisk::setMaxMappingSize(100 * 1024 * 1024); }); } + +static CASTestingEnv createOnDisk(int I) { + unittest::TempDir Temp("on-disk-cas", /*Unique=*/true); + std::unique_ptr<ObjectStore> CAS; + EXPECT_THAT_ERROR(createOnDiskCAS(Temp.path()).moveInto(CAS), Succeeded()); + std::unique_ptr<ActionCache> Cache; + EXPECT_THAT_ERROR(createOnDiskActionCache(Temp.path()).moveInto(Cache), + Succeeded()); + return CASTestingEnv{std::move(CAS), std::move(Cache), std::move(Temp)}; +} +INSTANTIATE_TEST_SUITE_P(OnDiskCAS, CASTest, ::testing::Values(createOnDisk)); #else void setMaxOnDiskCASMappingSize() {} #endif /* LLVM_ENABLE_ONDISK_CAS */ diff --git a/llvm/unittests/CAS/CASTestConfig.h b/llvm/unittests/CAS/CASTestConfig.h index 8d3c553..b1c0e59 100644 --- a/llvm/unittests/CAS/CASTestConfig.h +++ b/llvm/unittests/CAS/CASTestConfig.h @@ -6,16 +6,28 @@ // //===----------------------------------------------------------------------===// +#ifndef LLVM_UNITTESTS_CASTESTCONFIG_H +#define LLVM_UNITTESTS_CASTESTCONFIG_H + #include "llvm/CAS/ActionCache.h" #include "llvm/CAS/ObjectStore.h" +#include "llvm/Testing/Support/SupportHelpers.h" #include "gtest/gtest.h" +#include <memory> -#ifndef LLVM_UNITTESTS_CASTESTCONFIG_H -#define LLVM_UNITTESTS_CASTESTCONFIG_H +namespace llvm::unittest::cas { +class MockEnv { + void anchor(); + +public: + virtual ~MockEnv(); +}; +} // namespace llvm::unittest::cas struct CASTestingEnv { std::unique_ptr<llvm::cas::ObjectStore> CAS; std::unique_ptr<llvm::cas::ActionCache> Cache; + std::optional<llvm::unittest::TempDir> Temp; }; void setMaxOnDiskCASMappingSize(); @@ -24,26 +36,47 @@ void setMaxOnDiskCASMappingSize(); class OnDiskCASTest : public ::testing::Test { protected: void SetUp() override { +#if !LLVM_ENABLE_ONDISK_CAS + GTEST_SKIP() << "OnDiskCAS is not enabled"; +#endif // Use a smaller database size for testing to conserve disk space. setMaxOnDiskCASMappingSize(); } }; +// Parametered test fixture for ObjectStore and ActionCache tests. class CASTest : public testing::TestWithParam<std::function<CASTestingEnv(int)>> { protected: std::optional<int> NextCASIndex; + llvm::SmallVector<llvm::unittest::TempDir> Dirs; + + llvm::SmallVector<std::unique_ptr<llvm::unittest::cas::MockEnv>> Envs; + std::unique_ptr<llvm::cas::ObjectStore> createObjectStore() { auto TD = GetParam()(++(*NextCASIndex)); + if (TD.Temp) + Dirs.push_back(std::move(*TD.Temp)); return std::move(TD.CAS); } std::unique_ptr<llvm::cas::ActionCache> createActionCache() { auto TD = GetParam()(++(*NextCASIndex)); + if (TD.Temp) + Dirs.push_back(std::move(*TD.Temp)); return std::move(TD.Cache); } - void SetUp() override { NextCASIndex = 0; } - void TearDown() override { NextCASIndex = std::nullopt; } + + void SetUp() override { + NextCASIndex = 0; + setMaxOnDiskCASMappingSize(); + } + + void TearDown() override { + NextCASIndex = std::nullopt; + Dirs.clear(); + Envs.clear(); + } }; #endif diff --git a/llvm/unittests/CAS/CMakeLists.txt b/llvm/unittests/CAS/CMakeLists.txt index da469f7..91e49be 100644 --- a/llvm/unittests/CAS/CMakeLists.txt +++ b/llvm/unittests/CAS/CMakeLists.txt @@ -1,9 +1,11 @@ set(ONDISK_CAS_TEST_SOURCES + BuiltinUnifiedCASDatabasesTest.cpp OnDiskGraphDBTest.cpp OnDiskDataAllocatorTest.cpp OnDiskKeyValueDBTest.cpp OnDiskTrieRawHashMapTest.cpp ProgramTest.cpp + UnifiedOnDiskCacheTest.cpp ) set(LLVM_OPTIONAL_SOURCES diff --git a/llvm/unittests/CAS/ObjectStoreTest.cpp b/llvm/unittests/CAS/ObjectStoreTest.cpp index 54083fd..b43ae33 100644 --- a/llvm/unittests/CAS/ObjectStoreTest.cpp +++ b/llvm/unittests/CAS/ObjectStoreTest.cpp @@ -1,4 +1,4 @@ -//===- ObjectStoreTest.cpp ------------------------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -76,7 +76,7 @@ multiline text multiline text multiline text multiline text multiline text)", // Run validation on all CASIDs. for (int I = 0, E = IDs.size(); I != E; ++I) - ASSERT_THAT_ERROR(CAS1->validate(IDs[I]), Succeeded()); + ASSERT_THAT_ERROR(CAS1->validateObject(IDs[I]), Succeeded()); // Check that the blobs can be retrieved multiple times. for (int I = 0, E = IDs.size(); I != E; ++I) { @@ -120,15 +120,15 @@ TEST_P(CASTest, BlobsBig) { std::optional<CASID> ID2; ASSERT_THAT_ERROR(CAS->createProxy({}, String1).moveInto(ID1), Succeeded()); ASSERT_THAT_ERROR(CAS->createProxy({}, String1).moveInto(ID2), Succeeded()); - ASSERT_THAT_ERROR(CAS->validate(*ID1), Succeeded()); - ASSERT_THAT_ERROR(CAS->validate(*ID2), Succeeded()); + ASSERT_THAT_ERROR(CAS->validateObject(*ID1), Succeeded()); + ASSERT_THAT_ERROR(CAS->validateObject(*ID2), Succeeded()); ASSERT_EQ(ID1, ID2); String1.append(String2); ASSERT_THAT_ERROR(CAS->createProxy({}, String2).moveInto(ID1), Succeeded()); ASSERT_THAT_ERROR(CAS->createProxy({}, String2).moveInto(ID2), Succeeded()); - ASSERT_THAT_ERROR(CAS->validate(*ID1), Succeeded()); - ASSERT_THAT_ERROR(CAS->validate(*ID2), Succeeded()); + ASSERT_THAT_ERROR(CAS->validateObject(*ID1), Succeeded()); + ASSERT_THAT_ERROR(CAS->validateObject(*ID2), Succeeded()); ASSERT_EQ(ID1, ID2); String2.append(String1); } @@ -176,10 +176,11 @@ multiline text multiline text multiline text multiline text multiline text)", // Check basic printing of IDs. IDs.push_back(CAS1->getID(*Node)); - auto ID = CAS1->getID(Nodes.back()); - EXPECT_EQ(ID.toString(), IDs.back().toString()); - EXPECT_EQ(*Node, Nodes.back()); - EXPECT_EQ(ID, IDs.back()); + EXPECT_EQ(IDs.back().toString(), IDs.back().toString()); + EXPECT_EQ(Nodes.front(), Nodes.front()); + EXPECT_EQ(Nodes.back(), Nodes.back()); + EXPECT_EQ(IDs.front(), IDs.front()); + EXPECT_EQ(IDs.back(), IDs.back()); if (Nodes.size() <= 1) continue; EXPECT_NE(Nodes.front(), Nodes.back()); @@ -266,7 +267,7 @@ TEST_P(CASTest, NodesBig) { } for (auto ID : CreatedNodes) - ASSERT_THAT_ERROR(CAS->validate(CAS->getID(ID)), Succeeded()); + ASSERT_THAT_ERROR(CAS->validateObject(CAS->getID(ID)), Succeeded()); } #if LLVM_ENABLE_THREADS @@ -332,17 +333,124 @@ static void testBlobsParallel1(ObjectStore &CAS, uint64_t BlobSize) { } TEST_P(CASTest, BlobsParallel) { - std::shared_ptr<ObjectStore> CAS = createObjectStore(); + std::unique_ptr<ObjectStore> CAS = createObjectStore(); uint64_t Size = 1ULL * 1024; ASSERT_NO_FATAL_FAILURE(testBlobsParallel1(*CAS, Size)); } #ifdef EXPENSIVE_CHECKS TEST_P(CASTest, BlobsBigParallel) { - std::shared_ptr<ObjectStore> CAS = createObjectStore(); + std::unique_ptr<ObjectStore> CAS = createObjectStore(); // 100k is large enough to be standalone files in our on-disk cas. uint64_t Size = 100ULL * 1024; ASSERT_NO_FATAL_FAILURE(testBlobsParallel1(*CAS, Size)); } #endif // EXPENSIVE_CHECKS + +#ifndef _WIN32 // create_link won't work for directories on Windows +TEST_F(OnDiskCASTest, OnDiskCASBlobsParallelMultiCAS) { + // This test intentionally uses symlinked paths to the same CAS to subvert the + // shared memory mappings that would normally be created within a single + // process. This breaks the lock file guarantees, so we must be careful not + // to create or destroy the CAS objects concurrently, which is when the locks + // are normally important. + unittest::TempDir Temp("on-disk-cas", /*Unique=*/true); + ASSERT_EQ(sys::fs::create_directory(Temp.path("real_cas")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas1")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas2")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas3")), + std::error_code()); + + std::unique_ptr<ObjectStore> CAS1, CAS2, CAS3, CAS4; + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("real_cas")).moveInto(CAS1), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas1")).moveInto(CAS2), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas2")).moveInto(CAS3), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas3")).moveInto(CAS4), + Succeeded()); + + uint64_t Size = 1ULL * 1024; + ASSERT_NO_FATAL_FAILURE(testBlobsParallel(*CAS1, *CAS2, *CAS3, *CAS4, Size)); +} + +TEST_F(OnDiskCASTest, OnDiskCASBlobsBigParallelMultiCAS) { + // See comment in BlobsParallelMultiCAS. + unittest::TempDir Temp("on-disk-cas", /*Unique=*/true); + ASSERT_EQ(sys::fs::create_directory(Temp.path("real_cas")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas1")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas2")), + std::error_code()); + ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas3")), + std::error_code()); + + std::unique_ptr<ObjectStore> CAS1, CAS2, CAS3, CAS4; + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("real_cas")).moveInto(CAS1), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas1")).moveInto(CAS2), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas2")).moveInto(CAS3), + Succeeded()); + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas3")).moveInto(CAS4), + Succeeded()); + + // 100k is large enough to be standalone files in our on-disk cas. + uint64_t Size = 100ULL * 1024; + ASSERT_NO_FATAL_FAILURE(testBlobsParallel(*CAS1, *CAS2, *CAS3, *CAS4, Size)); +} +#endif // _WIN32 #endif // LLVM_ENABLE_THREADS + +TEST_F(OnDiskCASTest, OnDiskCASDiskSize) { + unittest::TempDir Temp("on-disk-cas", /*Unique=*/true); + std::unique_ptr<ObjectStore> CAS; + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path()).moveInto(CAS), Succeeded()); + + uint64_t MaxSize = 100 * 1024 * 1024; + + // Check that we map the files to the correct size. + auto CheckFileSizes = [&](bool Mapped) { + bool FoundIndex = false, FoundData = false; + std::error_code EC; + for (sys::fs::directory_iterator I(Temp.path(), EC), E; I != E && !EC; + I.increment(EC)) { + StringRef Filename = sys::path::filename(I->path()); + if (Filename.starts_with("index.") && !Filename.ends_with(".shared")) { + FoundIndex = true; + ASSERT_TRUE(I->status()); + if (Mapped) + EXPECT_EQ(I->status()->getSize(), MaxSize); + else + EXPECT_LT(I->status()->getSize(), MaxSize); + } + if (Filename.starts_with("data.") && !Filename.ends_with(".shared")) { + FoundData = true; + ASSERT_TRUE(I->status()); + if (Mapped) + EXPECT_EQ(I->status()->getSize(), MaxSize); + else + EXPECT_LT(I->status()->getSize(), MaxSize); + } + } + ASSERT_TRUE(FoundIndex); + ASSERT_TRUE(FoundData); + }; + + // Check that we have the full mapping size when the CAS is open. + CheckFileSizes(/*Mapped=*/true); + CAS.reset(); + // Check that the CAS is shrunk to a smaller size. + CheckFileSizes(/*Mapped=*/false); + + // Repeat the checks when starting from an existing CAS. + ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path()).moveInto(CAS), Succeeded()); + CheckFileSizes(/*Mapped=*/true); + CAS.reset(); + CheckFileSizes(/*Mapped=*/false); +} diff --git a/llvm/unittests/CAS/OnDiskCommonUtils.h b/llvm/unittests/CAS/OnDiskCommonUtils.h index 89f93e0..48a1830 100644 --- a/llvm/unittests/CAS/OnDiskCommonUtils.h +++ b/llvm/unittests/CAS/OnDiskCommonUtils.h @@ -12,6 +12,8 @@ #include "llvm/CAS/BuiltinObjectHasher.h" #include "llvm/CAS/OnDiskGraphDB.h" +#include "llvm/CAS/OnDiskKeyValueDB.h" +#include "llvm/CAS/UnifiedOnDiskCache.h" #include "llvm/Support/BLAKE3.h" #include "llvm/Testing/Support/Error.h" @@ -58,6 +60,25 @@ inline Expected<ObjectID> store(OnDiskGraphDB &DB, StringRef Data, return ID; } +inline Expected<ObjectID> cachePut(OnDiskKeyValueDB &DB, ArrayRef<uint8_t> Key, + ObjectID ID) { + auto Value = UnifiedOnDiskCache::getValueFromObjectID(ID); + auto Result = DB.put(Key, Value); + if (!Result) + return Result.takeError(); + return UnifiedOnDiskCache::getObjectIDFromValue(*Result); +} + +inline Expected<std::optional<ObjectID>> cacheGet(OnDiskKeyValueDB &DB, + ArrayRef<uint8_t> Key) { + auto Result = DB.get(Key); + if (!Result) + return Result.takeError(); + if (!*Result) + return std::nullopt; + return UnifiedOnDiskCache::getObjectIDFromValue(**Result); +} + inline Error printTree(OnDiskGraphDB &DB, ObjectID ID, raw_ostream &OS, unsigned Indent = 0) { std::optional<ondisk::ObjectHandle> Obj; diff --git a/llvm/unittests/CAS/OnDiskGraphDBTest.cpp b/llvm/unittests/CAS/OnDiskGraphDBTest.cpp index 3c2e963..e9c73bf 100644 --- a/llvm/unittests/CAS/OnDiskGraphDBTest.cpp +++ b/llvm/unittests/CAS/OnDiskGraphDBTest.cpp @@ -102,7 +102,7 @@ TEST_F(OnDiskCASTest, OnDiskGraphDBFaultInSingleNode) { std::unique_ptr<OnDiskGraphDB> DB; ASSERT_THAT_ERROR( OnDiskGraphDB::open(Temp.path(), "blake3", sizeof(HashType), - std::move(UpstreamDB), + UpstreamDB.get(), OnDiskGraphDB::FaultInPolicy::SingleNode) .moveInto(DB), Succeeded()); @@ -208,7 +208,7 @@ TEST_F(OnDiskCASTest, OnDiskGraphDBFaultInFullTree) { unittest::TempDir Temp("ondiskcas", /*Unique=*/true); std::unique_ptr<OnDiskGraphDB> DB; ASSERT_THAT_ERROR(OnDiskGraphDB::open(Temp.path(), "blake3", sizeof(HashType), - std::move(UpstreamDB), + UpstreamDB.get(), OnDiskGraphDB::FaultInPolicy::FullTree) .moveInto(DB), Succeeded()); @@ -264,14 +264,14 @@ TEST_F(OnDiskCASTest, OnDiskGraphDBFaultInPolicyConflict) { unittest::TempDir Temp("ondiskcas", /*Unique=*/true); std::unique_ptr<OnDiskGraphDB> DB; ASSERT_THAT_ERROR(OnDiskGraphDB::open(Temp.path(), "blake3", - sizeof(HashType), - std::move(UpstreamDB), Policy1) + sizeof(HashType), UpstreamDB.get(), + Policy1) .moveInto(DB), Succeeded()); DB.reset(); ASSERT_THAT_ERROR(OnDiskGraphDB::open(Temp.path(), "blake3", - sizeof(HashType), - std::move(UpstreamDB), Policy2) + sizeof(HashType), UpstreamDB.get(), + Policy2) .moveInto(DB), Failed()); }; diff --git a/llvm/unittests/CAS/UnifiedOnDiskCacheTest.cpp b/llvm/unittests/CAS/UnifiedOnDiskCacheTest.cpp new file mode 100644 index 0000000..09aebc2 --- /dev/null +++ b/llvm/unittests/CAS/UnifiedOnDiskCacheTest.cpp @@ -0,0 +1,198 @@ +//===----------------------------------------------------------------------===// +// +// 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()); + } +} |
