//===- unittest/Support/YAMLRemarksSerializerTest.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
//
//===----------------------------------------------------------------------===//

#include "llvm/Remarks/Remark.h"
#include "llvm/Remarks/RemarkParser.h"
#include "llvm/Remarks/YAMLRemarkSerializer.h"
#include "llvm/Support/Error.h"
#include "gtest/gtest.h"

// We need to supprt Windows paths as well. In order to have paths with the same
// length, use a different path according to the platform.
#ifdef _WIN32
#define EXTERNALFILETESTPATH "C:/externalfi"
#else
#define EXTERNALFILETESTPATH "/externalfile"
#endif

using namespace llvm;

static void check(remarks::Format SerializerFormat,
                  remarks::SerializerMode Mode, ArrayRef<remarks::Remark> Rs,
                  StringRef ExpectedR, Optional<StringRef> ExpectedMeta,
                  Optional<remarks::StringTable> StrTab = None) {
  std::string Buf;
  raw_string_ostream OS(Buf);
  Expected<std::unique_ptr<remarks::RemarkSerializer>> MaybeS = [&] {
    if (StrTab)
      return createRemarkSerializer(SerializerFormat, Mode, OS,
                                    std::move(*StrTab));
    else
      return createRemarkSerializer(SerializerFormat, Mode, OS);
  }();
  EXPECT_FALSE(errorToBool(MaybeS.takeError()));
  std::unique_ptr<remarks::RemarkSerializer> S = std::move(*MaybeS);

  for (const remarks::Remark &R : Rs)
    S->emit(R);
  EXPECT_EQ(OS.str(), ExpectedR);

  if (ExpectedMeta) {
    Buf.clear();
    std::unique_ptr<remarks::MetaSerializer> MS =
        S->metaSerializer(OS, StringRef(EXTERNALFILETESTPATH));
    MS->emit();
    EXPECT_EQ(OS.str(), *ExpectedMeta);
  }
}

static void check(remarks::Format SerializerFormat, const remarks::Remark &R,
                  StringRef ExpectedR, StringRef ExpectedMeta,
                  Optional<remarks::StringTable> StrTab = None) {
  return check(SerializerFormat, remarks::SerializerMode::Separate,
               makeArrayRef(&R, &R + 1), ExpectedR, ExpectedMeta,
               std::move(StrTab));
}

static void checkStandalone(remarks::Format SerializerFormat,
                            const remarks::Remark &R, StringRef ExpectedR,
                            Optional<remarks::StringTable> StrTab = None) {
  return check(SerializerFormat, remarks::SerializerMode::Standalone,
               makeArrayRef(&R, &R + 1), ExpectedR,
               /*ExpectedMeta=*/None, std::move(StrTab));
}

TEST(YAMLRemarks, SerializerRemark) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  check(remarks::Format::YAML, R,
        "--- !Missed\n"
        "Pass:            pass\n"
        "Name:            name\n"
        "DebugLoc:        { File: path, Line: 3, Column: 4 }\n"
        "Function:        func\n"
        "Hotness:         5\n"
        "Args:\n"
        "  - key:             value\n"
        "  - keydebug:        valuedebug\n"
        "    DebugLoc:        { File: argpath, Line: 6, Column: 7 }\n"
        "...\n",
        StringRef("REMARKS\0"
                  "\0\0\0\0\0\0\0\0"
                  "\0\0\0\0\0\0\0\0" EXTERNALFILETESTPATH "\0",
                  38));
}

TEST(YAMLRemarks, SerializerRemarkStandalone) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  checkStandalone(
      remarks::Format::YAML, R,
      StringRef("--- !Missed\n"
                "Pass:            pass\n"
                "Name:            name\n"
                "DebugLoc:        { File: path, Line: 3, Column: 4 }\n"
                "Function:        func\n"
                "Hotness:         5\n"
                "Args:\n"
                "  - key:             value\n"
                "  - keydebug:        valuedebug\n"
                "    DebugLoc:        { File: argpath, Line: 6, Column: 7 }\n"
                "...\n"));
}

TEST(YAMLRemarks, SerializerRemarkStrTab) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  check(remarks::Format::YAMLStrTab, R,
        "--- !Missed\n"
        "Pass:            0\n"
        "Name:            1\n"
        "DebugLoc:        { File: 3, Line: 3, Column: 4 }\n"
        "Function:        2\n"
        "Hotness:         5\n"
        "Args:\n"
        "  - key:             4\n"
        "  - keydebug:        5\n"
        "    DebugLoc:        { File: 6, Line: 6, Column: 7 }\n"
        "...\n",
        StringRef("REMARKS\0"
                  "\0\0\0\0\0\0\0\0"
                  "\x2d\0\0\0\0\0\0\0"
                  "pass\0name\0func\0path\0value\0valuedebug\0argpath"
                  "\0" EXTERNALFILETESTPATH "\0",
                  83));
}

TEST(YAMLRemarks, SerializerRemarkParsedStrTab) {
  StringRef StrTab("pass\0name\0func\0path\0value\0valuedebug\0argpath\0", 45);
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  check(remarks::Format::YAMLStrTab, R,
        "--- !Missed\n"
        "Pass:            0\n"
        "Name:            1\n"
        "DebugLoc:        { File: 3, Line: 3, Column: 4 }\n"
        "Function:        2\n"
        "Hotness:         5\n"
        "Args:\n"
        "  - key:             4\n"
        "  - keydebug:        5\n"
        "    DebugLoc:        { File: 6, Line: 6, Column: 7 }\n"
        "...\n",
        StringRef("REMARKS\0"
                  "\0\0\0\0\0\0\0\0"
                  "\x2d\0\0\0\0\0\0\0"
                  "pass\0name\0func\0path\0value\0valuedebug\0argpath"
                  "\0" EXTERNALFILETESTPATH "\0",
                  83),
        remarks::StringTable(remarks::ParsedStringTable(StrTab)));
}

TEST(YAMLRemarks, SerializerRemarkParsedStrTabStandaloneNoStrTab) {
  // Check that we don't use the string table even if it was provided.
  StringRef StrTab("pass\0name\0func\0path\0value\0valuedebug\0argpath\0", 45);
  remarks::ParsedStringTable ParsedStrTab(StrTab);
  remarks::StringTable PreFilledStrTab(ParsedStrTab);
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  checkStandalone(
      remarks::Format::YAML, R,
      StringRef("--- !Missed\n"
                "Pass:            pass\n"
                "Name:            name\n"
                "DebugLoc:        { File: path, Line: 3, Column: 4 }\n"
                "Function:        func\n"
                "Hotness:         5\n"
                "Args:\n"
                "  - key:             value\n"
                "  - keydebug:        valuedebug\n"
                "    DebugLoc:        { File: argpath, Line: 6, Column: 7 }\n"
                "...\n"),
      std::move(PreFilledStrTab));
}

TEST(YAMLRemarks, SerializerRemarkParsedStrTabStandalone) {
  StringRef StrTab("pass\0name\0func\0path\0value\0valuedebug\0argpath\0", 45);
  remarks::ParsedStringTable ParsedStrTab(StrTab);
  remarks::StringTable PreFilledStrTab(ParsedStrTab);
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  checkStandalone(
      remarks::Format::YAMLStrTab, R,
      StringRef("REMARKS\0"
                "\0\0\0\0\0\0\0\0"
                "\x2d\0\0\0\0\0\0\0"
                "pass\0name\0func\0path\0value\0valuedebug\0argpath\0"
                "--- !Missed\n"
                "Pass:            0\n"
                "Name:            1\n"
                "DebugLoc:        { File: 3, Line: 3, Column: 4 }\n"
                "Function:        2\n"
                "Hotness:         5\n"
                "Args:\n"
                "  - key:             4\n"
                "  - keydebug:        5\n"
                "    DebugLoc:        { File: 6, Line: 6, Column: 7 }\n"
                "...\n",
                315),
      std::move(PreFilledStrTab));
}

TEST(YAMLRemarks, SerializerRemarkParsedStrTabStandaloneMultipleRemarks) {
  StringRef StrTab("pass\0name\0func\0path\0value\0valuedebug\0argpath\0", 45);
  remarks::ParsedStringTable ParsedStrTab(StrTab);
  remarks::StringTable PreFilledStrTab(ParsedStrTab);
  SmallVector<remarks::Remark, 2> Rs;
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "name";
  R.FunctionName = "func";
  R.Loc = remarks::RemarkLocation{"path", 3, 4};
  R.Hotness = 5;
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.emplace_back();
  R.Args.back().Key = "keydebug";
  R.Args.back().Val = "valuedebug";
  R.Args.back().Loc = remarks::RemarkLocation{"argpath", 6, 7};
  Rs.emplace_back(R.clone());
  Rs.emplace_back(std::move(R));
  check(remarks::Format::YAMLStrTab, remarks::SerializerMode::Standalone, Rs,
        StringRef("REMARKS\0"
                  "\0\0\0\0\0\0\0\0"
                  "\x2d\0\0\0\0\0\0\0"
                  "pass\0name\0func\0path\0value\0valuedebug\0argpath\0"
                  "--- !Missed\n"
                  "Pass:            0\n"
                  "Name:            1\n"
                  "DebugLoc:        { File: 3, Line: 3, Column: 4 }\n"
                  "Function:        2\n"
                  "Hotness:         5\n"
                  "Args:\n"
                  "  - key:             4\n"
                  "  - keydebug:        5\n"
                  "    DebugLoc:        { File: 6, Line: 6, Column: 7 }\n"
                  "...\n"
                  "--- !Missed\n"
                  "Pass:            0\n"
                  "Name:            1\n"
                  "DebugLoc:        { File: 3, Line: 3, Column: 4 }\n"
                  "Function:        2\n"
                  "Hotness:         5\n"
                  "Args:\n"
                  "  - key:             4\n"
                  "  - keydebug:        5\n"
                  "    DebugLoc:        { File: 6, Line: 6, Column: 7 }\n"
                  "...\n",
                  561),
        /*ExpectedMeta=*/None, std::move(PreFilledStrTab));
}
