mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
c51fea6a85
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
252 lines
6.1 KiB
C++
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);
|
|
}
|
|
}
|