blob: 82afa73b90c96283057c5b2f98eb9b9552cdd409 [file] [log] [blame]
// Copyright 2016 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "local_output_cache.h"
#include <memory>
#include <unordered_set>
#include <vector>
#include <gtest/gtest.h>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "content.h"
#include "path.h"
#include "unittest_util.h"
#ifdef _WIN32
# include "posix_helper_win.h"
#endif
namespace devtools_goma {
//
// <tmpdir>/cache -- LocalOutputCache
// build -- build directory
//
class LocalOutputCacheTest : public ::testing::Test {
protected:
void SetUp() override {
tmpdir_ = absl::make_unique<TmpdirUtil>("localoutputcache-test");
tmpdir_->MkdirForPath("build", true);
tmpdir_->MkdirForPath("cache", true);
}
void TearDown() override {
LocalOutputCache::Quit();
}
void InitLocalOutputCache() {
const std::int64_t max_cache_amount = 1000000;
const std::int64_t threshold_cache_amount = 10000000;
const size_t max_items = 1000;
const size_t threshold_items = 1000;
InitLocalOutputCacheWithParams(max_cache_amount,
threshold_cache_amount,
max_items,
threshold_items);
}
void InitLocalOutputCacheWithParams(std::int64_t max_cache_amount,
std::int64_t threshold_cache_amount,
size_t max_items,
size_t threshold_items) {
LocalOutputCache::Init(tmpdir_->FullPath("cache"),
nullptr,
max_cache_amount,
threshold_cache_amount,
max_items,
threshold_items);
}
ExecReq MakeFakeExecReq() {
ExecReq req;
req.mutable_command_spec()->set_name("clang");
req.mutable_command_spec()->set_version("4.2.1");
req.mutable_command_spec()->set_target("x86_64-unknown-linux-gnu");
req.set_cwd(tmpdir_->FullPath("build"));
return req;
}
ExecReq MakeFakeExecReqWithArgs(const std::vector<std::string>& args) {
ExecReq req = MakeFakeExecReq();
for (const auto& arg : args) {
req.add_arg(arg);
}
return req;
}
ExecResp MakeFakeExecResp() {
ExecResp resp;
resp.mutable_result()->set_exit_status(0);
ExecResult_Output* output = resp.mutable_result()->add_output();
output->set_filename("output.o");
return resp;
}
std::string CacheFilePath(absl::string_view key) {
return LocalOutputCache::instance()->CacheFilePath(key);
}
bool ShouldInvokeGarbageCollection() {
return LocalOutputCache::instance()->ShouldInvokeGarbageCollection();
}
void RunGarbageCollection(LocalOutputCache::GarbageCollectionStat* stat) {
LocalOutputCache::instance()->RunGarbageCollection(stat);
}
std::unique_ptr<TmpdirUtil> tmpdir_;
};
TEST_F(LocalOutputCacheTest, Match) {
InitLocalOutputCache();
const std::string trace_id = "(test-match)";
// 1. Make ExecReq and ExecResp for fake compile
ExecReq req = MakeFakeExecReq();
ExecResp resp = MakeFakeExecResp();
// 2. Try to Save output.
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::MakeCacheKey(req);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
// 3. Clean build directory
tmpdir_->RemoveTmpFile("build/output.o");
// 4. Lookup
ExecResp looked_up_resp;
EXPECT_TRUE(LocalOutputCache::instance()->Lookup(key,
&looked_up_resp,
trace_id));
// 5. Check ExecResp content
EXPECT_EQ(1, looked_up_resp.result().output_size());
EXPECT_EQ("output.o",
looked_up_resp.result().output(0).filename());
}
TEST_F(LocalOutputCacheTest, NoMatch) {
InitLocalOutputCache();
const std::string trace_id = "(test-nomatch)";
// 1. Make ExecReq and ExecResp for fake compile
ExecReq req = MakeFakeExecReq();
ExecResp resp = MakeFakeExecResp();
// 2. Try to Save output.
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::MakeCacheKey(req);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
// 3. Clean build directory
tmpdir_->RemoveTmpFile("build/output.o");
// 4. Lookup (should fail here)
ExecResp looked_up_resp;
std::string fake_key =
"000000000000000000000000000000000000000000000000000000000000fa6e";
EXPECT_FALSE(LocalOutputCache::instance()->Lookup(fake_key,
&looked_up_resp,
trace_id));
}
TEST_F(LocalOutputCacheTest, CollectGarbage) {
InitLocalOutputCacheWithParams(0, 0, 100, 100);
const std::string trace_id = "(garbage)";
// Make Item.
ExecReq req = MakeFakeExecReq();
ExecResp resp = MakeFakeExecResp();
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::instance()->MakeCacheKey(req);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
// Check key exists.
std::string path = CacheFilePath(key);
EXPECT_EQ(0, access(path.c_str(), F_OK));
// The item should be removed here, since max cache amount is small enough.
{
LocalOutputCache::GarbageCollectionStat stat;
RunGarbageCollection(&stat);
EXPECT_NE(0, access(path.c_str(), F_OK));
EXPECT_EQ(1U, stat.num_removed);
EXPECT_EQ(0U, stat.num_failed);
}
}
TEST_F(LocalOutputCacheTest, WontCollectGarbage) {
InitLocalOutputCacheWithParams(1000000, 1000000, 100, 100);
const std::string trace_id = "(garbage)";
// Make Item.
ExecReq req = MakeFakeExecReq();
ExecResp resp = MakeFakeExecResp();
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::instance()->MakeCacheKey(req);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
// Check key exists.
std::string path = CacheFilePath(key);
EXPECT_EQ(0, access(path.c_str(), F_OK));
// Run garbage collection. Here, anything won't be removed, since
// max cache amount is large enough.
{
LocalOutputCache::GarbageCollectionStat stat;
RunGarbageCollection(&stat);
EXPECT_EQ(0, access(path.c_str(), F_OK));
EXPECT_EQ(0U, stat.num_removed);
EXPECT_EQ(0U, stat.num_failed);
}
}
TEST_F(LocalOutputCacheTest, CollectGarbageByNumItems) {
// Allow max 99 items.
InitLocalOutputCacheWithParams(10000000, 10000000, 99, 60);
const std::string trace_id = "(garbage)";
std::vector<std::string> keys;
std::unordered_set<std::string> key_set;
// Make 99 items.
for (int i = 0; i < 99; ++i) {
ExecReq req = MakeFakeExecReqWithArgs(std::vector<std::string> {
"clang",
"-DFOO=" + std::to_string(i),
});
ExecResp resp = MakeFakeExecResp();
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::instance()->MakeCacheKey(req);
keys.push_back(key);
key_set.insert(key);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
}
// All keys must be different.
EXPECT_EQ(99UL, key_set.size());
// Check key exists.
for (const auto& key : keys) {
std::string path = CacheFilePath(key);
EXPECT_EQ(0, access(path.c_str(), F_OK));
}
// GC won't run yet.
EXPECT_FALSE(ShouldInvokeGarbageCollection());
// Add last one.
{
ExecReq req = MakeFakeExecReqWithArgs(std::vector<std::string> {
"clang",
"-DFOO=" + std::to_string(99),
});
ExecResp resp = MakeFakeExecResp();
tmpdir_->CreateTmpFile("build/output.o", "(output)");
std::string key = LocalOutputCache::instance()->MakeCacheKey(req);
keys.push_back(key);
key_set.insert(key);
EXPECT_TRUE(LocalOutputCache::instance()->SaveOutput(
key, &req, &resp, trace_id));
}
// All keys must be different.
EXPECT_EQ(100UL, key_set.size());
// GC should run now.
EXPECT_TRUE(ShouldInvokeGarbageCollection());
// Run garbage collection.
// Since threshold is 60, 40 items must be removed.
{
LocalOutputCache::GarbageCollectionStat stat;
RunGarbageCollection(&stat);
EXPECT_EQ(40U, stat.num_removed);
EXPECT_EQ(0U, stat.num_failed);
}
}
} // namespace devtools_goma