blob: e93ec77d97546b08e8c8abadea9064011b9db768 [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 <memory>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/fxl/files/directory.h>
#include <lib/fxl/files/path.h>
#include <lib/fxl/macros.h>
#include <lib/fxl/strings/string_number_conversions.h>
#include "gtest/gtest.h"
#include "peridot/bin/ledger/filesystem/detached_path.h"
#include "peridot/bin/ledger/storage/impl/leveldb_factory.h"
#include "peridot/bin/ledger/testing/test_with_environment.h"
#include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
namespace storage {
namespace {
class LevelDbFactoryTest : public ledger::TestWithEnvironment {
public:
LevelDbFactoryTest()
: base_path_(tmpfs_.root_fd()),
cache_path_(base_path_.SubPath("cache")),
db_path_(base_path_.SubPath("databases")),
db_factory_(&environment_, cache_path_) {}
~LevelDbFactoryTest() override {}
// ledger::TestWithEnvironment:
void SetUp() override {
ledger::TestWithEnvironment::SetUp();
ASSERT_TRUE(
files::CreateDirectoryAt(cache_path_.root_fd(), cache_path_.path()));
ASSERT_TRUE(files::CreateDirectoryAt(db_path_.root_fd(), db_path_.path()));
db_factory_.Init();
RunLoopUntilIdle();
}
private:
scoped_tmpfs::ScopedTmpFS tmpfs_;
ledger::DetachedPath base_path_;
protected:
ledger::DetachedPath cache_path_;
ledger::DetachedPath db_path_;
LevelDbFactory db_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(LevelDbFactoryTest);
};
TEST_F(LevelDbFactoryTest, GetOrCreateDb) {
// Create a new instance.
Status status;
std::unique_ptr<Db> db;
bool called;
db_factory_.GetOrCreateDb(
db_path_.SubPath("db"), DbFactory::OnDbNotFound::CREATE,
callback::Capture(callback::SetWhenCalled(&called), &status, &db));
RunLoopUntilIdle();
ASSERT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, db);
// Write one key-value pair.
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
std::unique_ptr<Db::Batch> batch;
EXPECT_EQ(Status::OK, db->StartBatch(handler, &batch));
EXPECT_EQ(Status::OK, batch->Put(handler, "key", "value"));
EXPECT_EQ(Status::OK, batch->Execute(handler));
});
// Close the previous instance and open it again.
db.reset();
db_factory_.GetOrCreateDb(
db_path_.SubPath("db"), DbFactory::OnDbNotFound::RETURN,
callback::Capture(callback::SetWhenCalled(&called), &status, &db));
RunLoopUntilIdle();
ASSERT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, db);
// Expect to find the previously written key-value pair.
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
std::string value;
EXPECT_EQ(Status::OK, db->Get(handler, "key", &value));
EXPECT_EQ("value", value);
});
}
TEST_F(LevelDbFactoryTest, GetDbOnNotFound) {
// Try to get a non existing Db and expect a |NOT_FOUND| status.
Status status;
std::unique_ptr<Db> db;
bool called;
db_factory_.GetOrCreateDb(
db_path_.SubPath("db"), DbFactory::OnDbNotFound::RETURN,
callback::Capture(callback::SetWhenCalled(&called), &status, &db));
RunLoopUntilIdle();
ASSERT_TRUE(called);
EXPECT_EQ(Status::NOT_FOUND, status);
EXPECT_EQ(nullptr, db);
}
TEST_F(LevelDbFactoryTest, CreateMultipleDbs) {
int N = 5;
Status status;
std::unique_ptr<Db> db;
bool called;
// Create N LevelDb instances, one after the other. All of them will use the
// existing cached instance and then, initialize the creation of a new one.
for (int i = 0; i < N; ++i) {
ledger::DetachedPath path = db_path_.SubPath(fxl::NumberToString(i));
EXPECT_FALSE(files::IsDirectoryAt(path.root_fd(), path.path()));
db_factory_.GetOrCreateDb(
path, DbFactory::OnDbNotFound::CREATE,
callback::Capture(callback::SetWhenCalled(&called), &status, &db));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, db);
// Check that the directory was created.
EXPECT_TRUE(files::IsDirectoryAt(path.root_fd(), path.path()));
}
}
TEST_F(LevelDbFactoryTest, CreateMultipleDbsConcurrently) {
int N = 5;
Status statuses[N];
std::unique_ptr<Db> dbs[N];
bool called[N];
// Create N LevelDb instances concurrently. The first one will use the cached
// instance, the 2nd one will be queued up to get the cached one when it's
// initialized, and all the others will be created directly at the destination
// directory.
for (int i = 0; i < N; ++i) {
ledger::DetachedPath path = db_path_.SubPath(fxl::NumberToString(i));
EXPECT_FALSE(files::IsDirectoryAt(path.root_fd(), path.path()));
db_factory_.GetOrCreateDb(
db_path_.SubPath(fxl::NumberToString(i)),
DbFactory::OnDbNotFound::CREATE,
callback::Capture(callback::SetWhenCalled(&called[i]), &statuses[i],
&dbs[i]));
}
RunLoopUntilIdle();
for (int i = 0; i < N; ++i) {
ledger::DetachedPath path = db_path_.SubPath(fxl::NumberToString(i));
EXPECT_TRUE(called[i]);
EXPECT_EQ(Status::OK, statuses[i]);
EXPECT_NE(nullptr, dbs[i]);
// Check that the directory was created.
EXPECT_TRUE(files::IsDirectoryAt(path.root_fd(), path.path()));
}
}
TEST_F(LevelDbFactoryTest, GetOrCreateDbInCallback) {
bool called1;
ledger::DetachedPath path1 = db_path_.SubPath("1");
bool called2;
ledger::DetachedPath path2 = db_path_.SubPath("2");
Status status2;
std::unique_ptr<Db> db2;
db_factory_.GetOrCreateDb(
path1, DbFactory::OnDbNotFound::CREATE,
[&](Status status1, std::unique_ptr<Db> db1) {
called1 = true;
EXPECT_EQ(Status::OK, status1);
EXPECT_NE(nullptr, db1);
db_factory_.GetOrCreateDb(
path2, DbFactory::OnDbNotFound::CREATE,
callback::Capture(callback::SetWhenCalled(&called2), &status2,
&db2));
});
RunLoopUntilIdle();
EXPECT_TRUE(called1);
EXPECT_TRUE(called2);
EXPECT_EQ(Status::OK, status2);
EXPECT_NE(nullptr, db2);
// Check that the directories were created.
EXPECT_TRUE(files::IsDirectoryAt(path1.root_fd(), path1.path()));
EXPECT_TRUE(files::IsDirectoryAt(path2.root_fd(), path2.path()));
}
TEST_F(LevelDbFactoryTest, InitWithCachedDbAvailable) {
// When an empty LevelDb instance is already cached from a previous
// LevelDbFactory execution, don't create a new instance, but use the existing
// one directly.
scoped_tmpfs::ScopedTmpFS tmpfs;
ledger::DetachedPath cache_path(tmpfs.root_fd(), "cache");
// Must be the same as |kCachedDbPath| in leveldb_factory.cc.
ledger::DetachedPath cached_db_path = cache_path.SubPath("cached_db");
auto db_factory = std::make_unique<LevelDbFactory>(&environment_, cache_path);
// The cached db directory should not be created, yet.
EXPECT_FALSE(
files::IsDirectoryAt(cached_db_path.root_fd(), cached_db_path.path()));
// Initialize and wait for the cached instance to be created.
db_factory->Init();
RunLoopUntilIdle();
// Close the factory. This will not affect the created cached instance, which
// was created under |cached_db_path|.
db_factory.reset();
EXPECT_TRUE(
files::IsDirectoryAt(cached_db_path.root_fd(), cached_db_path.path()));
// Reset and re-initialize the factory object. It should now use the
// previously created instance.
db_factory = std::make_unique<LevelDbFactory>(&environment_, cache_path);
db_factory->Init();
RunLoopUntilIdle();
}
} // namespace
} // namespace storage