//===- unittest/Support/BitstreamRemarksSerializerTest.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/Bitcode/BitcodeAnalyzer.h"
#include "llvm/Remarks/BitstreamRemarkSerializer.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
#include <string>

// 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 checkAnalyze(StringRef Input, StringRef Expected) {
  std::string OutputBuf;
  raw_string_ostream OutputOS(OutputBuf);
  BCDumpOptions O(OutputOS);
  O.ShowBinaryBlobs = true;
  BitcodeAnalyzer BA(Input);
  EXPECT_FALSE(BA.analyze(O)); // Expect no errors.
  EXPECT_EQ(OutputOS.str(), Expected);
}

static void check(remarks::SerializerMode Mode, const remarks::Remark &R,
                  StringRef ExpectedR, Optional<StringRef> ExpectedMeta,
                  Optional<remarks::StringTable> StrTab) {
  // Emit the remark.
  std::string InputBuf;
  raw_string_ostream InputOS(InputBuf);
  Expected<std::unique_ptr<remarks::RemarkSerializer>> MaybeSerializer = [&] {
    if (StrTab)
      return createRemarkSerializer(remarks::Format::Bitstream, Mode, InputOS,
                                    std::move(*StrTab));
    else
      return createRemarkSerializer(remarks::Format::Bitstream, Mode, InputOS);
  }();
  EXPECT_FALSE(errorToBool(MaybeSerializer.takeError()));
  std::unique_ptr<remarks::RemarkSerializer> Serializer =
      std::move(*MaybeSerializer);
  Serializer->emit(R);

  // Analyze the serialized remark.
  checkAnalyze(InputOS.str(), ExpectedR);

  // Analyze the serialized metadata if it's not in standalone mode.
  if (ExpectedMeta) {
    std::string MetaBuf;
    raw_string_ostream MetaOS(MetaBuf);
    std::unique_ptr<remarks::MetaSerializer> MetaSerializer =
        Serializer->metaSerializer(MetaOS, StringRef(EXTERNALFILETESTPATH));
    MetaSerializer->emit();
    checkAnalyze(MetaOS.str(), *ExpectedMeta);
  }
}

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

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

TEST(BitstreamRemarkSerializer, SeparateRemarkFileNoOptionals) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=1 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=14 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileNoOptionalsSeparateStrTab) {
  remarks::StringTable StrTab;
  StrTab.add("function");
  StrTab.add("pass");
  StrTab.add("remark");
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=1 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=2 op2=1 op3=0/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=14 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'function\\x00pass\\x00remark\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n",
        std::move(StrTab));
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileDebugLoc) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Loc.emplace();
  R.Loc->SourceFilePath = "path";
  R.Loc->SourceLine = 99;
  R.Loc->SourceColumn = 55;
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=99 op2=55/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=15 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00path\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileHotness) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Hotness.emplace(999999999);
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=3 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "  <Remark hotness codeid=7 abbrevid=6 op0=999999999/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=14 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileArgNoDebugLoc) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=2 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "  <Argument codeid=9 abbrevid=8 op0=3 op1=4/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=16 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00key\\x00value\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileArgDebugLoc) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.back().Loc.emplace();
  R.Args.back().Loc->SourceFilePath = "path";
  R.Args.back().Loc->SourceLine = 99;
  R.Args.back().Loc->SourceColumn = 55;
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "  <Argument with debug location codeid=8 abbrevid=7 op0=3 op1=4 op2=5 "
        "op3=99 op4=55/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=17 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00key\\x00value\\x00path\\x00'\n"
        "  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, SeparateRemarkFileAll) {
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Loc.emplace();
  R.Loc->SourceFilePath = "path";
  R.Loc->SourceLine = 99;
  R.Loc->SourceColumn = 55;
  R.Hotness.emplace(999999999);
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.back().Loc.emplace();
  R.Args.back().Loc->SourceFilePath = "argpath";
  R.Args.back().Loc->SourceLine = 11;
  R.Args.back().Loc->SourceColumn = 66;
  check(R,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=3 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=1/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=8 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=0 op2=1 op3=2/>\n"
        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=99 op2=55/>\n"
        "  <Remark hotness codeid=7 abbrevid=6 op0=999999999/>\n"
        "  <Argument with debug location codeid=8 abbrevid=7 op0=4 op1=5 op2=6 "
        "op3=11 op4=66/>\n"
        "</Remark>\n",
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=19 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=0/>\n"
        "  <String table codeid=3 abbrevid=5/> blob data = "
        "'remark\\x00pass\\x00function\\x00path\\x00key\\x00value\\x00argpa"
        "th\\x00'\n  <External File codeid=4 abbrevid=6/> blob data = "
        "'" EXTERNALFILETESTPATH"'\n"
        "</Meta>\n");
}

TEST(BitstreamRemarkSerializer, Standalone) {
  // Pre-populate the string table.
  remarks::StringTable StrTab;
  StrTab.add("pass");
  StrTab.add("remark");
  StrTab.add("function");
  StrTab.add("path");
  StrTab.add("key");
  StrTab.add("value");
  StrTab.add("argpath");
  remarks::Remark R;
  R.RemarkType = remarks::Type::Missed;
  R.PassName = "pass";
  R.RemarkName = "remark";
  R.FunctionName = "function";
  R.Loc.emplace();
  R.Loc->SourceFilePath = "path";
  R.Loc->SourceLine = 99;
  R.Loc->SourceColumn = 55;
  R.Hotness.emplace(999999999);
  R.Args.emplace_back();
  R.Args.back().Key = "key";
  R.Args.back().Val = "value";
  R.Args.back().Loc.emplace();
  R.Args.back().Loc->SourceFilePath = "argpath";
  R.Args.back().Loc->SourceLine = 11;
  R.Args.back().Loc->SourceColumn = 66;
  checkStandalone(
      R,
      "<BLOCKINFO_BLOCK/>\n"
      "<Meta BlockID=8 NumWords=15 BlockCodeSize=3>\n"
      "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
      "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
      "  <String table codeid=3 abbrevid=6/> blob data = "
      "'pass\\x00remark\\x00function\\x00path\\x00key\\x00value\\x00argpath\\x0"
      "0'\n"
      "</Meta>\n"
      "<Remark BlockID=9 NumWords=8 BlockCodeSize=4>\n"
      "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
      "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=99 op2=55/>\n"
      "  <Remark hotness codeid=7 abbrevid=6 op0=999999999/>\n"
      "  <Argument with debug location codeid=8 abbrevid=7 op0=4 op1=5 op2=6 "
      "op3=11 op4=66/>\n"
      "</Remark>\n",
      std::move(StrTab));
}
