================
@@ -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

Reply via email to