//===----------------------------------------------------------------------===// // // 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/NamedValuesSchema.h" #include "llvm/CAS/ObjectStore.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::cas; TEST(NamedValuesSchemaTest, Basic) { std::unique_ptr CAS1 = createInMemoryCAS(); std::unique_ptr CAS2 = createInMemoryCAS(); auto createBlobInBoth = [&](StringRef Content) { std::optional H1, H2; EXPECT_THAT_ERROR(CAS1->storeFromString({}, Content).moveInto(H1), Succeeded()); EXPECT_THAT_ERROR(CAS2->storeFromString({}, Content).moveInto(H2), Succeeded()); EXPECT_EQ(CAS1->getID(*H1), CAS2->getID(*H2)); return *H1; }; ObjectRef Blob1 = createBlobInBoth("blob1"); ObjectRef Blob2 = createBlobInBoth("blob2"); ObjectRef Blob3 = createBlobInBoth("blob3"); SmallVector, 0> FlatEntries = { {}, {NamedValuesEntry("regular", Blob1)}, {NamedValuesEntry("executable", Blob2)}, {NamedValuesEntry("symlink", Blob3)}, { NamedValuesEntry("various", Blob1), NamedValuesEntry("names", Blob1), NamedValuesEntry("that", Blob1), NamedValuesEntry("do", Blob1), NamedValuesEntry("not", Blob1), NamedValuesEntry("conflict", Blob1), NamedValuesEntry("but have spaces and...", Blob1), NamedValuesEntry("`~,!@#$%^&*()-+=[]{}\\<>'\"", Blob1), }, }; SmallVector FlatRefs; SmallVector FlatIDs; NamedValuesSchema Schema1 = cantFail(NamedValuesSchema::create(*CAS1)); for (ArrayRef Entries : FlatEntries) { std::optional H; ASSERT_THAT_ERROR(Schema1.construct(Entries).moveInto(H), Succeeded()); FlatIDs.push_back(H->getID()); FlatRefs.push_back(H->getRef()); } // Confirm we get the same IDs the second time and that the trees can be // visited (the entries themselves will be checked later). for (int I = 0, E = FlatIDs.size(); I != E; ++I) { std::optional H; ASSERT_THAT_ERROR(Schema1.construct(FlatEntries[I]).moveInto(H), Succeeded()); EXPECT_EQ(FlatRefs[I], CAS1->getReference(*H)); std::optional Value; ASSERT_THAT_ERROR(Schema1.load(*H).moveInto(Value), Succeeded()); EXPECT_EQ(FlatEntries[I].size(), Value->size()); size_t NumCalls = 0; EXPECT_THAT_ERROR( Value->forEachEntry([&NumCalls](const NamedValuesEntry &E) { ++NumCalls; return Error::success(); }), Succeeded()); EXPECT_EQ(FlatEntries[I].size(), NumCalls); } // Run validation. for (int I = 1, E = FlatIDs.size(); I != E; ++I) ASSERT_THAT_ERROR(CAS1->validateObject(FlatIDs[I]), Succeeded()); // Confirm these trees don't exist in a fresh CAS instance. Skip the first // tree, which is empty and could be implicitly in some CAS. for (int I = 1, E = FlatIDs.size(); I != E; ++I) EXPECT_FALSE(CAS2->getReference(FlatIDs[I])); // Insert into the other CAS and confirm the IDs are stable. for (int I = FlatIDs.size(), E = 0; I != E; --I) { for (ObjectStore *CAS : {&*CAS1, &*CAS2}) { NamedValuesSchema Schema = cantFail(NamedValuesSchema::create(*CAS)); auto &ID = FlatIDs[I - 1]; // Make a copy of the original entries and sort them. SmallVector NewEntries; for (const NamedValuesEntry &Entry : FlatEntries[I - 1]) { std::optional NewRef = CAS->getReference(CAS1->getID(Entry.Ref)); ASSERT_TRUE(NewRef); NewEntries.emplace_back(Entry.Name, *NewRef); } llvm::sort(NewEntries); // Confirm we get the same tree out of CAS2. { std::optional Value; ASSERT_THAT_ERROR(Schema.construct(NewEntries).moveInto(Value), Succeeded()); EXPECT_EQ(ID, Value->getID()); } // Check that the correct entries come back. std::optional Ref = CAS->getReference(ID); ASSERT_TRUE(Ref); std::optional Value; ASSERT_THAT_ERROR(Schema.load(*Ref).moveInto(Value), Succeeded()); for (int I = 0, E = NewEntries.size(); I != E; ++I) EXPECT_EQ(NewEntries[I], Value->get(I)); } } // Create some nested trees. SmallVector Nested = FlatRefs; for (int I = 0, E = FlatEntries.size() * 3; I != E; ++I) { // Copy one of the flat entries and add some trees. auto OriginalEntries = ArrayRef(FlatEntries[I % FlatEntries.size()]); SmallVector Entries(OriginalEntries.begin(), OriginalEntries.end()); std::string Name = ("tree" + Twine(I)).str(); Entries.emplace_back( Name, *CAS1->getReference(FlatIDs[(I + 4) % FlatIDs.size()])); std::optional Name1, Name2; if (Nested.size() >= 2) { int Nested1 = I % Nested.size(); int Nested2 = (I * 3 + 2) % Nested.size(); if (Nested2 == Nested1) Nested2 = (Nested1 + 1) % Nested.size(); ASSERT_NE(Nested1, Nested2); Name1.emplace(("tree" + Twine(I) + "-" + Twine(Nested1)).str()); Name2.emplace(("tree" + Twine(I) + "-" + Twine(Nested2)).str()); Entries.emplace_back(*Name1, Nested[I % Nested.size()]); Entries.emplace_back(*Name2, Nested[(I * 3 + 2) % Nested.size()]); } std::optional ID; { std::optional Value; ASSERT_THAT_ERROR(Schema1.construct(Entries).moveInto(Value), Succeeded()); ID = Value->getID(); } llvm::sort(Entries); for (ObjectStore *CAS : {&*CAS1, &*CAS2}) { NamedValuesSchema Schema = cantFail(NamedValuesSchema::create(*CAS)); // Make a copy of the original entries and sort them. SmallVector NewEntries; for (const NamedValuesEntry &Entry : Entries) { std::optional NewRef = CAS->getReference(CAS1->getID(Entry.Ref)); ASSERT_TRUE(NewRef); NewEntries.emplace_back(Entry.Name, *NewRef); } llvm::sort(NewEntries); std::optional Value; ASSERT_THAT_ERROR(Schema.construct(NewEntries).moveInto(Value), Succeeded()); ASSERT_EQ(*ID, Value->getID()); ASSERT_THAT_ERROR(CAS->validateObject(*ID), Succeeded()); Value.reset(); std::optional Ref = CAS->getReference(*ID); ASSERT_TRUE(Ref); ASSERT_THAT_ERROR(Schema.load(*Ref).moveInto(Value), Succeeded()); for (int I = 0, E = NewEntries.size(); I != E; ++I) EXPECT_EQ(NewEntries[I], Value->get(I)); } } } TEST(NamedValuesSchemaTest, Lookup) { std::unique_ptr CAS = createInMemoryCAS(); std::optional Node; EXPECT_THAT_ERROR(CAS->storeFromString({}, "blob").moveInto(Node), Succeeded()); ObjectRef Blob = *Node; SmallVector FlatEntries = { NamedValuesEntry("e", Blob), NamedValuesEntry("b", Blob), NamedValuesEntry("f", Blob), NamedValuesEntry("a", Blob), NamedValuesEntry("c", Blob), NamedValuesEntry("d", Blob), }; std::optional Value; NamedValuesSchema Schema = cantFail(NamedValuesSchema::create(*CAS)); ASSERT_THAT_ERROR(Schema.construct(FlatEntries).moveInto(Value), Succeeded()); ASSERT_EQ(Value->size(), (size_t)6); auto CheckEntry = [&](StringRef Name) { auto MaybeEntry = Value->lookup(Name); ASSERT_TRUE(MaybeEntry); ASSERT_EQ(MaybeEntry->Name, Name); }; CheckEntry("a"); CheckEntry("b"); CheckEntry("c"); CheckEntry("d"); CheckEntry("e"); CheckEntry("f"); ASSERT_FALSE(Value->lookup("h")); } TEST(NamedValuesSchemaTest, Builder) { std::unique_ptr CAS = createInMemoryCAS(); ObjectRef Blob = cantFail(CAS->storeFromString({}, "")); NamedValuesSchema::Builder Builder(*CAS); Builder.add("a", Blob); Builder.add("a", Blob); ASSERT_THAT_EXPECTED(Builder.build(), Failed()); NamedValuesSchema::Builder Builder2(*CAS); Builder2.add("a", Blob); Builder2.add("b", Blob); ASSERT_THAT_EXPECTED(Builder2.build(), Succeeded()); } TEST(NamedValuesSchemaTest, forEachEntry) { std::unique_ptr CAS = createInMemoryCAS(); auto make = [&](StringRef Content) { return cantFail(CAS->storeFromString({}, Content)); }; std::pair ExpectedEntries[] = { {"i", "blob2"}, {"l", "blob1"}, {"s", "blob3"}, {"t", "blob4"}, }; NamedValuesSchema::Builder Builder(*CAS); for (auto &E : ExpectedEntries) Builder.add(E.first, make(E.second)); std::optional Root; ASSERT_THAT_ERROR(Builder.build().moveInto(Root), Succeeded()); auto RemainingEntries = ArrayRef(ExpectedEntries); NamedValuesSchema Schema = cantFail(NamedValuesSchema::create(*CAS)); std::optional Loaded; ASSERT_THAT_ERROR(Schema.load(*Root).moveInto(Loaded), Succeeded()); Error E = Loaded->forEachEntry([&](const NamedValuesEntry &Entry) -> Error { if (RemainingEntries.empty()) return createStringError(inconvertibleErrorCode(), "unexpected entry: '" + Entry.Name + "'"); auto ExpectedEntry = RemainingEntries.front(); RemainingEntries = RemainingEntries.drop_front(); EXPECT_EQ(ExpectedEntry.first, Entry.Name); EXPECT_EQ(make(ExpectedEntry.second), Entry.Ref); return Error::success(); }); EXPECT_THAT_ERROR(std::move(E), Succeeded()); }