================ @@ -0,0 +1,1376 @@ +//===- 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/IntrusiveRefCntPtr.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/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#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 TestAnalysis : EntitySummary { + TestAnalysis() : EntitySummary(SummaryName("test_summary")) {} + std::vector<std::pair<EntityId, EntityId>> Pairs; +}; + +static json::Object +serializeTestAnalysis(const EntitySummary &Summary, + const JSONFormat::EntityIdConverter &Converter) { + const auto &TA = static_cast<const TestAnalysis &>(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>> +deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable, + const JSONFormat::EntityIdConverter &Converter) { + auto Result = std::make_unique<TestAnalysis>(); + const json::Array *PairsArray = Obj.getArray("pairs"); + if (!PairsArray) + return createStringError(inconvertibleErrorCode(), + "missing or invalid field 'pairs'"); + for (size_t I = 0; I < PairsArray->size(); ++I) { + const json::Object *Pair = (*PairsArray)[I].getAsObject(); + if (!Pair) + return createStringError( + inconvertibleErrorCode(), + "pairs element at index %zu is not a JSON object", I); + auto FirstOpt = Pair->getInteger("first"); + if (!FirstOpt) + return createStringError(inconvertibleErrorCode(), + "missing or invalid 'first' field at index '%zu'", + I); + auto SecondOpt = Pair->getInteger("second"); + if (!SecondOpt) + return createStringError(inconvertibleErrorCode(), + "missing or invalid 'second' field at index '%zu'", + I); + Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt), + Converter.fromJSON(*SecondOpt)); + } + return std::move(Result); +} + +struct TestAnalysisFormatInfo : JSONFormat::FormatInfo { + TestAnalysisFormatInfo() + : JSONFormat::FormatInfo(SummaryName("test_summary"), + serializeTestAnalysis, deserializeTestAnalysis) { + } +}; + +static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo> + RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data"); + +// ============================================================================ +// Test Fixture +// ============================================================================ + +class JSONFormatTest : public ::testing::Test { +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); } + + llvm::Expected<TUSummary> readJSON(StringRef JSON, + StringRef Filename = "test.json") { + SmallString<128> FilePath = TestDir; + sys::path::append(FilePath, Filename); + + std::error_code EC; + raw_fd_ostream OS(FilePath, EC); + EXPECT_FALSE(EC) << "Failed to create file: " << EC.message(); + OS << JSON; + OS.close(); + + auto Result = JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath); + if (!Result) { + std::string Message = llvm::toString(Result.takeError()); + llvm::outs() << Message << "\n\n"; + return {llvm::createStringError(std::move(Message))}; + } + return Result; + } + + void readWriteJSON(StringRef InputJSON) { + // Read the input JSON + auto Summary1 = readJSON(InputJSON, "input.json"); + ASSERT_THAT_EXPECTED(Summary1, Succeeded()); + + // Write to first output file + SmallString<128> Output1Path = TestDir; + sys::path::append(Output1Path, "output1.json"); + + JSONFormat Format(vfs::getRealFileSystem()); + auto WriteErr1 = Format.writeTUSummary(*Summary1, Output1Path); + ASSERT_THAT_ERROR(std::move(WriteErr1), Succeeded()); + + // Read back from first output + auto Summary2 = Format.readTUSummary(Output1Path); + ASSERT_THAT_EXPECTED(Summary2, Succeeded()); + + // Write to second output file + SmallString<128> Output2Path = TestDir; + sys::path::append(Output2Path, "output2.json"); + + auto WriteErr2 = Format.writeTUSummary(*Summary2, Output2Path); + ASSERT_THAT_ERROR(std::move(WriteErr2), Succeeded()); + + // Compare the two output files byte-by-byte + auto Buffer1 = MemoryBuffer::getFile(Output1Path); + ASSERT_TRUE(Buffer1) << "Failed to read output1.json"; + + auto Buffer2 = MemoryBuffer::getFile(Output2Path); + ASSERT_TRUE(Buffer2) << "Failed to read output2.json"; + + EXPECT_EQ(Buffer1.get()->getBuffer(), Buffer2.get()->getBuffer()) + << "Serialization is not stable: first write differs from second write"; + } +}; + +// ============================================================================ +// readJSON() Error Tests +// ============================================================================ + +TEST_F(JSONFormatTest, NonexistentFile) { + SmallString<128> NonexistentPath = TestDir; + sys::path::append(NonexistentPath, "nonexistent.json"); + + JSONFormat Format(vfs::getRealFileSystem()); + auto Result = Format.readTUSummary(NonexistentPath); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"), + HasSubstr("file does not exist")))); +} + +TEST_F(JSONFormatTest, PathIsDirectory) { + SmallString<128> DirPath = TestDir; + sys::path::append(DirPath, "test_directory.json"); + + std::error_code EC = sys::fs::create_directory(DirPath); + ASSERT_FALSE(EC) << "Failed to create directory: " << EC.message(); + + JSONFormat Format(vfs::getRealFileSystem()); + auto Result = Format.readTUSummary(DirPath); + + EXPECT_THAT_EXPECTED( + Result, + FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"), + HasSubstr("path is a directory, not a file")))); +} + +TEST_F(JSONFormatTest, NotJsonExtension) { + auto Result = readJSON("{}", "test.txt"); + + 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) { + SmallString<128> TargetPath = TestDir; + sys::path::append(TargetPath, "nonexistent_target.json"); + + SmallString<128> SymlinkPath = TestDir; + sys::path::append(SymlinkPath, "broken_symlink.json"); + + // Create a symlink pointing to a non-existent file + std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath); + ASSERT_FALSE(EC) << "Failed to create symlink: " << EC.message(); + + JSONFormat Format(vfs::getRealFileSystem()); + auto Result = Format.readTUSummary(SymlinkPath); + + EXPECT_THAT_EXPECTED( + Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"), + HasSubstr("failed to read file")))); +} + +TEST_F(JSONFormatTest, NoReadPermission) { +#ifndef _WIN32 // Skip on Windows as permission model is different ---------------- steakhal wrote:
I think it's cleaner with `GTEST_SKIP() << "reason";` The test would show up as "skipped", instead of passing. It reduces the indentation for the rest of the test. https://github.com/llvm/llvm-project/pull/180021 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
