| //===----------------------------------------------------------------------===// |
| // |
| // 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/Support/Program.h" |
| #include "llvm/CAS/MappedFileRegionArena.h" |
| #include "llvm/Config/llvm-config.h" |
| #include "llvm/Support/ConvertUTF.h" |
| #include "llvm/Support/ExponentialBackoff.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/ThreadPool.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest.h" |
| #if defined(__APPLE__) |
| #include <crt_externs.h> |
| #elif !defined(_MSC_VER) |
| // Forward declare environ in case it's not provided by stdlib.h. |
| extern char **environ; |
| #endif |
| |
| using namespace llvm; |
| using namespace llvm::cas; |
| |
| extern const char *TestMainArgv0; |
| static char ProgramID = 0; |
| |
| class CASProgramTest : public testing::Test { |
| std::vector<StringRef> EnvTable; |
| std::vector<std::string> EnvStorage; |
| |
| protected: |
| void SetUp() override { |
| auto EnvP = [] { |
| #if defined(_WIN32) |
| _wgetenv(L"TMP"); // Populate _wenviron, initially is null |
| return _wenviron; |
| #elif defined(__APPLE__) |
| return *_NSGetEnviron(); |
| #else |
| return environ; |
| #endif |
| }(); |
| ASSERT_TRUE(EnvP); |
| |
| auto prepareEnvVar = [this](decltype(*EnvP) Var) -> StringRef { |
| #if defined(_WIN32) |
| // On Windows convert UTF16 encoded variable to UTF8 |
| auto Len = wcslen(Var); |
| ArrayRef<char> Ref{reinterpret_cast<char const *>(Var), |
| Len * sizeof(*Var)}; |
| EnvStorage.emplace_back(); |
| auto convStatus = llvm::convertUTF16ToUTF8String(Ref, EnvStorage.back()); |
| EXPECT_TRUE(convStatus); |
| return EnvStorage.back(); |
| #else |
| (void)this; |
| return StringRef(Var); |
| #endif |
| }; |
| |
| while (*EnvP != nullptr) { |
| auto S = prepareEnvVar(*EnvP); |
| if (!StringRef(S).starts_with("GTEST_")) |
| EnvTable.emplace_back(S); |
| ++EnvP; |
| } |
| } |
| |
| void TearDown() override { |
| EnvTable.clear(); |
| EnvStorage.clear(); |
| } |
| |
| void addEnvVar(StringRef Var) { EnvTable.emplace_back(Var); } |
| |
| ArrayRef<StringRef> getEnviron() const { return EnvTable; } |
| }; |
| |
| #if LLVM_ENABLE_ONDISK_CAS |
| |
| static Error emptyConstructor(MappedFileRegionArena &) { |
| return Error::success(); |
| } |
| |
| TEST_F(CASProgramTest, MappedFileRegionArenaTest) { |
| auto TestAllocator = [](StringRef Path) { |
| std::optional<MappedFileRegionArena> Alloc; |
| ASSERT_THAT_ERROR( |
| MappedFileRegionArena::create(Path, /*Capacity=*/10 * 1024 * 1024, |
| /*HeaderOffset=*/0, emptyConstructor) |
| .moveInto(Alloc), |
| Succeeded()); |
| |
| std::vector<unsigned *> AllocatedPtr; |
| AllocatedPtr.resize(100); |
| DefaultThreadPool Threads; |
| for (unsigned I = 0; I < 100; ++I) { |
| Threads.async( |
| [&](unsigned Idx) { |
| // Allocate a buffer that is larger than needed so allocator hits |
| // additional pages for test coverage. |
| unsigned *P = (unsigned *)cantFail(Alloc->allocate(100)); |
| *P = Idx; |
| AllocatedPtr[Idx] = P; |
| }, |
| I); |
| } |
| |
| Threads.wait(); |
| for (unsigned I = 0; I < 100; ++I) |
| EXPECT_EQ(*AllocatedPtr[I], I); |
| }; |
| |
| if (const char *File = getenv("LLVM_CAS_TEST_MAPPED_FILE_REGION")) { |
| TestAllocator(File); |
| exit(0); |
| } |
| |
| SmallString<128> FilePath; |
| sys::fs::createUniqueDirectory("MappedFileRegionArena", FilePath); |
| sys::path::append(FilePath, "allocation-file"); |
| |
| std::string Executable = |
| sys::fs::getMainExecutable(TestMainArgv0, &ProgramID); |
| StringRef Argv[] = { |
| Executable, "--gtest_filter=CASProgramTest.MappedFileRegionArenaTest"}; |
| |
| // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child. |
| std::string EnvVar = "LLVM_CAS_TEST_MAPPED_FILE_REGION="; |
| EnvVar += FilePath.str(); |
| addEnvVar(EnvVar); |
| |
| std::string Error; |
| bool ExecutionFailed; |
| sys::ProcessInfo PI = sys::ExecuteNoWait(Executable, Argv, getEnviron(), {}, |
| 0, &Error, &ExecutionFailed); |
| TestAllocator(FilePath); |
| |
| ASSERT_FALSE(ExecutionFailed) << Error; |
| ASSERT_NE(PI.Pid, sys::ProcessInfo::InvalidPid) << "Invalid process id"; |
| PI = llvm::sys::Wait(PI, /*SecondsToWait=*/5, &Error); |
| ASSERT_TRUE(PI.ReturnCode == 0); |
| ASSERT_TRUE(Error.empty()); |
| |
| // Clean up after both processes finish testing. |
| sys::fs::remove(FilePath); |
| sys::fs::remove_directories(sys::path::parent_path(FilePath)); |
| } |
| |
| TEST_F(CASProgramTest, MappedFileRegionArenaSizeTest) { |
| using namespace std::chrono_literals; |
| if (const char *File = getenv("LLVM_CAS_TEST_MAPPED_FILE_REGION")) { |
| ExponentialBackoff Backoff(5s); |
| do { |
| if (sys::fs::exists(File)) { |
| break; |
| } |
| } while (Backoff.waitForNextAttempt()); |
| |
| std::optional<MappedFileRegionArena> Alloc; |
| ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/1024, |
| /*HeaderOffset=*/0, |
| emptyConstructor) |
| .moveInto(Alloc), |
| Succeeded()); |
| ASSERT_TRUE(Alloc->capacity() == 2048); |
| |
| Alloc.reset(); |
| ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/4096, |
| /*HeaderOffset=*/0, |
| emptyConstructor) |
| .moveInto(Alloc), |
| Succeeded()); |
| ASSERT_TRUE(Alloc->capacity() == 2048); |
| Alloc.reset(); |
| |
| ASSERT_THAT_ERROR( |
| MappedFileRegionArena::create(File, /*Capacity=*/2048, |
| /*HeaderOffset=*/32, emptyConstructor) |
| .moveInto(Alloc), |
| FailedWithMessage( |
| "specified header offset (32) does not match existing config (0)")); |
| |
| ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/2048, |
| /*HeaderOffset=*/0, |
| emptyConstructor) |
| .moveInto(Alloc), |
| Succeeded()); |
| |
| exit(0); |
| } |
| |
| SmallString<128> FilePath; |
| sys::fs::createUniqueDirectory("MappedFileRegionArena", FilePath); |
| sys::path::append(FilePath, "allocation-file"); |
| |
| std::string Executable = |
| sys::fs::getMainExecutable(TestMainArgv0, &ProgramID); |
| StringRef Argv[] = { |
| Executable, |
| "--gtest_filter=CASProgramTest.MappedFileRegionArenaSizeTest"}; |
| |
| // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child. |
| std::string EnvVar = "LLVM_CAS_TEST_MAPPED_FILE_REGION="; |
| EnvVar += FilePath.str(); |
| addEnvVar(EnvVar); |
| |
| std::optional<MappedFileRegionArena> Alloc; |
| ASSERT_THAT_ERROR(MappedFileRegionArena::create(FilePath, /*Capacity=*/2048, |
| /*HeaderOffset=*/0, |
| emptyConstructor) |
| .moveInto(Alloc), |
| Succeeded()); |
| |
| std::string Error; |
| bool ExecutionFailed; |
| sys::ProcessInfo PI = sys::ExecuteNoWait(Executable, Argv, getEnviron(), {}, |
| 0, &Error, &ExecutionFailed); |
| |
| ASSERT_FALSE(ExecutionFailed) << Error; |
| ASSERT_NE(PI.Pid, sys::ProcessInfo::InvalidPid) << "Invalid process id"; |
| PI = llvm::sys::Wait(PI, /*SecondsToWait=*/100, &Error); |
| ASSERT_TRUE(PI.ReturnCode == 0); |
| ASSERT_TRUE(Error.empty()); |
| |
| // Size is still the requested 2048. |
| ASSERT_TRUE(Alloc->capacity() == 2048); |
| |
| // Clean up after both processes finish testing. |
| sys::fs::remove(FilePath); |
| sys::fs::remove_directories(sys::path::parent_path(FilePath)); |
| } |
| |
| #endif // LLVM_ENABLE_ONDISK_CAS |