================ @@ -0,0 +1,1817 @@ +//===- unittests/Analysis/Scalable/JSONFormatTest.cpp --------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Unit tests for SSAF JSON serialization format reading and writing. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Scalable/Serialization/JSONFormat.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Registry.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +using namespace clang::ssaf; +using namespace llvm; +using ::testing::AllOf; +using ::testing::HasSubstr; + +namespace { + +// ============================================================================ +// Test Analysis - Simple analysis for testing JSON serialization +// ============================================================================ + +struct PairsEntitySummaryForJSONFormatTest final : EntitySummary { + + SummaryName getSummaryName() const override { + return SummaryName("PairsEntitySummaryForJSONFormatTest"); + } + + std::vector<std::pair<EntityId, EntityId>> Pairs; +}; + +static json::Object serializePairsEntitySummaryForJSONFormatTest( + const EntitySummary &Summary, + const JSONFormat::EntityIdConverter &Converter) { + const auto &TA = + static_cast<const PairsEntitySummaryForJSONFormatTest &>(Summary); + json::Array PairsArray; + for (const auto &[First, Second] : TA.Pairs) { + PairsArray.push_back(json::Object{ + {"first", Converter.toJSON(First)}, + {"second", Converter.toJSON(Second)}, + }); + } + return json::Object{{"pairs", std::move(PairsArray)}}; +} + +static Expected<std::unique_ptr<EntitySummary>> +deserializePairsEntitySummaryForJSONFormatTest( + const json::Object &Obj, EntityIdTable &IdTable, + const JSONFormat::EntityIdConverter &Converter) { + auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>(); + const json::Array *PairsArray = Obj.getArray("pairs"); + if (!PairsArray) + return createStringError(inconvertibleErrorCode(), + "missing or invalid field 'pairs'"); + for (const auto &[Index, Value] : llvm::enumerate(*PairsArray)) { + const json::Object *Pair = Value.getAsObject(); + if (!Pair) + return createStringError( + inconvertibleErrorCode(), + "pairs element at index %zu is not a JSON object", Index); + auto FirstOpt = Pair->getInteger("first"); + if (!FirstOpt) + return createStringError( + inconvertibleErrorCode(), + "missing or invalid 'first' field at index '%zu'", Index); + auto SecondOpt = Pair->getInteger("second"); + if (!SecondOpt) + return createStringError( + inconvertibleErrorCode(), + "missing or invalid 'second' field at index '%zu'", Index); + Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt), + Converter.fromJSON(*SecondOpt)); + } + return std::move(Result); +} + +struct PairsEntitySummaryForJSONFormatTestFormatInfo : JSONFormat::FormatInfo { + PairsEntitySummaryForJSONFormatTestFormatInfo() + : JSONFormat::FormatInfo( + SummaryName("PairsEntitySummaryForJSONFormatTest"), + serializePairsEntitySummaryForJSONFormatTest, + deserializePairsEntitySummaryForJSONFormatTest) {} +}; + +static llvm::Registry<JSONFormat::FormatInfo>::Add< + PairsEntitySummaryForJSONFormatTestFormatInfo> + RegisterPairsEntitySummaryForJSONFormatTest( + "PairsEntitySummaryForJSONFormatTest", + "Format info for PairsArrayEntitySummary"); + +// ============================================================================ +// Test Fixture +// ============================================================================ + +class JSONFormatTest : public ::testing::Test { +public: + using PathString = SmallString<128>; + +protected: + SmallString<128> TestDir; + + void SetUp() override { + std::error_code EC = + sys::fs::createUniqueDirectory("json-format-test", TestDir); + ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message(); + } + + void TearDown() override { sys::fs::remove_directories(TestDir); } + + PathString makePath(StringRef FileOrDirectoryName) const { + PathString FullPath = TestDir; + sys::path::append(FullPath, FileOrDirectoryName); + + return FullPath; + } + + PathString makePath(StringRef Dir, StringRef FileName) const { + PathString FullPath = TestDir; + sys::path::append(FullPath, Dir, FileName); + + return FullPath; + } + + Expected<PathString> makeDirectory(StringRef DirectoryName) const { + PathString DirPath = makePath(DirectoryName); + + std::error_code EC = sys::fs::create_directory(DirPath); + if (EC) { + return createStringError(EC, "Failed to create directory '%s': %s", + DirPath.c_str(), EC.message().c_str()); + } + + return DirPath; + } + + Expected<PathString> makeSymlink(StringRef TargetFileName, + StringRef SymlinkFileName) const { + PathString TargetPath = makePath(TargetFileName); + PathString SymlinkPath = makePath(SymlinkFileName); + + std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath); + if (EC) { + return createStringError(EC, "Failed to create symlink '%s' -> '%s': %s", + SymlinkPath.c_str(), TargetPath.c_str(), + EC.message().c_str()); + } + + return SymlinkPath; + } + + llvm::Error setPermission(StringRef FileName, + const sys::fs::perms Perms) const { + PathString Path = makePath(FileName); + + std::error_code EC = sys::fs::setPermissions(Path, Perms); + if (EC) { + return createStringError(EC, "Failed to set permissions on '%s': %s", + Path.c_str(), EC.message().c_str()); + } + + return llvm::Error::success(); + } + + Expected<json::Value> readJSONFromFile(StringRef FileName) const { + PathString FilePath = makePath(FileName); + + auto BufferOrError = MemoryBuffer::getFile(FilePath); + if (!BufferOrError) { + return createStringError(BufferOrError.getError(), + "Failed to read file: %s", FilePath.c_str()); + } + + Expected<json::Value> ExpectedValue = + json::parse(BufferOrError.get()->getBuffer()); + if (!ExpectedValue) + return ExpectedValue.takeError(); + + return *ExpectedValue; + } + + Expected<PathString> writeJSON(StringRef JSON, StringRef FileName) const { + PathString FilePath = makePath(FileName); + + std::error_code EC; + raw_fd_ostream OS(FilePath, EC); + if (EC) { + return createStringError(EC, "Failed to create file '%s': %s", + FilePath.c_str(), EC.message().c_str()); + } + + OS << JSON; + OS.close(); + + if (OS.has_error()) { + return createStringError(OS.error(), "Failed to write to file '%s': %s", + FilePath.c_str(), OS.error().message().c_str()); + } + + return FilePath; + } + + llvm::Expected<TUSummary> readTUSummaryFromFile(StringRef FileName) const { + PathString FilePath = makePath(FileName); + + return JSONFormat().readTUSummary(FilePath); + } + + llvm::Expected<TUSummary> + readTUSummaryFromString(StringRef JSON, + StringRef FileName = "test.json") const { + auto ExpectedFilePath = writeJSON(JSON, FileName); + if (!ExpectedFilePath) + return ExpectedFilePath.takeError(); + + return readTUSummaryFromFile(FileName); + } + + llvm::Error writeTUSummary(const TUSummary &Summary, + StringRef FileName) const { + PathString FilePath = makePath(FileName); + + return JSONFormat().writeTUSummary(Summary, FilePath); + } + + // Normalize TUSummary JSON by sorting id_table by id field + static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) { + auto *Obj = Val.getAsObject(); + if (!Obj) { + return createStringError( + inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: expected an object"); + } + + auto *IDTable = Obj->getArray("id_table"); + if (!IDTable) { + return createStringError(inconvertibleErrorCode(), + "Cannot normalize TUSummary JSON: 'id_table' " + "field is either missing or has the wrong type"); + } + + // Sort id_table entries by the "id" field to ensure deterministic ordering + // for comparison + std::sort(IDTable->begin(), IDTable->end(), + [](const json::Value &A, const json::Value &B) { + const auto *AObj = A.getAsObject(); + const auto *BObj = B.getAsObject(); + if (!AObj || !BObj) + return false; + + auto AID = AObj->getInteger("id"); + auto BID = BObj->getInteger("id"); + if (!AID || !BID) + return false; + + return *AID < *BID; + }); + + return Val; + } + + // Compare two TUSummary JSON values with normalization + static Expected<bool> compareTUSummaryJSON(json::Value A, json::Value B) { + auto ExpectedNormalizedA = normalizeTUSummaryJSON(std::move(A)); + if (!ExpectedNormalizedA) + return ExpectedNormalizedA.takeError(); + + auto ExpectedNormalizedB = normalizeTUSummaryJSON(std::move(B)); + if (!ExpectedNormalizedB) + return ExpectedNormalizedB.takeError(); + + return *ExpectedNormalizedA == *ExpectedNormalizedB; + } + + void readWriteCompareTUSummary(StringRef JSON) const { + const PathString InputFileName("input.json"); + const PathString OutputFileName("output.json"); + + auto ExpectedInputFilePath = writeJSON(JSON, InputFileName); + ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded()); + + auto ExpectedTUSummary = readTUSummaryFromFile(InputFileName); + ASSERT_THAT_EXPECTED(ExpectedTUSummary, Succeeded()); + + auto WriteError = writeTUSummary(*ExpectedTUSummary, OutputFileName); + ASSERT_THAT_ERROR(std::move(WriteError), Succeeded()) + << "Failed to write to file: " << OutputFileName; + + auto ExpectedInputJSON = readJSONFromFile(InputFileName); + ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded()); + auto ExpectedOutputJSON = readJSONFromFile(OutputFileName); + ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded()); + + auto ExpectedComparisonResult = + compareTUSummaryJSON(*ExpectedInputJSON, *ExpectedOutputJSON); + ASSERT_THAT_EXPECTED(ExpectedComparisonResult, Succeeded()) + << "Failed to normalize JSON for comparison"; + + if (!*ExpectedComparisonResult) { + auto ExpectedNormalizedInput = normalizeTUSummaryJSON(*ExpectedInputJSON); + auto ExpectedNormalizedOutput = + normalizeTUSummaryJSON(*ExpectedOutputJSON); + FAIL() << "Serialization is broken: input JSON is different from output " + "json\n" + << "Input: " + << (ExpectedNormalizedInput + ? llvm::formatv("{0:2}", *ExpectedNormalizedInput).str() + : "normalization failed") + << "\n" + << "Output: " + << (ExpectedNormalizedOutput + ? llvm::formatv("{0:2}", *ExpectedNormalizedOutput).str() + : "normalization failed"); + } + } +}; + +// ============================================================================ +// readJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, NonexistentFile) { + auto Result = readTUSummaryFromFile("nonexistent.json"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"), + HasSubstr("file does not exist")))); +} + +TEST_F(JSONFormatTest, PathIsDirectory) { + PathString DirName("test_directory.json"); + + auto ExpectedDirPath = makeDirectory(DirName); + ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded()); + + auto Result = readTUSummaryFromFile(DirName); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"), + HasSubstr("path is a directory, not a file")))); +} + +TEST_F(JSONFormatTest, NotJsonExtension) { + PathString FileName("test.txt"); + + auto ExpectedFilePath = writeJSON("{}", FileName); + ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded()); + + auto Result = readTUSummaryFromFile(FileName); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read file"), + HasSubstr("file does not end with '.json' extension")))); +} + +TEST_F(JSONFormatTest, BrokenSymlink) { + // Create a symlink pointing to a non-existent file + auto ExpectedSymlinkPath = + makeSymlink("nonexistent_target.json", "broken_symlink.json"); + ASSERT_THAT_EXPECTED(ExpectedSymlinkPath, Succeeded()); + + auto Result = readTUSummaryFromFile(*ExpectedSymlinkPath); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read file")))); +} + +TEST_F(JSONFormatTest, NoReadPermission) { +#ifdef _WIN32 + GTEST_SKIP() << "Permission model differs on Windows"; +#endif + + PathString FileName("no-read-permission.json"); + + auto ExpectedFilePath = writeJSON(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [], + "data": [] + })", + FileName); + ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded()); + + auto PermError = setPermission(FileName, sys::fs::perms::owner_write | + sys::fs::perms::owner_exe); + ASSERT_THAT_ERROR(std::move(PermError), Succeeded()); + + auto Result = readTUSummaryFromFile(FileName); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read file")))); + + // Restore permissions for cleanup + auto RestoreError = setPermission(FileName, sys::fs::perms::all_all); + EXPECT_THAT_ERROR(std::move(RestoreError), Succeeded()); +} + +TEST_F(JSONFormatTest, InvalidSyntax) { + auto Result = readTUSummaryFromString("{ invalid json }"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("Expected object key")))); +} + +TEST_F(JSONFormatTest, NotObject) { + auto Result = readTUSummaryFromString("[]"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read TUSummary"), + HasSubstr("expected JSON object")))); +} + +// ============================================================================ +// JSONFormat::buildNamespaceKindFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, InvalidKind) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "invalid_kind", + "name": "test.cpp" + }, + "id_table": [], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading BuildNamespace from field 'tu_namespace'"), + HasSubstr("reading BuildNamespaceKind from field 'kind'"), + HasSubstr( + "invalid 'kind' BuildNamespaceKind value 'invalid_kind'")))); +} + +// ============================================================================ +// JSONFormat::buildNamespaceFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, MissingKind) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "name": "test.cpp" + }, + "id_table": [], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading BuildNamespace from field 'tu_namespace'"), + HasSubstr("failed to read BuildNamespaceKind from field 'kind'"), + HasSubstr("expected JSON string")))); +} + +TEST_F(JSONFormatTest, MissingName) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit" + }, + "id_table": [], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading BuildNamespace from field 'tu_namespace'"), + HasSubstr("failed to read BuildNamespaceName from field 'name'"), + HasSubstr("expected JSON string")))); +} + +// ============================================================================ +// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, NamespaceElementNotObject) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": ["invalid"] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("reading NestedBuildNamespace from field 'namespace'"), + HasSubstr("failed to read BuildNamespace from index '0'"), + HasSubstr("expected JSON object")))); +} + +TEST_F(JSONFormatTest, NamespaceElementMissingKind) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [ + { + "name": "test.cpp" + } + ] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("reading NestedBuildNamespace from field 'namespace'"), + HasSubstr("reading BuildNamespace from index '0'"), + HasSubstr("failed to read BuildNamespaceKind from field 'kind'"), + HasSubstr("expected JSON string")))); +} + +TEST_F(JSONFormatTest, NamespaceElementInvalidKind) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [ + { + "kind": "invalid_kind", + "name": "test.cpp" + } + ] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("reading NestedBuildNamespace from field 'namespace'"), + HasSubstr("reading BuildNamespace from index '0'"), + HasSubstr("reading BuildNamespaceKind from field 'kind'"), + HasSubstr( + "invalid 'kind' BuildNamespaceKind value 'invalid_kind'")))); +} + +TEST_F(JSONFormatTest, NamespaceElementMissingName) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [ + { + "kind": "compilation_unit" + } + ] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("reading NestedBuildNamespace from field 'namespace'"), + HasSubstr("reading BuildNamespace from index '0'"), + HasSubstr("failed to read BuildNamespaceName from field 'name'"), + HasSubstr("expected JSON string")))); +} + +// ============================================================================ +// JSONFormat::entityNameFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, EntityNameMissingUSR) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "suffix": "", + "namespace": [] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("failed to read USR from field 'usr'"), + HasSubstr("expected JSON string")))); +} + +TEST_F(JSONFormatTest, EntityNameMissingSuffix) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "namespace": [] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr("failed to read Suffix from field 'suffix'"), + HasSubstr("expected JSON string")))); +} + +TEST_F(JSONFormatTest, EntityNameMissingNamespace) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "" + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("reading EntityName from field 'name'"), + HasSubstr( + "failed to read NestedBuildNamespace from field 'namespace'"), + HasSubstr("expected JSON array")))); +} + +// ============================================================================ +// JSONFormat::entityIdTableEntryFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, IDTableEntryMissingID) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("failed to read EntityId from field 'id'"), + HasSubstr("expected JSON (unsigned 64-bit)")))); +} + +TEST_F(JSONFormatTest, IDTableEntryMissingName) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0 + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("failed to read EntityName from field 'name'"), + HasSubstr("expected JSON object")))); +} + +TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": "not_a_number", + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("reading EntityIdTable entry from index '0'"), + HasSubstr("failed to read EntityId from field 'id'"), + HasSubstr("expected JSON integer (unsigned 64-bit)")))); +} + +// ============================================================================ +// JSONFormat::entityIdTableFromJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, IDTableNotArray) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": {}, + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf( + HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read IdTable from field 'id_table'"), + HasSubstr("expected JSON array")))); +} + +TEST_F(JSONFormatTest, IDTableElementNotObject) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [123], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("failed to read EntityIdTable entry from index '0'"), + HasSubstr("expected JSON object")))); +} + +TEST_F(JSONFormatTest, DuplicateEntity) { + auto Result = readTUSummaryFromString(R"({ + "tu_namespace": { + "kind": "compilation_unit", + "name": "test.cpp" + }, + "id_table": [ + { + "id": 0, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [ + { + "kind": "compilation_unit", + "name": "test.cpp" + } + ] + } + }, + { + "id": 1, + "name": { + "usr": "c:@F@foo", + "suffix": "", + "namespace": [ + { + "kind": "compilation_unit", + "name": "test.cpp" + } + ] + } + } + ], + "data": [] + })"); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage( + AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("reading IdTable from field 'id_table'"), + HasSubstr("failed to insert EntityIdTable entry at index '1'"), + HasSubstr("encountered duplicate EntityId '0'")))); ---------------- steakhal wrote:
I found this error a bit puzzling because it sort of implied to me that in the `id_table` we have two entries of which the `id` field are the same (duplicate). However, what the error wanted to convey was that the same (USR, namespace, suffix) triplet are the same as entity ID 0. I think there is some room for improvement here, but if we anyway want to revisit the error message stack, I think we can do it later. https://github.com/llvm/llvm-project/pull/180021 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
