sapling/eden/fs/utils/test/MappedDiskVectorTest.cpp
Chad Austin c51fea6a85 add support for migrating MappedDiskVector record formats
Summary:
simpkins was curious how data format migrations would be handled in
the upcoming InodeTable. This diff implements the bulk of the logic
which is largely at the MappedDiskVector level. The existing file
format supported record version negotiation and this diff hooks it up
with some type-level operations.

Reviewed By: simpkins

Differential Revision: D7836249

fbshipit-source-id: 00e36bc67068c7524956e908b3872c80a79241c0
2018-05-09 16:50:52 -07:00

252 lines
6.1 KiB
C++

/*
* Copyright (c) 2018-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "eden/fs/utils/MappedDiskVector.h"
#include <folly/experimental/TestUtil.h>
#include <folly/test/TestUtils.h>
#include <gtest/gtest.h>
using facebook::eden::MappedDiskVector;
using folly::test::TemporaryDirectory;
TEST(MappedDiskVector, roundUpToNonzeroPageSize) {
using namespace facebook::eden::detail;
EXPECT_EQ(kPageSize, roundUpToNonzeroPageSize(0));
EXPECT_EQ(kPageSize, roundUpToNonzeroPageSize(1));
EXPECT_EQ(kPageSize, roundUpToNonzeroPageSize(kPageSize - 1));
EXPECT_EQ(kPageSize, roundUpToNonzeroPageSize(kPageSize));
EXPECT_EQ(kPageSize * 2, roundUpToNonzeroPageSize(kPageSize + 1));
EXPECT_EQ(kPageSize * 2, roundUpToNonzeroPageSize(kPageSize * 2 - 1));
EXPECT_EQ(kPageSize * 2, roundUpToNonzeroPageSize(kPageSize * 2));
}
namespace {
struct MappedDiskVectorTest : ::testing::Test {
MappedDiskVectorTest()
: tmpDir{"eden_mdv_"}, mdvPath{(tmpDir.path() / "test.mdv").string()} {}
TemporaryDirectory tmpDir;
std::string mdvPath;
};
struct U64 {
enum { VERSION = 0 };
/* implicit */ U64(uint64_t v) : value{v} {}
operator uint64_t() const {
return value;
}
uint64_t value;
};
} // namespace
TEST_F(MappedDiskVectorTest, grows_file) {
auto mdv = MappedDiskVector<U64>::open(mdvPath);
EXPECT_EQ(0, mdv.size());
struct stat st;
ASSERT_EQ(0, stat(mdvPath.c_str(), &st));
auto old_size = st.st_size;
// 8 MB
constexpr uint64_t N = 1000000;
for (uint64_t i = 0; i < N; ++i) {
mdv.emplace_back(i);
}
EXPECT_EQ(N, mdv.size());
ASSERT_EQ(0, stat(mdvPath.c_str(), &st));
auto new_size = st.st_size;
EXPECT_GT(new_size, old_size);
}
TEST_F(MappedDiskVectorTest, remembers_contents_on_reopen) {
{
auto mdv = MappedDiskVector<U64>::open(mdvPath);
mdv.emplace_back(15ull);
mdv.emplace_back(25ull);
mdv.emplace_back(35ull);
}
auto mdv = MappedDiskVector<U64>::open(mdvPath);
EXPECT_EQ(3, mdv.size());
EXPECT_EQ(15, mdv[0]);
EXPECT_EQ(25, mdv[1]);
EXPECT_EQ(35, mdv[2]);
}
TEST_F(MappedDiskVectorTest, pop_back) {
auto mdv = MappedDiskVector<U64>::open(mdvPath);
mdv.emplace_back(1ull);
mdv.emplace_back(2ull);
mdv.pop_back();
mdv.emplace_back(3ull);
EXPECT_EQ(2, mdv.size());
EXPECT_EQ(1, mdv[0]);
EXPECT_EQ(3, mdv[1]);
}
namespace {
struct Small {
enum { VERSION = 0 };
unsigned x;
};
struct Large {
enum { VERSION = 0 };
unsigned x;
unsigned y;
};
struct SmallNew {
enum { VERSION = 1 };
unsigned x;
};
} // namespace
TEST_F(MappedDiskVectorTest, throws_if_size_does_not_match) {
{
auto mdv = MappedDiskVector<Small>::open(mdvPath);
mdv.emplace_back(Small{1});
}
try {
auto mdv = MappedDiskVector<Large>::open(mdvPath);
FAIL() << "MappedDiskVector didn't throw";
} catch (const std::runtime_error& e) {
EXPECT_EQ(
"Record size does not match size recorded in file. "
"Expected 8 but file has 4",
std::string(e.what()));
} catch (const std::exception& e) {
FAIL() << "Unexpected exception: " << e.what();
}
}
TEST_F(MappedDiskVectorTest, throws_if_version_does_not_match) {
{
auto mdv = MappedDiskVector<Small>::open(mdvPath);
mdv.emplace_back(Small{1});
}
try {
auto mdv = MappedDiskVector<SmallNew>::open(mdvPath);
FAIL() << "MappedDiskVector didn't throw";
} catch (const std::runtime_error& e) {
EXPECT_EQ(
"Unexpected record size and version. "
"Expected size=4, version=1 but got size=4, version=0",
std::string(e.what()));
} catch (const std::exception& e) {
FAIL() << "Unexpected exception: " << e.what();
}
}
namespace {
struct Old {
enum { VERSION = 0 };
unsigned x;
};
struct New {
enum { VERSION = 1 };
explicit New(const Old& old) : x(-old.x), y(old.x) {}
unsigned x;
unsigned y;
};
} // namespace
TEST_F(MappedDiskVectorTest, migrates_from_old_version_to_new) {
{
auto mdv = MappedDiskVector<Old>::open(mdvPath);
mdv.emplace_back(Old{1});
mdv.emplace_back(Old{2});
}
{
auto mdv = MappedDiskVector<New>::open<Old>(mdvPath);
EXPECT_EQ(2, mdv.size());
EXPECT_EQ(-1, mdv[0].x);
EXPECT_EQ(1, mdv[0].y);
EXPECT_EQ(-2, mdv[1].x);
EXPECT_EQ(2, mdv[1].y);
}
// and moves the new database over the old one
{
auto mdv = MappedDiskVector<New>::open(mdvPath);
EXPECT_EQ(2, mdv.size());
EXPECT_EQ(-1, mdv[0].x);
EXPECT_EQ(1, mdv[0].y);
EXPECT_EQ(-2, mdv[1].x);
EXPECT_EQ(2, mdv[1].y);
}
}
namespace {
struct V1 {
enum { VERSION = 1 };
uint8_t value;
uint8_t conversionCount{0};
};
struct V2 {
enum { VERSION = 2 };
explicit V2(V1 old)
: value(old.value), conversionCount(old.conversionCount + 1) {}
uint16_t value;
uint16_t conversionCount{0};
};
struct V3 {
enum { VERSION = 3 };
explicit V3(V2 old)
: value(old.value), conversionCount(old.conversionCount + 1) {}
uint32_t value;
uint32_t conversionCount{0};
};
struct V4 {
enum { VERSION = 4 };
explicit V4(V3 old)
: value(old.value), conversionCount(old.conversionCount + 1) {}
uint64_t value;
uint64_t conversionCount{0};
};
} // namespace
TEST_F(MappedDiskVectorTest, migrates_across_multiple_versions) {
{
auto mdv = MappedDiskVector<V1>::open(mdvPath);
mdv.emplace_back(V1{1});
mdv.emplace_back(V1{2});
}
{
auto mdv = MappedDiskVector<V4>::open<V3, V2, V1>(mdvPath);
EXPECT_EQ(1, mdv[0].value);
EXPECT_EQ(3, mdv[0].conversionCount);
EXPECT_EQ(2, mdv[1].value);
EXPECT_EQ(3, mdv[1].conversionCount);
}
}
TEST_F(MappedDiskVectorTest, migrate_overwrites_existing_tmp_file) {
{
auto mdv = MappedDiskVector<Old>::open(mdvPath);
mdv.emplace_back(Old{1});
mdv.emplace_back(Old{2});
}
folly::writeFileAtomic(mdvPath + ".tmp", "junk data");
{
auto mdv = MappedDiskVector<New>::open<Old>(mdvPath);
EXPECT_EQ(2, mdv.size());
EXPECT_EQ(-1, mdv[0].x);
EXPECT_EQ(1, mdv[0].y);
EXPECT_EQ(-2, mdv[1].x);
EXPECT_EQ(2, mdv[1].y);
}
}