/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "virtual_runtime_test.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <link.h>
#include <random>
#include <sys/mman.h>

#include <hilog/log.h>
#include "mingw_adapter.h"
#include "symbols_file_test.h"

using namespace testing::ext;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
class VirtualRuntimeTest : public testing::Test {
public:
    static void SetUpTestCase(void);
    static void TearDownTestCase(void);
    void SetUp();
    void TearDown();
    const std::string TEST_LOG_MESSAGE = "<HELLO_TEST_LOG_MESSAGE>";
    void LogLevelTest(std::vector<std::string> args, DebugLevel level);
    std::default_random_engine rnd_;
    std::unique_ptr<VirtualRuntime> runtime_;
    bool RecordCallBack(PerfEventRecord& record);
    size_t callbackCount_ = 0;

    void PrepareKernelSymbol();
    void PrepareUserSymbol();
};

void VirtualRuntimeTest::SetUpTestCase()
{
    DebugLogger::GetInstance()->OpenLog(DEFAULT_UT_LOG_DIR + "VirtualRuntimeTest.txt");
}

void VirtualRuntimeTest::TearDownTestCase()
{
    DebugLogger::GetInstance()->RestoreLog();
}

void VirtualRuntimeTest::SetUp()
{
    runtime_ = std::make_unique<VirtualRuntime>();
    callbackCount_ = 0;
}

void VirtualRuntimeTest::TearDown()
{
    runtime_.reset();
}

bool VirtualRuntimeTest::RecordCallBack(PerfEventRecord& record)
{
    callbackCount_++;
    printf("callbackCount_ %zu: type %d\n", callbackCount_, record.GetType());
    return true;
}

/**
 * @tc.name: SetRecordMode
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, SetRecordMode, TestSize.Level1)
{
    auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);

    EXPECT_EQ(runtime_->threadManager_->recordCallBack_, nullptr);
    runtime_->SetRecordMode(callBack);
    EXPECT_NE(runtime_->threadManager_->recordCallBack_, nullptr);
}

/**
 * @tc.name: SetRecordMode
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UpdateFromRecord, TestSize.Level1)
{
    HLOGD("Func2:%s", __FUNCTION__);
    PerfRecordComm recordComm(false, -1, -2, "3");
    PerfEventRecord &record = static_cast<PerfEventRecord &>(recordComm);
    auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);

    runtime_->SetRecordMode(callBack);
    runtime_->UpdateFromRecord(record);
    // one is -1 the other is -2
    EXPECT_EQ(callbackCount_, 2u);
}

/**
 * @tc.name: UpdateKernelSymbols
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UpdateKernelSymbols, TestSize.Level1)
{
    runtime_->UpdateKernelSymbols();
    if (access("/sys/kernel/notes", F_OK) == 0) {
        EXPECT_EQ(runtime_->symbolsFiles_.size(), 1u);
    } else {
        printf("cannot access /sys/kernel/notes\n");
    }
}

/**
 * @tc.name: UpdateKernelModulesSymbols
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UpdateKernelModulesSymbols, TestSize.Level1)
{
    runtime_->UpdateKernelModulesSpaceMaps();
    runtime_->UpdateKernelModulesSymbols();
    std::string modulesMap = ReadFileToString("/proc/modules");
    size_t lines = std::count(modulesMap.begin(), modulesMap.end(), '\n');
    std::set<std::string> modulesCount;
    if (runtime_->mapManager_->kernelSpaceMemMaps_.size() == 0) {
        lines = 0;
    }
    EXPECT_EQ(runtime_->mapManager_->kernelSpaceMemMaps_.size(), lines);
    int hasBuildId = 0;
    int noBuildId = 0;
    for (const std::unique_ptr<SymbolsFile> &symbolsFile : runtime_->GetSymbolsFiles()) {
        if (symbolsFile->GetBuildId().empty()) {
            noBuildId++;
        } else {
            hasBuildId++;
        }
    }
    printf("no BuildId: %d hasBuildId: %d\n", noBuildId, hasBuildId);
}

/**
 * @tc.name: SetSymbolsPaths
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, SetSymbolsPaths, TestSize.Level1)
{
    std::vector<std::string> symbolsSearchPaths;
    runtime_->SetSymbolsPaths(symbolsSearchPaths);

    symbolsSearchPaths.clear();
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);

    symbolsSearchPaths.clear();
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);

    symbolsSearchPaths.clear();
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    symbolsSearchPaths.push_back(PATH_DATA_TEMP);
    symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
    EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);
}

/**
 * @tc.name: GetSymbolsFiles
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, GetSymbolsFiles, TestSize.Level1)
{
    EXPECT_EQ(runtime_->GetSymbolsFiles().size(), 0u);
    runtime_->UpdateKernelSymbols();
    if (access("/sys/kernel/notes", F_OK) == 0) {
        EXPECT_EQ(runtime_->GetSymbolsFiles().size(), 1u);
    } else {
        printf("cannot access /sys/kernel/notes\n");
    }
}

/**
 * @tc.name: SetCallStackExpend
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, SetCallStackExpend, TestSize.Level1)
{
    runtime_->SetCallStackExpend(true);
    EXPECT_EQ(runtime_->callStackProcessor_->callstackMergeLevel_, true);
    runtime_->SetCallStackExpend(false);
    EXPECT_EQ(runtime_->callStackProcessor_->callstackMergeLevel_, false);
}

/**
 * @tc.name: SetDisableUnwind
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, SetDisableUnwind, TestSize.Level1)
{
    runtime_->SetDisableUnwind(true);
    EXPECT_EQ(runtime_->callStackProcessor_->disableUnwind_, true);
    runtime_->SetDisableUnwind(false);
    EXPECT_EQ(runtime_->callStackProcessor_->disableUnwind_, false);
}

/**
 * @tc.name: ReadThreadName_Truncate_Substr
 * @tc.desc: Validate truncation at '\0' using substr and removal of CR/LF
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, ReadThreadName_Truncate_Substr, TestSize.Level1)
{
    std::string raw = "commName\0extra\r\n";
    size_t nullPos = raw.find('\0');
    std::string comm = raw;
    if (nullPos != std::string::npos) {
        comm.resize(nullPos);
    }
    comm.erase(std::remove(comm.begin(), comm.end(), '\r'), comm.end());
    comm.erase(std::remove(comm.begin(), comm.end(), '\n'), comm.end());
    EXPECT_EQ(comm, "commName");
}

namespace {
constexpr const pid_t testTid = 1;
constexpr const uint64_t testUserVaddr = 0x1000;
constexpr const uint64_t testKernelVaddr = testUserVaddr / 4;
constexpr const uint64_t testKernelLen = testUserVaddr / 2;
constexpr const uint64_t testUserMapBegin = 0x2000;
constexpr const uint64_t testUserMapLen = 0x4000;
} // namespace

void VirtualRuntimeTest::PrepareKernelSymbol()
{
    std::string kernelSymbol = "kernel_symbol";
    auto kernel = SymbolsFile::CreateSymbolsFile(SYMBOL_KERNEL_FILE);
    kernel->filePath_ = kernelSymbol;
    kernel->symbols_.emplace_back(testKernelVaddr, 1u, "first_kernel_func", kernel->filePath_);
    kernel->symbols_.emplace_back(testKernelVaddr + 1u, 1u, "second_kernel_func",
                                  kernel->filePath_);
    runtime_->symbolsFiles_.emplace_back(std::move(kernel));

    auto &kernelMap = runtime_->mapManager_->kernelSpaceMemMaps_.emplace_back();
    kernelMap.name = kernelSymbol;
    kernelMap.begin = 0;
    kernelMap.end = 0 + testKernelLen;
    kernelMap.offset = 0;
}

void VirtualRuntimeTest::PrepareUserSymbol()
{
    std::string userSymbol = "user_symbol";
    auto user = SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE);
    user->filePath_ = userSymbol;
    user->symbols_.emplace_back(testUserVaddr, 1u, "first_user_func", user->filePath_);
    user->symbols_.emplace_back(testUserVaddr + 1u, 1u, "second_user_func", user->filePath_);
    user->textExecVaddrFileOffset_ = testUserVaddr;
    user->textExecVaddr_ = testUserVaddr;
    user->debugInfoLoadResult_ = true;
    runtime_->symbolsFiles_.emplace_back(std::move(user));

    VirtualThread &thread = runtime_->GetThread(testTid, testTid);
    thread.CreateMapItem(userSymbol, testUserMapBegin, testUserMapLen, 0);
}

/**
 * @tc.name: GetSymbol
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, GetSymbol, TestSize.Level0)
{
    DfxSymbol symbol;
    PrepareKernelSymbol();
    PrepareUserSymbol();

    ScopeDebugLevel tempLogLevel(LEVEL_MUCH);

    symbol = runtime_->GetSymbol(0u, testTid, testTid);
    EXPECT_EQ(symbol.IsValid(), false);

    symbol = runtime_->GetSymbol(testKernelVaddr, testTid, testTid);
    // in kernel
    EXPECT_EQ(symbol.IsValid(), true);
    EXPECT_EQ(symbol.funcVaddr_, testKernelVaddr);
    EXPECT_STREQ(symbol.name_.data(), "first_kernel_func");

    symbol = runtime_->GetSymbol(testUserVaddr + testUserMapBegin, testTid, testTid);
    // in user
    EXPECT_EQ(symbol.IsValid(), true);
    EXPECT_EQ(symbol.funcVaddr_, testUserVaddr);
    EXPECT_STREQ(symbol.name_.data(), "first_user_func");
}

/**
 * @tc.name: GetThread
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, GetThread, TestSize.Level1)
{
    runtime_->GetThread(1, 2);
    runtime_->GetThread(3, 4);
    runtime_->GetThread(5, 6);
    // runtime have 0 thread, so here need +1u
    EXPECT_EQ(runtime_->GetThreads().size(), 7u);
    if (HasFailure()) {
        for (auto &pair : runtime_->GetThreads()) {
            printf("pid %d tid %d\n", pair.second.pid_, pair.second.tid_);
        }
    }
}

/**
 * @tc.name: UpdateFromPerfData
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UpdateFromPerfData, TestSize.Level1)
{
    std::vector<SymbolFileStruct> symbolFileStructs;
    SymbolFileStruct &symbolFileStruct = symbolFileStructs.emplace_back();
    symbolFileStruct.filePath_ = "a";
    symbolFileStruct.textExecVaddr_ = testUserVaddr;
    symbolFileStruct.textExecVaddrFileOffset_ = testUserVaddr;
    symbolFileStruct.buildId_ = "b";
    symbolFileStruct.symbolStructs_.emplace_back(testUserVaddr, 1u, "first_user_func");
    symbolFileStruct.symbolStructs_.emplace_back(testUserVaddr + 1u, 1u, "second_user_func");

    ASSERT_EQ(runtime_->SetSymbolsPaths({"/data/local/tmp"}), true);
    runtime_->UpdateFromPerfData(symbolFileStructs);
    ASSERT_EQ(runtime_->GetSymbolsFiles().size(), 1u);
    ASSERT_STREQ(runtime_->GetSymbolsFiles().front()->filePath_.c_str(), "a");
    ASSERT_STREQ(runtime_->GetSymbolsFiles().front()->GetBuildId().c_str(), "b");
    ASSERT_EQ(runtime_->GetSymbolsFiles().front()->GetSymbols().size(), 2u);
}

/**
 * @tc.name: UnwindFromRecord
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UnwindFromRecord, TestSize.Level0)
{
    // symbol
    auto &symbolsFile = runtime_->symbolsFiles_.emplace_back(
        SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, TEST_DWARF_ELF));
    ASSERT_EQ(symbolsFile->setSymbolsFilePath(PATH_RESOURCE_TEST_DWARF_DATA), true);
    ASSERT_EQ(symbolsFile->LoadSymbols(nullptr, TEST_DWARF_ELF), true);
    symbolsFile->filePath_ = TEST_DWARF_ELF;

    // thread
    VirtualThread &thread = runtime_->GetThread(TEST_DWARF_RECORD_PID, TEST_DWARF_RECORD_TID);
    MakeMaps(thread);

    // record
    std::vector<uint8_t> data;
    LoadFromFile(PATH_RESOURCE_TEST_DWARF_DATA + TEST_DWARF_RECORD, data);
    ASSERT_NE(data.size(), 0u);
    perf_event_attr attr;
    if (memset_s(&attr, sizeof(perf_event_attr), 0, sizeof(perf_event_attr)) != 0) {
        printf("memset_s perf_event_attr failed");
    }
    attr.sample_type = TEST_RECORD_SAMPLE_TYPE;
    attr.sample_regs_user = TEST_DWARF_RECORD_REGS_USER;
    PerfRecordSample sample;
    sample.Init(data.data(), attr);
    sample.DumpLog("UnwindFromRecord");
    ASSERT_EQ(sample.data_.stack_size, TEST_DWARF_RECORD_STACK_SIZE);

    // unwind
    runtime_->UnwindFromRecord(sample);
    ASSERT_LE(TEST_RECORD_CALLSTACK_IP_FUNC.size(), sample.callFrames_.size());
    for (size_t i = 0; i < TEST_RECORD_CALLSTACK_IP_FUNC.size(); i++) {
        EXPECT_EQ(TEST_RECORD_CALLSTACK_IP_FUNC[i].first, sample.callFrames_[i].funcOffset);
        EXPECT_STREQ(TEST_RECORD_CALLSTACK_IP_FUNC[i].second.data(),
                     sample.callFrames_[i].funcName.data());
    }
}

/**
 * @tc.name: Update
 * @tc.desc: Test update maps and symbols
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, Update, TestSize.Level2)
{
    EXPECT_EQ(runtime_->IsKernelThread(0), false);
    runtime_->UpdateServiceSpaceMaps();
    runtime_->UpdateDevhostSpaceMaps();
    runtime_->UpdateServiceSymbols();
    runtime_->UpdateDevhostSymbols();
}

/**
 * @tc.name: ClearSymbolCache
 * @tc.desc: Test Clear Symbol Cache
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, ClearSymbolCache, TestSize.Level2)
{
    VirtualRuntime runtime;
    runtime.kernelSymbolLoader_->SetSymbolsPaths({"abc", "def"});
    runtime.recordProcessor_->SetSymbolsPaths({"abc", "def"});
    runtime.symbolsFiles_.emplace_back(SymbolsFile::CreateSymbolsFile(SYMBOL_KERNEL_FILE));
    runtime.ClearSymbolCache();
    EXPECT_EQ(runtime.threadManager_->GetThreads().size(), 0u);
    EXPECT_EQ(runtime.mapManager_->GetKernelMaps().size(), 0u);
    EXPECT_EQ(runtime.callStackProcessor_->GetUniStackTable()->size(), 0u);
    EXPECT_EQ(runtime.symbolsFiles_.size(), 0u);
    EXPECT_EQ(runtime.kernelSymbolLoader_->GetSymbolsPaths().size(), 0u);
    EXPECT_EQ(runtime.recordProcessor_->symbolsPaths_.size(), 0u);
}

/**
 * @tc.name: UpdateHapSymbolsWithNull
 * @tc.desc: Test UpdateHapSymbols with null
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, UpdateHapSymbolsWithNull, TestSize.Level2)
{
    EXPECT_FALSE(runtime_->recordProcessor_->UpdateHapSymbols(nullptr));
}

HWTEST_F(VirtualRuntimeTest, UpdateProcessSymbols, TestSize.Level2)
{
    std::vector<std::unique_ptr<SymbolsFile>> files;
    pid_t pid = getpid();
    VirtualThread thread(pid, files);
    thread.ParseMap();
    EXPECT_EQ(runtime_->IsKernelThread(0), false);
    auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);
    runtime_->SetRecordMode(callBack);
    runtime_->recordProcessor_->UpdateProcessSymbols(thread, pid);
}

/**
 * @tc.name: CheckGetElfByMap
 * @tc.desc: Test case for GetElfByMap to get elf
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, CheckGetElfByMap, TestSize.Level2)
{
    std::vector<std::unique_ptr<SymbolsFile>> files;
    VirtualThread thread(getpid(), files);
    thread.ParseMap();
    auto& maps = thread.GetMaps();
    for (const auto& map : maps) {
        if (OHOS::HiviewDFX::DfxMaps::IsLegalMapItem(map->name)) {
            EXPECT_NE(GetElfByMap(map), nullptr);
            break;
        }
    }
}

/**
 * @tc.name: CheckGetElfByInvalidMap
 * @tc.desc: Test case for GetElfByMap to get elf fail
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, CheckGetElfByInvalidMap, TestSize.Level2)
{
    std::string filename = "";
    uint64_t begin = rnd_();
    uint64_t len = rnd_();
    uint64_t offset = rnd_();
    uint32_t prot = rnd_();
    std::shared_ptr<DfxMap> map = std::make_shared<DfxMap>(begin, begin + len, offset, prot, filename);
    EXPECT_EQ(GetElfByMap(map), nullptr);
}
/**
 * @tc.name: TestSetSmoFlag
 * @tc.desc: Test SetSmoFlag function
 * @tc.type: FUNC
 */
HWTEST_F(VirtualRuntimeTest, TestSetSmoFlag, TestSize.Level2)
{
    VirtualRuntime runtime;

    // Test setting true
    runtime.SetSmoFlag(true);
    EXPECT_TRUE(runtime.GetRuntimeContext().smoFlag);

    // Test setting false
    runtime.SetSmoFlag(false);
    EXPECT_FALSE(runtime.GetRuntimeContext().smoFlag);
}

} // namespace HiPerf
} // namespace Developtools
} // namespace OHOS