457 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/CAS/ObjectStore.h"10#include "llvm/Config/llvm-config.h"11#include "llvm/Support/Process.h"12#include "llvm/Support/RandomNumberGenerator.h"13#include "llvm/Support/ThreadPool.h"14#include "llvm/Testing/Support/Error.h"15#include "gtest/gtest.h"16 17#include "CASTestConfig.h"18 19using namespace llvm;20using namespace llvm::cas;21 22TEST_P(CASTest, PrintIDs) {23 std::unique_ptr<ObjectStore> CAS = createObjectStore();24 25 std::optional<CASID> ID1, ID2;26 ASSERT_THAT_ERROR(CAS->createProxy({}, "1").moveInto(ID1), Succeeded());27 ASSERT_THAT_ERROR(CAS->createProxy({}, "2").moveInto(ID2), Succeeded());28 EXPECT_NE(ID1, ID2);29 std::string PrintedID1 = ID1->toString();30 std::string PrintedID2 = ID2->toString();31 EXPECT_NE(PrintedID1, PrintedID2);32 33 std::optional<CASID> ParsedID1, ParsedID2;34 ASSERT_THAT_ERROR(CAS->parseID(PrintedID1).moveInto(ParsedID1), Succeeded());35 ASSERT_THAT_ERROR(CAS->parseID(PrintedID2).moveInto(ParsedID2), Succeeded());36 EXPECT_EQ(ID1, ParsedID1);37 EXPECT_EQ(ID2, ParsedID2);38}39 40TEST_P(CASTest, Blobs) {41 std::unique_ptr<ObjectStore> CAS1 = createObjectStore();42 StringRef ContentStrings[] = {43 "word",44 "some longer text std::string's local memory",45 R"(multiline text multiline text multiline text multiline text46multiline text multiline text multiline text multiline text multiline text47multiline text multiline text multiline text multiline text multiline text48multiline text multiline text multiline text multiline text multiline text49multiline text multiline text multiline text multiline text multiline text50multiline text multiline text multiline text multiline text multiline text)",51 };52 53 SmallVector<CASID> IDs;54 for (StringRef Content : ContentStrings) {55 // Use StringRef::str() to create a temporary std::string. This could cause56 // problems if the CAS is storing references to the input string instead of57 // copying it.58 std::optional<ObjectProxy> Blob;59 ASSERT_THAT_ERROR(CAS1->createProxy({}, Content).moveInto(Blob),60 Succeeded());61 IDs.push_back(Blob->getID());62 63 // Check basic printing of IDs.64 EXPECT_EQ(IDs.back().toString(), IDs.back().toString());65 if (IDs.size() > 2)66 EXPECT_NE(IDs.front().toString(), IDs.back().toString());67 }68 69 // Check that the blobs give the same IDs later.70 for (int I = 0, E = IDs.size(); I != E; ++I) {71 std::optional<ObjectProxy> Blob;72 ASSERT_THAT_ERROR(CAS1->createProxy({}, ContentStrings[I]).moveInto(Blob),73 Succeeded());74 EXPECT_EQ(IDs[I], Blob->getID());75 }76 77 // Run validation on all CASIDs.78 for (int I = 0, E = IDs.size(); I != E; ++I)79 ASSERT_THAT_ERROR(CAS1->validateObject(IDs[I]), Succeeded());80 81 // Check that the blobs can be retrieved multiple times.82 for (int I = 0, E = IDs.size(); I != E; ++I) {83 for (int J = 0, JE = 3; J != JE; ++J) {84 std::optional<ObjectProxy> Buffer;85 ASSERT_THAT_ERROR(CAS1->getProxy(IDs[I]).moveInto(Buffer), Succeeded());86 EXPECT_EQ(ContentStrings[I], Buffer->getData());87 }88 }89 90 // Confirm these blobs don't exist in a fresh CAS instance.91 std::unique_ptr<ObjectStore> CAS2 = createObjectStore();92 for (int I = 0, E = IDs.size(); I != E; ++I) {93 std::optional<ObjectProxy> Proxy;94 EXPECT_THAT_ERROR(CAS2->getProxy(IDs[I]).moveInto(Proxy), Failed());95 }96 97 // Insert into the second CAS and confirm the IDs are stable. Getting them98 // should work now.99 for (int I = IDs.size(), E = 0; I != E; --I) {100 auto &ID = IDs[I - 1];101 auto &Content = ContentStrings[I - 1];102 std::optional<ObjectProxy> Blob;103 ASSERT_THAT_ERROR(CAS2->createProxy({}, Content).moveInto(Blob),104 Succeeded());105 EXPECT_EQ(ID, Blob->getID());106 107 std::optional<ObjectProxy> Buffer;108 ASSERT_THAT_ERROR(CAS2->getProxy(ID).moveInto(Buffer), Succeeded());109 EXPECT_EQ(Content, Buffer->getData());110 }111}112 113TEST_P(CASTest, BlobsBig) {114 // A little bit of validation that bigger blobs are okay. Climb up to 1MB.115 std::unique_ptr<ObjectStore> CAS = createObjectStore();116 SmallString<256> String1 = StringRef("a few words");117 SmallString<256> String2 = StringRef("others");118 while (String1.size() < 1024U * 1024U) {119 std::optional<CASID> ID1;120 std::optional<CASID> ID2;121 ASSERT_THAT_ERROR(CAS->createProxy({}, String1).moveInto(ID1), Succeeded());122 ASSERT_THAT_ERROR(CAS->createProxy({}, String1).moveInto(ID2), Succeeded());123 ASSERT_THAT_ERROR(CAS->validateObject(*ID1), Succeeded());124 ASSERT_THAT_ERROR(CAS->validateObject(*ID2), Succeeded());125 ASSERT_EQ(ID1, ID2);126 127 String1.append(String2);128 ASSERT_THAT_ERROR(CAS->createProxy({}, String2).moveInto(ID1), Succeeded());129 ASSERT_THAT_ERROR(CAS->createProxy({}, String2).moveInto(ID2), Succeeded());130 ASSERT_THAT_ERROR(CAS->validateObject(*ID1), Succeeded());131 ASSERT_THAT_ERROR(CAS->validateObject(*ID2), Succeeded());132 ASSERT_EQ(ID1, ID2);133 String2.append(String1);134 }135 136 // Specifically check near 1MB for objects large enough they're likely to be137 // stored externally in an on-disk CAS and will be near a page boundary.138 SmallString<0> Storage;139 const size_t InterestingSize = 1024U * 1024ULL;140 const size_t SizeE = InterestingSize + 2;141 if (Storage.size() < SizeE)142 Storage.resize(SizeE, '\01');143 for (size_t Size = InterestingSize - 2; Size != SizeE; ++Size) {144 StringRef Data(Storage.data(), Size);145 std::optional<ObjectProxy> Blob;146 ASSERT_THAT_ERROR(CAS->createProxy({}, Data).moveInto(Blob), Succeeded());147 ASSERT_EQ(Data, Blob->getData());148 ASSERT_EQ(0, Blob->getData().end()[0]);149 }150}151 152TEST_P(CASTest, LeafNodes) {153 std::unique_ptr<ObjectStore> CAS1 = createObjectStore();154 StringRef ContentStrings[] = {155 "word",156 "some longer text std::string's local memory",157 R"(multiline text multiline text multiline text multiline text158multiline text multiline text multiline text multiline text multiline text159multiline text multiline text multiline text multiline text multiline text160multiline text multiline text multiline text multiline text multiline text161multiline text multiline text multiline text multiline text multiline text162multiline text multiline text multiline text multiline text multiline text)",163 };164 165 SmallVector<ObjectRef> Nodes;166 SmallVector<CASID> IDs;167 for (StringRef Content : ContentStrings) {168 // Use StringRef::str() to create a temporary std::string. This could cause169 // problems if the CAS is storing references to the input string instead of170 // copying it.171 std::optional<ObjectRef> Node;172 ASSERT_THAT_ERROR(173 CAS1->store({}, arrayRefFromStringRef<char>(Content)).moveInto(Node),174 Succeeded());175 Nodes.push_back(*Node);176 177 // Check basic printing of IDs.178 IDs.push_back(CAS1->getID(*Node));179 EXPECT_EQ(IDs.back().toString(), IDs.back().toString());180 EXPECT_EQ(Nodes.front(), Nodes.front());181 EXPECT_EQ(Nodes.back(), Nodes.back());182 EXPECT_EQ(IDs.front(), IDs.front());183 EXPECT_EQ(IDs.back(), IDs.back());184 if (Nodes.size() <= 1)185 continue;186 EXPECT_NE(Nodes.front(), Nodes.back());187 EXPECT_NE(IDs.front(), IDs.back());188 }189 190 // Check that the blobs give the same IDs later.191 for (int I = 0, E = IDs.size(); I != E; ++I) {192 std::optional<ObjectRef> Node;193 ASSERT_THAT_ERROR(194 CAS1->store({}, arrayRefFromStringRef<char>(ContentStrings[I]))195 .moveInto(Node),196 Succeeded());197 EXPECT_EQ(IDs[I], CAS1->getID(*Node));198 }199 200 // Check that the blobs can be retrieved multiple times.201 for (int I = 0, E = IDs.size(); I != E; ++I) {202 for (int J = 0, JE = 3; J != JE; ++J) {203 std::optional<ObjectProxy> Object;204 ASSERT_THAT_ERROR(CAS1->getProxy(IDs[I]).moveInto(Object), Succeeded());205 ASSERT_TRUE(Object);206 EXPECT_EQ(ContentStrings[I], Object->getData());207 }208 }209 210 // Confirm these blobs don't exist in a fresh CAS instance.211 std::unique_ptr<ObjectStore> CAS2 = createObjectStore();212 for (int I = 0, E = IDs.size(); I != E; ++I) {213 std::optional<ObjectProxy> Object;214 EXPECT_THAT_ERROR(CAS2->getProxy(IDs[I]).moveInto(Object), Failed());215 }216 217 // Insert into the second CAS and confirm the IDs are stable. Getting them218 // should work now.219 for (int I = IDs.size(), E = 0; I != E; --I) {220 auto &ID = IDs[I - 1];221 auto &Content = ContentStrings[I - 1];222 std::optional<ObjectRef> Node;223 ASSERT_THAT_ERROR(224 CAS2->store({}, arrayRefFromStringRef<char>(Content)).moveInto(Node),225 Succeeded());226 EXPECT_EQ(ID, CAS2->getID(*Node));227 228 std::optional<ObjectProxy> Object;229 ASSERT_THAT_ERROR(CAS2->getProxy(ID).moveInto(Object), Succeeded());230 ASSERT_TRUE(Object);231 EXPECT_EQ(Content, Object->getData());232 }233}234 235TEST_P(CASTest, NodesBig) {236 std::unique_ptr<ObjectStore> CAS = createObjectStore();237 238 // Specifically check near 1MB for objects large enough they're likely to be239 // stored externally in an on-disk CAS, and such that one of them will be240 // near a page boundary.241 SmallString<0> Storage;242 constexpr size_t InterestingSize = 1024U * 1024ULL;243 constexpr size_t WordSize = sizeof(void *);244 245 // Start much smaller to account for headers.246 constexpr size_t SizeB = InterestingSize - 8 * WordSize;247 constexpr size_t SizeE = InterestingSize + 1;248 if (Storage.size() < SizeE)249 Storage.resize(SizeE, '\01');250 251 SmallVector<ObjectRef, 4> CreatedNodes;252 // Avoid checking every size because this is an expensive test. Just check253 // for data that is 8B-word-aligned, and one less. Also appending the created254 // nodes as the references in the next block to check references are created255 // correctly.256 for (size_t Size = SizeB; Size < SizeE; Size += WordSize) {257 for (bool IsAligned : {false, true}) {258 StringRef Data(Storage.data(), Size - (IsAligned ? 0 : 1));259 std::optional<ObjectProxy> Node;260 ASSERT_THAT_ERROR(CAS->createProxy(CreatedNodes, Data).moveInto(Node),261 Succeeded());262 ASSERT_EQ(Data, Node->getData());263 ASSERT_EQ(0, Node->getData().end()[0]);264 ASSERT_EQ(Node->getNumReferences(), CreatedNodes.size());265 CreatedNodes.emplace_back(Node->getRef());266 }267 }268 269 for (auto ID : CreatedNodes)270 ASSERT_THAT_ERROR(CAS->validateObject(CAS->getID(ID)), Succeeded());271}272 273#if LLVM_ENABLE_THREADS274/// Common test functionality for creating blobs in parallel. You can vary which275/// cas instances are the same or different, and the size of the created blobs.276static void testBlobsParallel(ObjectStore &Read1, ObjectStore &Read2,277 ObjectStore &Write1, ObjectStore &Write2,278 uint64_t BlobSize) {279 SCOPED_TRACE("testBlobsParallel");280 unsigned BlobCount = 100;281 std::vector<std::string> Blobs;282 Blobs.reserve(BlobCount);283 for (unsigned I = 0; I < BlobCount; ++I) {284 std::string Blob;285 Blob.resize(BlobSize);286 getRandomBytes(Blob.data(), BlobSize);287 Blobs.push_back(std::move(Blob));288 }289 290 std::mutex NodesMtx;291 std::vector<std::optional<CASID>> CreatedNodes(BlobCount);292 293 auto Producer = [&](unsigned I, ObjectStore *CAS) {294 std::optional<ObjectProxy> Node;295 EXPECT_THAT_ERROR(CAS->createProxy({}, Blobs[I]).moveInto(Node),296 Succeeded());297 {298 std::lock_guard<std::mutex> L(NodesMtx);299 CreatedNodes[I] = Node ? Node->getID() : CASID::getDenseMapTombstoneKey();300 }301 };302 303 auto Consumer = [&](unsigned I, ObjectStore *CAS) {304 std::optional<CASID> ID;305 while (!ID) {306 // Busy wait.307 std::lock_guard<std::mutex> L(NodesMtx);308 ID = CreatedNodes[I];309 }310 if (ID == CASID::getDenseMapTombstoneKey())311 // Producer failed; already reported.312 return;313 314 std::optional<ObjectProxy> Node;315 ASSERT_THAT_ERROR(CAS->getProxy(*ID).moveInto(Node), Succeeded());316 EXPECT_EQ(Node->getData(), Blobs[I]);317 };318 319 DefaultThreadPool Threads;320 for (unsigned I = 0; I < BlobCount; ++I) {321 Threads.async(Producer, I, &Write1);322 Threads.async(Producer, I, &Write2);323 Threads.async(Consumer, I, &Read1);324 Threads.async(Consumer, I, &Read2);325 }326 327 Threads.wait();328}329 330static void testBlobsParallel1(ObjectStore &CAS, uint64_t BlobSize) {331 SCOPED_TRACE("testBlobsParallel1");332 testBlobsParallel(CAS, CAS, CAS, CAS, BlobSize);333}334 335TEST_P(CASTest, BlobsParallel) {336 std::unique_ptr<ObjectStore> CAS = createObjectStore();337 uint64_t Size = 1ULL * 1024;338 ASSERT_NO_FATAL_FAILURE(testBlobsParallel1(*CAS, Size));339}340 341#ifdef EXPENSIVE_CHECKS342TEST_P(CASTest, BlobsBigParallel) {343 std::unique_ptr<ObjectStore> CAS = createObjectStore();344 // 100k is large enough to be standalone files in our on-disk cas.345 uint64_t Size = 100ULL * 1024;346 ASSERT_NO_FATAL_FAILURE(testBlobsParallel1(*CAS, Size));347}348#endif // EXPENSIVE_CHECKS349 350#ifndef _WIN32 // create_link won't work for directories on Windows351TEST_F(OnDiskCASTest, OnDiskCASBlobsParallelMultiCAS) {352 // This test intentionally uses symlinked paths to the same CAS to subvert the353 // shared memory mappings that would normally be created within a single354 // process. This breaks the lock file guarantees, so we must be careful not355 // to create or destroy the CAS objects concurrently, which is when the locks356 // are normally important.357 unittest::TempDir Temp("on-disk-cas", /*Unique=*/true);358 ASSERT_EQ(sys::fs::create_directory(Temp.path("real_cas")),359 std::error_code());360 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas1")),361 std::error_code());362 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas2")),363 std::error_code());364 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas3")),365 std::error_code());366 367 std::unique_ptr<ObjectStore> CAS1, CAS2, CAS3, CAS4;368 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("real_cas")).moveInto(CAS1),369 Succeeded());370 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas1")).moveInto(CAS2),371 Succeeded());372 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas2")).moveInto(CAS3),373 Succeeded());374 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas3")).moveInto(CAS4),375 Succeeded());376 377 uint64_t Size = 1ULL * 1024;378 ASSERT_NO_FATAL_FAILURE(testBlobsParallel(*CAS1, *CAS2, *CAS3, *CAS4, Size));379}380 381TEST_F(OnDiskCASTest, OnDiskCASBlobsBigParallelMultiCAS) {382 // See comment in BlobsParallelMultiCAS.383 unittest::TempDir Temp("on-disk-cas", /*Unique=*/true);384 ASSERT_EQ(sys::fs::create_directory(Temp.path("real_cas")),385 std::error_code());386 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas1")),387 std::error_code());388 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas2")),389 std::error_code());390 ASSERT_EQ(sys::fs::create_link("real_cas", Temp.path("sym_cas3")),391 std::error_code());392 393 std::unique_ptr<ObjectStore> CAS1, CAS2, CAS3, CAS4;394 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("real_cas")).moveInto(CAS1),395 Succeeded());396 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas1")).moveInto(CAS2),397 Succeeded());398 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas2")).moveInto(CAS3),399 Succeeded());400 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path("sym_cas3")).moveInto(CAS4),401 Succeeded());402 403 // 100k is large enough to be standalone files in our on-disk cas.404 uint64_t Size = 100ULL * 1024;405 ASSERT_NO_FATAL_FAILURE(testBlobsParallel(*CAS1, *CAS2, *CAS3, *CAS4, Size));406}407#endif // _WIN32408#endif // LLVM_ENABLE_THREADS409 410TEST_F(OnDiskCASTest, OnDiskCASDiskSize) {411 unittest::TempDir Temp("on-disk-cas", /*Unique=*/true);412 std::unique_ptr<ObjectStore> CAS;413 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path()).moveInto(CAS), Succeeded());414 415 uint64_t MaxSize = 100 * 1024 * 1024;416 417 // Check that we map the files to the correct size.418 auto CheckFileSizes = [&](bool Mapped) {419 bool FoundIndex = false, FoundData = false;420 std::error_code EC;421 for (sys::fs::directory_iterator I(Temp.path(), EC), E; I != E && !EC;422 I.increment(EC)) {423 StringRef Filename = sys::path::filename(I->path());424 if (Filename.starts_with("index.") && !Filename.ends_with(".shared")) {425 FoundIndex = true;426 ASSERT_TRUE(I->status());427 if (Mapped)428 EXPECT_EQ(I->status()->getSize(), MaxSize);429 else430 EXPECT_LT(I->status()->getSize(), MaxSize);431 }432 if (Filename.starts_with("data.") && !Filename.ends_with(".shared")) {433 FoundData = true;434 ASSERT_TRUE(I->status());435 if (Mapped)436 EXPECT_EQ(I->status()->getSize(), MaxSize);437 else438 EXPECT_LT(I->status()->getSize(), MaxSize);439 }440 }441 ASSERT_TRUE(FoundIndex);442 ASSERT_TRUE(FoundData);443 };444 445 // Check that we have the full mapping size when the CAS is open.446 CheckFileSizes(/*Mapped=*/true);447 CAS.reset();448 // Check that the CAS is shrunk to a smaller size.449 CheckFileSizes(/*Mapped=*/false);450 451 // Repeat the checks when starting from an existing CAS.452 ASSERT_THAT_ERROR(createOnDiskCAS(Temp.path()).moveInto(CAS), Succeeded());453 CheckFileSizes(/*Mapped=*/true);454 CAS.reset();455 CheckFileSizes(/*Mapped=*/false);456}457