240 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Support/Program.h"10#include "llvm/CAS/MappedFileRegionArena.h"11#include "llvm/Config/llvm-config.h"12#include "llvm/Support/ConvertUTF.h"13#include "llvm/Support/ExponentialBackoff.h"14#include "llvm/Support/FileSystem.h"15#include "llvm/Support/Path.h"16#include "llvm/Support/ThreadPool.h"17#include "llvm/Testing/Support/Error.h"18#include "gtest/gtest.h"19#if defined(__APPLE__)20#include <crt_externs.h>21#elif !defined(_MSC_VER)22// Forward declare environ in case it's not provided by stdlib.h.23extern char **environ;24#endif25 26using namespace llvm;27using namespace llvm::cas;28 29#if LLVM_ENABLE_ONDISK_CAS30 31extern const char *TestMainArgv0;32static char ProgramID = 0;33 34class CASProgramTest : public testing::Test {35 std::vector<StringRef> EnvTable;36 std::vector<std::string> EnvStorage;37 38protected:39 void SetUp() override {40 auto EnvP = [] {41#if defined(_WIN32)42 _wgetenv(L"TMP"); // Populate _wenviron, initially is null43 return _wenviron;44#elif defined(__APPLE__)45 return *_NSGetEnviron();46#else47 return environ;48#endif49 }();50 ASSERT_TRUE(EnvP);51 52 auto prepareEnvVar = [this](decltype(*EnvP) Var) -> StringRef {53#if defined(_WIN32)54 // On Windows convert UTF16 encoded variable to UTF855 auto Len = wcslen(Var);56 ArrayRef<char> Ref{reinterpret_cast<char const *>(Var),57 Len * sizeof(*Var)};58 EnvStorage.emplace_back();59 auto convStatus = llvm::convertUTF16ToUTF8String(Ref, EnvStorage.back());60 EXPECT_TRUE(convStatus);61 return EnvStorage.back();62#else63 (void)this;64 return StringRef(Var);65#endif66 };67 68 while (*EnvP != nullptr) {69 auto S = prepareEnvVar(*EnvP);70 if (!StringRef(S).starts_with("GTEST_"))71 EnvTable.emplace_back(S);72 ++EnvP;73 }74 }75 76 void TearDown() override {77 EnvTable.clear();78 EnvStorage.clear();79 }80 81 void addEnvVar(StringRef Var) { EnvTable.emplace_back(Var); }82 83 ArrayRef<StringRef> getEnviron() const { return EnvTable; }84};85 86static Error emptyConstructor(MappedFileRegionArena &) {87 return Error::success();88}89 90TEST_F(CASProgramTest, MappedFileRegionArenaTest) {91 auto TestAllocator = [](StringRef Path) {92 std::optional<MappedFileRegionArena> Alloc;93 ASSERT_THAT_ERROR(94 MappedFileRegionArena::create(Path, /*Capacity=*/10 * 1024 * 1024,95 /*HeaderOffset=*/0, emptyConstructor)96 .moveInto(Alloc),97 Succeeded());98 99 std::vector<unsigned *> AllocatedPtr;100 AllocatedPtr.resize(100);101 DefaultThreadPool Threads;102 for (unsigned I = 0; I < 100; ++I) {103 Threads.async(104 [&](unsigned Idx) {105 // Allocate a buffer that is larger than needed so allocator hits106 // additional pages for test coverage.107 unsigned *P = (unsigned *)cantFail(Alloc->allocate(100));108 *P = Idx;109 AllocatedPtr[Idx] = P;110 },111 I);112 }113 114 Threads.wait();115 for (unsigned I = 0; I < 100; ++I)116 EXPECT_EQ(*AllocatedPtr[I], I);117 };118 119 if (const char *File = getenv("LLVM_CAS_TEST_MAPPED_FILE_REGION")) {120 TestAllocator(File);121 exit(0);122 }123 124 SmallString<128> FilePath;125 sys::fs::createUniqueDirectory("MappedFileRegionArena", FilePath);126 sys::path::append(FilePath, "allocation-file");127 128 std::string Executable =129 sys::fs::getMainExecutable(TestMainArgv0, &ProgramID);130 StringRef Argv[] = {131 Executable, "--gtest_filter=CASProgramTest.MappedFileRegionArenaTest"};132 133 // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child.134 std::string EnvVar = "LLVM_CAS_TEST_MAPPED_FILE_REGION=";135 EnvVar += FilePath.str();136 addEnvVar(EnvVar);137 138 std::string Error;139 bool ExecutionFailed;140 sys::ProcessInfo PI = sys::ExecuteNoWait(Executable, Argv, getEnviron(), {},141 0, &Error, &ExecutionFailed);142 TestAllocator(FilePath);143 144 ASSERT_FALSE(ExecutionFailed) << Error;145 ASSERT_NE(PI.Pid, sys::ProcessInfo::InvalidPid) << "Invalid process id";146 PI = llvm::sys::Wait(PI, /*SecondsToWait=*/5, &Error);147 ASSERT_TRUE(PI.ReturnCode == 0);148 ASSERT_TRUE(Error.empty());149 150 // Clean up after both processes finish testing.151 sys::fs::remove(FilePath);152 sys::fs::remove_directories(sys::path::parent_path(FilePath));153}154 155TEST_F(CASProgramTest, MappedFileRegionArenaSizeTest) {156 using namespace std::chrono_literals;157 if (const char *File = getenv("LLVM_CAS_TEST_MAPPED_FILE_REGION")) {158 ExponentialBackoff Backoff(5s);159 do {160 if (sys::fs::exists(File)) {161 break;162 }163 } while (Backoff.waitForNextAttempt());164 165 std::optional<MappedFileRegionArena> Alloc;166 ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/1024,167 /*HeaderOffset=*/0,168 emptyConstructor)169 .moveInto(Alloc),170 Succeeded());171 ASSERT_TRUE(Alloc->capacity() == 2048);172 173 Alloc.reset();174 ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/4096,175 /*HeaderOffset=*/0,176 emptyConstructor)177 .moveInto(Alloc),178 Succeeded());179 ASSERT_TRUE(Alloc->capacity() == 2048);180 Alloc.reset();181 182 ASSERT_THAT_ERROR(183 MappedFileRegionArena::create(File, /*Capacity=*/2048,184 /*HeaderOffset=*/32, emptyConstructor)185 .moveInto(Alloc),186 FailedWithMessage(187 "specified header offset (32) does not match existing config (0)"));188 189 ASSERT_THAT_ERROR(MappedFileRegionArena::create(File, /*Capacity=*/2048,190 /*HeaderOffset=*/0,191 emptyConstructor)192 .moveInto(Alloc),193 Succeeded());194 195 exit(0);196 }197 198 SmallString<128> FilePath;199 sys::fs::createUniqueDirectory("MappedFileRegionArena", FilePath);200 sys::path::append(FilePath, "allocation-file");201 202 std::string Executable =203 sys::fs::getMainExecutable(TestMainArgv0, &ProgramID);204 StringRef Argv[] = {205 Executable,206 "--gtest_filter=CASProgramTest.MappedFileRegionArenaSizeTest"};207 208 // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child.209 std::string EnvVar = "LLVM_CAS_TEST_MAPPED_FILE_REGION=";210 EnvVar += FilePath.str();211 addEnvVar(EnvVar);212 213 std::optional<MappedFileRegionArena> Alloc;214 ASSERT_THAT_ERROR(MappedFileRegionArena::create(FilePath, /*Capacity=*/2048,215 /*HeaderOffset=*/0,216 emptyConstructor)217 .moveInto(Alloc),218 Succeeded());219 220 std::string Error;221 bool ExecutionFailed;222 sys::ProcessInfo PI = sys::ExecuteNoWait(Executable, Argv, getEnviron(), {},223 0, &Error, &ExecutionFailed);224 225 ASSERT_FALSE(ExecutionFailed) << Error;226 ASSERT_NE(PI.Pid, sys::ProcessInfo::InvalidPid) << "Invalid process id";227 PI = llvm::sys::Wait(PI, /*SecondsToWait=*/100, &Error);228 ASSERT_TRUE(PI.ReturnCode == 0);229 ASSERT_TRUE(Error.empty());230 231 // Size is still the requested 2048.232 ASSERT_TRUE(Alloc->capacity() == 2048);233 234 // Clean up after both processes finish testing.235 sys::fs::remove(FilePath);236 sys::fs::remove_directories(sys::path::parent_path(FilePath));237}238 239#endif // LLVM_ENABLE_ONDISK_CAS240