mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
b3a7d80c03
Summary: When a RawOverlayTest test fails, it's sometimes difficult to diagnose the failure because assertions use inode numbers instead of file paths. When a test fails, include inode data (with file paths) in the failure message. Reviewed By: chadaustin Differential Revision: D9806106 fbshipit-source-id: 6160632bf8c64ceeb84e9d4709347e9268747ca4
659 lines
20 KiB
C++
659 lines
20 KiB
C++
/*
|
|
* Copyright (c) 2004-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/inodes/Overlay.h"
|
|
|
|
#include <folly/Exception.h>
|
|
#include <folly/FileUtil.h>
|
|
#include <folly/Subprocess.h>
|
|
#include <folly/experimental/TestUtil.h>
|
|
#include <folly/test/TestUtils.h>
|
|
#include <gtest/gtest.h>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/service/PrettyPrinters.h"
|
|
#include "eden/fs/testharness/FakeBackingStore.h"
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
#include "eden/fs/testharness/TestChecks.h"
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
#include "eden/fs/testharness/TestUtil.h"
|
|
|
|
using namespace folly::string_piece_literals;
|
|
using folly::Future;
|
|
using folly::makeFuture;
|
|
using folly::StringPiece;
|
|
using folly::Subprocess;
|
|
using folly::test::TemporaryDirectory;
|
|
using std::string;
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
namespace {
|
|
std::string debugDumpOverlayInodes(Overlay&, InodeNumber rootInode);
|
|
}
|
|
|
|
TEST(OverlayGoldMasterTest, can_load_overlay_v2) {
|
|
// eden/test-data/overlay-v2.tgz contains a saved copy of an overlay
|
|
// directory generated by edenfs. Unpack it into a temporary directory,
|
|
// then try loading it.
|
|
//
|
|
// This test helps ensure that new edenfs versions can still successfully load
|
|
// this overlay format even if we change how the overlay is saved in the
|
|
// future.
|
|
TemporaryDirectory tmpdir("eden_test");
|
|
Subprocess tarProcess({"/bin/tar",
|
|
"-xzf",
|
|
"eden/test-data/overlay-v2.tgz",
|
|
"-C",
|
|
tmpdir.path().string()});
|
|
tarProcess.waitChecked();
|
|
|
|
Overlay overlay{realpath(tmpdir.path().string()) + "overlay-v2"_pc};
|
|
|
|
InodeTimestamps timestamps;
|
|
|
|
Hash hash1{folly::ByteRange{"abcdabcdabcdabcdabcd"_sp}};
|
|
Hash hash2{folly::ByteRange{"01234012340123401234"_sp}};
|
|
Hash hash3{folly::ByteRange{"e0e0e0e0e0e0e0e0e0e0"_sp}};
|
|
Hash hash4{folly::ByteRange{"44444444444444444444"_sp}};
|
|
|
|
auto rootTree = overlay.loadOverlayDir(kRootNodeId);
|
|
auto file =
|
|
overlay.openFile(2_ino, Overlay::kHeaderIdentifierFile, timestamps);
|
|
auto subdir = overlay.loadOverlayDir(3_ino);
|
|
auto emptyDir = overlay.loadOverlayDir(4_ino);
|
|
auto hello =
|
|
overlay.openFile(5_ino, Overlay::kHeaderIdentifierFile, timestamps);
|
|
|
|
ASSERT_TRUE(rootTree);
|
|
EXPECT_EQ(2, rootTree->first.size());
|
|
const auto& fileEntry = rootTree->first.at("file"_pc);
|
|
EXPECT_EQ(2_ino, fileEntry.getInodeNumber());
|
|
EXPECT_EQ(hash1, fileEntry.getHash());
|
|
EXPECT_EQ(S_IFREG | 0644, fileEntry.getInitialMode());
|
|
const auto& subdirEntry = rootTree->first.at("subdir"_pc);
|
|
EXPECT_EQ(3_ino, subdirEntry.getInodeNumber());
|
|
EXPECT_EQ(hash2, subdirEntry.getHash());
|
|
EXPECT_EQ(S_IFDIR | 0755, subdirEntry.getInitialMode());
|
|
|
|
folly::checkUnixError(lseek(file.fd(), Overlay::kHeaderLength, SEEK_SET));
|
|
std::string result;
|
|
folly::readFile(file.fd(), result);
|
|
EXPECT_EQ("contents", result);
|
|
|
|
ASSERT_TRUE(subdir);
|
|
EXPECT_EQ(2, subdir->first.size());
|
|
const auto& emptyEntry = subdir->first.at("empty"_pc);
|
|
EXPECT_EQ(4_ino, emptyEntry.getInodeNumber());
|
|
EXPECT_EQ(hash3, emptyEntry.getHash());
|
|
EXPECT_EQ(S_IFDIR | 0755, emptyEntry.getInitialMode());
|
|
const auto& helloEntry = subdir->first.at("hello"_pc);
|
|
EXPECT_EQ(5_ino, helloEntry.getInodeNumber());
|
|
EXPECT_EQ(hash4, helloEntry.getHash());
|
|
EXPECT_EQ(S_IFREG | 0644, helloEntry.getInitialMode());
|
|
|
|
ASSERT_TRUE(emptyDir);
|
|
EXPECT_EQ(0, emptyDir->first.size());
|
|
|
|
folly::checkUnixError(lseek(hello.fd(), Overlay::kHeaderLength, SEEK_SET));
|
|
folly::readFile(file.fd(), result);
|
|
EXPECT_EQ("", result);
|
|
}
|
|
|
|
class OverlayTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
// Set up a directory structure that we will use for most
|
|
// of the tests below
|
|
FakeTreeBuilder builder;
|
|
builder.setFiles({
|
|
{"dir/a.txt", "This is a.txt.\n"},
|
|
});
|
|
mount_.initialize(builder);
|
|
}
|
|
|
|
// Helper method to check if two timestamps are same or not.
|
|
static void expectTimeSpecsEqual(
|
|
const EdenTimestamp& at,
|
|
const EdenTimestamp& bt) {
|
|
auto a = at.toTimespec();
|
|
auto b = bt.toTimespec();
|
|
EXPECT_EQ(a.tv_sec, b.tv_sec);
|
|
EXPECT_EQ(a.tv_nsec, b.tv_nsec);
|
|
}
|
|
|
|
static void expectTimeStampsEqual(
|
|
const InodeTimestamps& a,
|
|
const InodeTimestamps& b) {
|
|
expectTimeSpecsEqual(a.atime, b.atime);
|
|
expectTimeSpecsEqual(a.mtime, b.mtime);
|
|
expectTimeSpecsEqual(a.ctime, b.ctime);
|
|
}
|
|
|
|
TestMount mount_;
|
|
};
|
|
|
|
TEST_F(OverlayTest, testRemount) {
|
|
mount_.addFile("dir/new.txt", "test\n");
|
|
mount_.remount();
|
|
// Confirm that the tree has been updated correctly.
|
|
auto newInode = mount_.getFileInode("dir/new.txt");
|
|
EXPECT_FILE_INODE(newInode, "test\n", 0644);
|
|
}
|
|
|
|
TEST_F(OverlayTest, testModifyRemount) {
|
|
// inode object has to be destroyed
|
|
// before remount is called to release the reference
|
|
{
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
|
EXPECT_FILE_INODE(inode, "This is a.txt.\n", 0644);
|
|
}
|
|
|
|
// materialize a directory
|
|
mount_.overwriteFile("dir/a.txt", "contents changed\n");
|
|
mount_.remount();
|
|
|
|
auto newInode = mount_.getFileInode("dir/a.txt");
|
|
EXPECT_FILE_INODE(newInode, "contents changed\n", 0644);
|
|
}
|
|
|
|
// In memory timestamps should be same before and after a remount.
|
|
// (inmemory timestamps should be written to overlay on
|
|
// on unmount and should be read back from the overlay on remount)
|
|
TEST_F(OverlayTest, testTimeStampsInOverlayOnMountAndUnmount) {
|
|
// Materialize file and directory
|
|
// test timestamp behavior in overlay on remount.
|
|
InodeTimestamps beforeRemountFile;
|
|
InodeTimestamps beforeRemountDir;
|
|
mount_.overwriteFile("dir/a.txt", "contents changed\n");
|
|
|
|
{
|
|
// We do not want to keep references to inode in order to remount.
|
|
auto inodeFile = mount_.getFileInode("dir/a.txt");
|
|
EXPECT_FILE_INODE(inodeFile, "contents changed\n", 0644);
|
|
beforeRemountFile = inodeFile->getMetadata().timestamps;
|
|
}
|
|
|
|
{
|
|
// Check for materialized files.
|
|
mount_.remount();
|
|
auto inodeRemount = mount_.getFileInode("dir/a.txt");
|
|
auto afterRemount = inodeRemount->getMetadata().timestamps;
|
|
expectTimeStampsEqual(beforeRemountFile, afterRemount);
|
|
}
|
|
|
|
{
|
|
auto inodeDir = mount_.getTreeInode("dir");
|
|
beforeRemountDir = inodeDir->getMetadata().timestamps;
|
|
}
|
|
|
|
{
|
|
// Check for materialized directory
|
|
mount_.remount();
|
|
auto inodeRemount = mount_.getTreeInode("dir");
|
|
auto afterRemount = inodeRemount->getMetadata().timestamps;
|
|
expectTimeStampsEqual(beforeRemountDir, afterRemount);
|
|
}
|
|
}
|
|
|
|
TEST_F(OverlayTest, roundTripThroughSaveAndLoad) {
|
|
auto hash = Hash{"0123456789012345678901234567890123456789"};
|
|
|
|
auto overlay = mount_.getEdenMount()->getOverlay();
|
|
|
|
auto ino1 = overlay->allocateInodeNumber();
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
auto ino3 = overlay->allocateInodeNumber();
|
|
|
|
DirContents dir;
|
|
dir.emplace("one"_pc, S_IFREG | 0644, ino2, hash);
|
|
dir.emplace("two"_pc, S_IFDIR | 0755, ino3);
|
|
|
|
overlay->saveOverlayDir(ino1, dir, InodeTimestamps{});
|
|
|
|
auto result = overlay->loadOverlayDir(ino1);
|
|
ASSERT_TRUE(result);
|
|
const auto* newDir = &result->first;
|
|
|
|
EXPECT_EQ(2, newDir->size());
|
|
const auto& one = newDir->find("one"_pc)->second;
|
|
const auto& two = newDir->find("two"_pc)->second;
|
|
EXPECT_EQ(ino2, one.getInodeNumber());
|
|
EXPECT_FALSE(one.isMaterialized());
|
|
EXPECT_EQ(ino3, two.getInodeNumber());
|
|
EXPECT_TRUE(two.isMaterialized());
|
|
}
|
|
|
|
TEST_F(OverlayTest, getFilePath) {
|
|
Overlay::InodePath path;
|
|
|
|
path = Overlay::getFilePath(1_ino);
|
|
EXPECT_EQ("01/1"_relpath, path);
|
|
path = Overlay::getFilePath(1234_ino);
|
|
EXPECT_EQ("d2/1234"_relpath, path);
|
|
|
|
// It's slightly unfortunate that we use hexadecimal for the subdirectory
|
|
// name and decimal for the final inode path. That doesn't seem worth fixing
|
|
// for now.
|
|
path = Overlay::getFilePath(15_ino);
|
|
EXPECT_EQ("0f/15"_relpath, path);
|
|
path = Overlay::getFilePath(16_ino);
|
|
EXPECT_EQ("10/16"_relpath, path);
|
|
}
|
|
|
|
enum class OverlayRestartMode {
|
|
CLEAN,
|
|
UNCLEAN,
|
|
};
|
|
|
|
class RawOverlayTest : public ::testing::TestWithParam<OverlayRestartMode> {
|
|
public:
|
|
RawOverlayTest() : testDir_{"eden_raw_overlay_test_"} {
|
|
loadOverlay();
|
|
}
|
|
|
|
void recreate(folly::Optional<OverlayRestartMode> restartMode = folly::none) {
|
|
overlay->close();
|
|
overlay.reset();
|
|
switch (restartMode.value_or(GetParam())) {
|
|
case OverlayRestartMode::CLEAN:
|
|
break;
|
|
case OverlayRestartMode::UNCLEAN:
|
|
if (unlink((getLocalDir() + "next-inode-number"_pc).c_str())) {
|
|
folly::throwSystemError("removing saved inode numebr");
|
|
}
|
|
break;
|
|
}
|
|
loadOverlay();
|
|
}
|
|
|
|
void loadOverlay() {
|
|
overlay = std::make_unique<Overlay>(getLocalDir());
|
|
}
|
|
|
|
AbsolutePath getLocalDir() {
|
|
return AbsolutePath{testDir_.path().string()};
|
|
}
|
|
|
|
folly::test::TemporaryDirectory testDir_;
|
|
std::unique_ptr<Overlay> overlay;
|
|
};
|
|
|
|
TEST_P(RawOverlayTest, max_inode_number_is_1_if_overlay_is_empty) {
|
|
EXPECT_EQ(kRootNodeId, overlay->scanForNextInodeNumber());
|
|
EXPECT_EQ(2_ino, overlay->allocateInodeNumber());
|
|
|
|
recreate(OverlayRestartMode::CLEAN);
|
|
|
|
EXPECT_EQ(2_ino, overlay->scanForNextInodeNumber());
|
|
EXPECT_EQ(3_ino, overlay->allocateInodeNumber());
|
|
|
|
recreate(OverlayRestartMode::UNCLEAN);
|
|
|
|
EXPECT_EQ(kRootNodeId, overlay->scanForNextInodeNumber());
|
|
EXPECT_EQ(2_ino, overlay->allocateInodeNumber());
|
|
}
|
|
|
|
TEST_P(RawOverlayTest, remembers_max_inode_number_of_tree_inodes) {
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, ino2);
|
|
|
|
DirContents dir;
|
|
overlay->saveOverlayDir(ino2, dir, InodeTimestamps{});
|
|
|
|
recreate();
|
|
|
|
EXPECT_EQ(2_ino, overlay->scanForNextInodeNumber());
|
|
}
|
|
|
|
TEST_P(RawOverlayTest, remembers_max_inode_number_of_tree_entries) {
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, ino2);
|
|
auto ino3 = overlay->allocateInodeNumber();
|
|
auto ino4 = overlay->allocateInodeNumber();
|
|
|
|
DirContents dir;
|
|
dir.emplace(PathComponentPiece{"f"}, S_IFREG | 0644, ino3);
|
|
dir.emplace(PathComponentPiece{"d"}, S_IFDIR | 0755, ino4);
|
|
overlay->saveOverlayDir(kRootNodeId, dir, InodeTimestamps{});
|
|
|
|
recreate();
|
|
|
|
SCOPED_TRACE("Inodes:\n" + debugDumpOverlayInodes(*overlay, kRootNodeId));
|
|
EXPECT_EQ(4_ino, overlay->scanForNextInodeNumber());
|
|
}
|
|
|
|
TEST_P(RawOverlayTest, remembers_max_inode_number_of_file) {
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, ino2);
|
|
auto ino3 = overlay->allocateInodeNumber();
|
|
|
|
// When materializing, overlay data is written leaf-to-root.
|
|
|
|
// The File is written first.
|
|
overlay->createOverlayFile(
|
|
ino3, InodeTimestamps{}, folly::ByteRange{"contents"_sp});
|
|
|
|
recreate();
|
|
|
|
EXPECT_EQ(3_ino, overlay->scanForNextInodeNumber());
|
|
}
|
|
|
|
TEST_P(RawOverlayTest, inode_numbers_not_reused_after_unclean_shutdown) {
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, ino2);
|
|
overlay->allocateInodeNumber();
|
|
auto ino4 = overlay->allocateInodeNumber();
|
|
auto ino5 = overlay->allocateInodeNumber();
|
|
|
|
// When materializing, overlay data is written leaf-to-root.
|
|
|
|
// The File is written first.
|
|
overlay->createOverlayFile(
|
|
ino5, InodeTimestamps{}, folly::ByteRange{"contents"_sp});
|
|
|
|
// The subdir is written next.
|
|
DirContents subdir;
|
|
subdir.emplace(PathComponentPiece{"f"}, S_IFREG | 0644, ino5);
|
|
overlay->saveOverlayDir(ino4, subdir, InodeTimestamps{});
|
|
|
|
// Crashed before root was written.
|
|
|
|
recreate();
|
|
|
|
SCOPED_TRACE(
|
|
"Inodes from subdir:\n" + debugDumpOverlayInodes(*overlay, ino4));
|
|
EXPECT_EQ(5_ino, overlay->scanForNextInodeNumber());
|
|
}
|
|
|
|
TEST_P(RawOverlayTest, inode_numbers_after_takeover) {
|
|
auto ino2 = overlay->allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, ino2);
|
|
auto ino3 = overlay->allocateInodeNumber();
|
|
auto ino4 = overlay->allocateInodeNumber();
|
|
auto ino5 = overlay->allocateInodeNumber();
|
|
|
|
// Write a subdir.
|
|
DirContents subdir;
|
|
subdir.emplace(PathComponentPiece{"f"}, S_IFREG | 0644, ino5);
|
|
overlay->saveOverlayDir(ino4, subdir, InodeTimestamps{});
|
|
|
|
// Write the root.
|
|
DirContents dir;
|
|
dir.emplace(PathComponentPiece{"f"}, S_IFREG | 0644, ino3);
|
|
dir.emplace(PathComponentPiece{"d"}, S_IFDIR | 0755, ino4);
|
|
overlay->saveOverlayDir(kRootNodeId, dir, InodeTimestamps{});
|
|
|
|
recreate();
|
|
|
|
overlay->scanForNextInodeNumber();
|
|
|
|
// Rewrite the root (say, after a takeover) without the file.
|
|
|
|
DirContents newroot;
|
|
newroot.emplace(PathComponentPiece{"d"}, S_IFDIR | 0755, 4_ino);
|
|
overlay->saveOverlayDir(kRootNodeId, newroot, InodeTimestamps{});
|
|
|
|
recreate(OverlayRestartMode::CLEAN);
|
|
|
|
SCOPED_TRACE("Inodes:\n" + debugDumpOverlayInodes(*overlay, kRootNodeId));
|
|
// Ensure an inode in the overlay but not referenced by the previous session
|
|
// counts.
|
|
EXPECT_EQ(5_ino, overlay->scanForNextInodeNumber());
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
Clean,
|
|
RawOverlayTest,
|
|
::testing::Values(OverlayRestartMode::CLEAN));
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
Unclean,
|
|
RawOverlayTest,
|
|
::testing::Values(OverlayRestartMode::UNCLEAN));
|
|
|
|
TEST(OverlayInodePath, defaultInodePathIsEmpty) {
|
|
Overlay::InodePath path;
|
|
EXPECT_STREQ(path.c_str(), "");
|
|
}
|
|
|
|
class DebugDumpOverlayInodesTest : public ::testing::Test {
|
|
public:
|
|
DebugDumpOverlayInodesTest()
|
|
: testDir_{"eden_DebugDumpOverlayInodesTest"},
|
|
overlay{AbsolutePathPiece{testDir_.path().string()}} {}
|
|
|
|
folly::test::TemporaryDirectory testDir_;
|
|
Overlay overlay;
|
|
};
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, dump_empty_directory) {
|
|
auto ino = kRootNodeId;
|
|
EXPECT_EQ(1_ino, ino);
|
|
|
|
overlay.saveOverlayDir(ino, DirContents{}, InodeTimestamps{});
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (0 total):\n",
|
|
debugDumpOverlayInodes(overlay, ino));
|
|
}
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, dump_directory_with_3_regular_files) {
|
|
auto rootIno = kRootNodeId;
|
|
EXPECT_EQ(1_ino, rootIno);
|
|
auto fileAIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, fileAIno);
|
|
auto fileBIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(3_ino, fileBIno);
|
|
auto fileCIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(4_ino, fileCIno);
|
|
|
|
DirContents root;
|
|
root.emplace("file_a"_pc, S_IFREG | 0644, fileAIno);
|
|
root.emplace("file_b"_pc, S_IFREG | 0644, fileBIno);
|
|
root.emplace("file_c"_pc, S_IFREG | 0644, fileCIno);
|
|
overlay.saveOverlayDir(rootIno, root, InodeTimestamps{});
|
|
|
|
overlay.createOverlayFile(
|
|
fileAIno, InodeTimestamps{}, folly::ByteRange{""_sp});
|
|
overlay.createOverlayFile(
|
|
fileBIno, InodeTimestamps{}, folly::ByteRange{""_sp});
|
|
overlay.createOverlayFile(
|
|
fileCIno, InodeTimestamps{}, folly::ByteRange{""_sp});
|
|
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (3 total):\n"
|
|
" 2 f 644 file_a\n"
|
|
" 3 f 644 file_b\n"
|
|
" 4 f 644 file_c\n",
|
|
debugDumpOverlayInodes(overlay, rootIno));
|
|
}
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, dump_directory_with_an_empty_subdirectory) {
|
|
auto rootIno = kRootNodeId;
|
|
EXPECT_EQ(1_ino, rootIno);
|
|
auto subdirIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, subdirIno);
|
|
|
|
DirContents root;
|
|
root.emplace("subdir"_pc, S_IFDIR | 0755, subdirIno);
|
|
overlay.saveOverlayDir(rootIno, root, InodeTimestamps{});
|
|
|
|
overlay.saveOverlayDir(subdirIno, DirContents{}, InodeTimestamps{});
|
|
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (1 total):\n"
|
|
" 2 d 755 subdir\n"
|
|
"/subdir\n"
|
|
" Inode number: 2\n"
|
|
" Entries (0 total):\n",
|
|
debugDumpOverlayInodes(overlay, rootIno));
|
|
}
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, dump_directory_with_unsaved_subdirectory) {
|
|
auto rootIno = kRootNodeId;
|
|
EXPECT_EQ(1_ino, rootIno);
|
|
auto directoryDoesNotExistIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, directoryDoesNotExistIno);
|
|
|
|
DirContents root;
|
|
root.emplace(
|
|
"directory_does_not_exist"_pc, S_IFDIR | 0755, directoryDoesNotExistIno);
|
|
overlay.saveOverlayDir(rootIno, root, InodeTimestamps{});
|
|
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (1 total):\n"
|
|
" 2 d 755 directory_does_not_exist\n"
|
|
"/directory_does_not_exist\n"
|
|
" Inode number: 2\n",
|
|
debugDumpOverlayInodes(overlay, rootIno));
|
|
}
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, dump_directory_with_unsaved_regular_file) {
|
|
auto rootIno = kRootNodeId;
|
|
EXPECT_EQ(1_ino, rootIno);
|
|
auto regularFileDoesNotExistIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, regularFileDoesNotExistIno);
|
|
|
|
DirContents root;
|
|
root.emplace(
|
|
"regular_file_does_not_exist"_pc,
|
|
S_IFREG | 0644,
|
|
regularFileDoesNotExistIno);
|
|
overlay.saveOverlayDir(rootIno, root, InodeTimestamps{});
|
|
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (1 total):\n"
|
|
" 2 f 644 regular_file_does_not_exist\n",
|
|
debugDumpOverlayInodes(overlay, rootIno));
|
|
}
|
|
|
|
TEST_F(DebugDumpOverlayInodesTest, directories_are_dumped_depth_first) {
|
|
auto rootIno = kRootNodeId;
|
|
EXPECT_EQ(1_ino, rootIno);
|
|
auto subdirAIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(2_ino, subdirAIno);
|
|
auto subdirAXIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(3_ino, subdirAXIno);
|
|
auto subdirAYIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(4_ino, subdirAYIno);
|
|
auto subdirBIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(5_ino, subdirBIno);
|
|
auto subdirBXIno = overlay.allocateInodeNumber();
|
|
EXPECT_EQ(6_ino, subdirBXIno);
|
|
|
|
DirContents root;
|
|
root.emplace("subdir_a"_pc, S_IFDIR | 0755, subdirAIno);
|
|
root.emplace("subdir_b"_pc, S_IFDIR | 0755, subdirBIno);
|
|
overlay.saveOverlayDir(rootIno, root, InodeTimestamps{});
|
|
|
|
DirContents subdirA;
|
|
subdirA.emplace("x"_pc, S_IFDIR | 0755, subdirAXIno);
|
|
subdirA.emplace("y"_pc, S_IFDIR | 0755, subdirAYIno);
|
|
overlay.saveOverlayDir(subdirAIno, subdirA, InodeTimestamps{});
|
|
|
|
DirContents subdirB;
|
|
subdirB.emplace("x"_pc, S_IFDIR | 0755, subdirBXIno);
|
|
overlay.saveOverlayDir(subdirBIno, subdirB, InodeTimestamps{});
|
|
|
|
overlay.saveOverlayDir(subdirAXIno, DirContents{}, InodeTimestamps{});
|
|
overlay.saveOverlayDir(subdirAYIno, DirContents{}, InodeTimestamps{});
|
|
overlay.saveOverlayDir(subdirBXIno, DirContents{}, InodeTimestamps{});
|
|
|
|
EXPECT_EQ(
|
|
"/\n"
|
|
" Inode number: 1\n"
|
|
" Entries (2 total):\n"
|
|
" 2 d 755 subdir_a\n"
|
|
" 5 d 755 subdir_b\n"
|
|
"/subdir_a\n"
|
|
" Inode number: 2\n"
|
|
" Entries (2 total):\n"
|
|
" 3 d 755 x\n"
|
|
" 4 d 755 y\n"
|
|
"/subdir_a/x\n"
|
|
" Inode number: 3\n"
|
|
" Entries (0 total):\n"
|
|
"/subdir_a/y\n"
|
|
" Inode number: 4\n"
|
|
" Entries (0 total):\n"
|
|
"/subdir_b\n"
|
|
" Inode number: 5\n"
|
|
" Entries (1 total):\n"
|
|
" 6 d 755 x\n"
|
|
"/subdir_b/x\n"
|
|
" Inode number: 6\n"
|
|
" Entries (0 total):\n",
|
|
debugDumpOverlayInodes(overlay, rootIno));
|
|
}
|
|
|
|
namespace {
|
|
void debugDumpOverlayInodes(
|
|
Overlay& overlay,
|
|
InodeNumber rootInode,
|
|
AbsolutePathPiece path,
|
|
std::ostringstream& out) {
|
|
out << path << "\n";
|
|
out << " Inode number: " << rootInode << "\n";
|
|
|
|
auto dir = overlay.loadOverlayDir(rootInode);
|
|
if (dir) {
|
|
auto& dirContents = dir->first;
|
|
out << " Entries (" << dirContents.size() << " total):\n";
|
|
|
|
auto dtypeToString = [](dtype_t dtype) noexcept->const char* {
|
|
switch (dtype) {
|
|
case dtype_t::Dir:
|
|
return "d";
|
|
case dtype_t::Regular:
|
|
return "f";
|
|
default:
|
|
return "?";
|
|
}
|
|
};
|
|
|
|
for (const auto& [entryPath, entry] : dirContents) {
|
|
auto permissions = entry.getInitialMode() & ~S_IFMT;
|
|
out << " " << std::dec << std::setw(11) << entry.getInodeNumber() << " "
|
|
<< dtypeToString(entry.getDtype()) << " " << std::oct << std::setw(4)
|
|
<< permissions << " " << entryPath << "\n";
|
|
}
|
|
for (const auto& [entryPath, entry] : dirContents) {
|
|
if (entry.getDtype() == dtype_t::Dir) {
|
|
debugDumpOverlayInodes(
|
|
overlay, entry.getInodeNumber(), path + entryPath, out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string debugDumpOverlayInodes(Overlay& overlay, InodeNumber rootInode) {
|
|
std::ostringstream out;
|
|
debugDumpOverlayInodes(overlay, rootInode, AbsolutePathPiece{}, out);
|
|
return out.str();
|
|
}
|
|
} // namespace
|
|
|
|
} // namespace eden
|
|
} // namespace facebook
|