brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.2 KiB · b43ae33 Raw
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