mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 08:18:15 +03:00
4235784907
Summary: It's not really magic because we don't have a virtual directory inode base any more. Instead, we mkdir and populate it at mount time. What is slightly magical about it is that we give it some special powers: * We know the inode number of the eden dir and prevent unlink operations on it or inside it. * The .eden dir is present in the contents of the root inode and will show up when that directory is `readdir`'d * When resolving a child of a TreeInode by name, we know to return the magic `.eden` inode number. This means that it is possible to `stat` and consume the `.eden` directory from any directory inside the eden mount, even though it won't show up in `readdir` for those child dirs. The contents of the `.eden` dir are: * `socket` - a symlink back to the unix domain socket that our thrift server is listening on. This means that it is a simple `readlink(".eden/socket")` operation to discover both whether a directory is part of an eden mount and how to talk to the server. * `root` - a symlink back to the root of this eden mount. This allows using `readlink(".eden/root")` as a simple 1-step operation to find the root of an eden mount, and avoids needing to walk up directory by directory as is the common pattern for locating `.hg` or `.git` dirs. Reviewed By: simpkins Differential Revision: D4637285 fbshipit-source-id: 0eabf98b29144acccef5c83bd367493399dc55bb
181 lines
5.7 KiB
C++
181 lines
5.7 KiB
C++
/*
|
|
* Copyright (c) 2016-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 <folly/Range.h>
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/model/Hash.h"
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace facebook::eden;
|
|
using folly::ByteRange;
|
|
using folly::StringPiece;
|
|
|
|
TEST(TestMount, createEmptyMount) {
|
|
FakeTreeBuilder builder;
|
|
TestMount testMount{builder};
|
|
auto rootTree = testMount.getRootTree();
|
|
EXPECT_EQ(0, rootTree->getTreeEntries().size())
|
|
<< "Initially, the tree should be empty.";
|
|
}
|
|
|
|
TEST(TestMount, createSimpleTestMount) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("path1", "first!");
|
|
builder.setFiles({
|
|
// clang-format off
|
|
{"path2", "hello"},
|
|
{"path3", "world"},
|
|
// clang-format on
|
|
});
|
|
TestMount testMount{builder};
|
|
|
|
auto path1Inode = testMount.getFileInode("path1");
|
|
EXPECT_NE(nullptr, path1Inode.get())
|
|
<< "Should be able to find FileInode for path1";
|
|
|
|
auto blobHash = path1Inode->getBlobHash();
|
|
ASSERT_TRUE(blobHash.hasValue());
|
|
auto expectedSha1 = Hash::sha1(ByteRange(StringPiece("first!")));
|
|
EXPECT_EQ(expectedSha1, blobHash.value())
|
|
<< "For simplicity, TestMount uses the SHA-1 of the contents as "
|
|
<< "the id for a Blob.";
|
|
|
|
auto dirTreeEntry = testMount.getTreeInode("");
|
|
{
|
|
auto dir = dirTreeEntry->getContents().rlock();
|
|
auto& rootEntries = dir->entries;
|
|
auto& path1Entry = rootEntries.at(PathComponentPiece("path1"));
|
|
ASSERT_FALSE(path1Entry->isMaterialized());
|
|
EXPECT_EQ(expectedSha1, path1Entry->getHash())
|
|
<< "Getting the Entry from the root Dir should also work.";
|
|
}
|
|
|
|
auto rootTree = testMount.getRootTree();
|
|
EXPECT_EQ(3, rootTree->getTreeEntries().size())
|
|
<< "Root Tree object should have 3 entries: path1, path2, path3";
|
|
}
|
|
|
|
TEST(TestMount, addFileAfterMountIsCreated) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile(
|
|
"file1.txt", "I am in the original commit that is backing the mount.");
|
|
TestMount testMount{builder};
|
|
|
|
testMount.addFile("file2.txt", "I am added by the user after mounting.");
|
|
auto dirTreeEntry = testMount.getTreeInode("");
|
|
{
|
|
auto dir = dirTreeEntry->getContents().rlock();
|
|
auto& rootEntries = dir->entries;
|
|
EXPECT_EQ(3, rootEntries.size()) << "New entry is visible in MountPoint";
|
|
}
|
|
|
|
auto rootTree = testMount.getRootTree();
|
|
EXPECT_EQ(1, rootTree->getTreeEntries().size())
|
|
<< "New entry is not in the Tree, though.";
|
|
}
|
|
|
|
TEST(TestMount, overwriteFile) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("file.txt", "original contents");
|
|
TestMount testMount{builder};
|
|
EXPECT_EQ("original contents", testMount.readFile("file.txt"));
|
|
|
|
testMount.overwriteFile("file.txt", "new contents");
|
|
EXPECT_EQ("new contents", testMount.readFile("file.txt"));
|
|
}
|
|
|
|
TEST(TestMount, hasFileAt) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("file.txt", "contents");
|
|
builder.setFile("a/file.txt", "contents");
|
|
TestMount testMount{builder};
|
|
|
|
// Verify hasFileAt() works properly on files added to the Tree.
|
|
EXPECT_TRUE(testMount.hasFileAt("file.txt"));
|
|
EXPECT_FALSE(testMount.hasFileAt("iDoNotExist.txt"));
|
|
EXPECT_TRUE(testMount.hasFileAt("a/file.txt"));
|
|
EXPECT_FALSE(testMount.hasFileAt("a"))
|
|
<< "hasFileAt(directory) should return false rather than throw";
|
|
|
|
testMount.addFile("newFile.txt", "contents");
|
|
testMount.mkdir("b");
|
|
testMount.addFile("b/newFile.txt", "contents");
|
|
|
|
// Verify hasFileAt() works properly on files added to the Overlay.
|
|
EXPECT_TRUE(testMount.hasFileAt("newFile.txt"));
|
|
EXPECT_FALSE(testMount.hasFileAt("iDoNotExist.txt"));
|
|
EXPECT_TRUE(testMount.hasFileAt("b/newFile.txt"));
|
|
EXPECT_FALSE(testMount.hasFileAt("b"))
|
|
<< "hasFileAt(directory) should return false rather than throw";
|
|
EXPECT_FALSE(testMount.hasFileAt("b/c/oneLevelBeyondLastExistingDirectory"))
|
|
<< "hasFileAt(directory) should return false rather than throw";
|
|
}
|
|
|
|
TEST(TestMount, mkdir) {
|
|
FakeTreeBuilder builder;
|
|
TestMount testMount{builder};
|
|
|
|
testMount.mkdir("a");
|
|
testMount.addFile("a/file.txt", "original contents");
|
|
EXPECT_EQ("original contents", testMount.readFile("a/file.txt"));
|
|
}
|
|
|
|
TEST(TestMount, deleteFile) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("file.txt", "original contents");
|
|
TestMount testMount{builder};
|
|
EXPECT_TRUE(testMount.hasFileAt("file.txt"));
|
|
|
|
testMount.deleteFile("file.txt");
|
|
EXPECT_FALSE(testMount.hasFileAt("file.txt"));
|
|
}
|
|
|
|
TEST(TestMount, rmdir) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("dir/file.txt", "original contents");
|
|
TestMount testMount{builder};
|
|
EXPECT_TRUE(testMount.hasFileAt("dir/file.txt"));
|
|
EXPECT_NE(nullptr, testMount.getTreeInode("dir"));
|
|
|
|
testMount.deleteFile("dir/file.txt");
|
|
EXPECT_NE(nullptr, testMount.getTreeInode("dir"));
|
|
testMount.rmdir("dir");
|
|
|
|
try {
|
|
testMount.getTreeInode("dir");
|
|
FAIL() << "ENOENT should be thrown";
|
|
} catch (const std::system_error& expected) {
|
|
ASSERT_EQ(ENOENT, expected.code().value());
|
|
}
|
|
}
|
|
|
|
TEST(TestMount, createFileInSubdirectory) {
|
|
FakeTreeBuilder builder;
|
|
builder.setFile("a/b/c.txt", "I am in the a/b/ directory.");
|
|
TestMount testMount{builder};
|
|
|
|
testMount.addFile("a/b/d.txt", "Another file in the a/b directory.");
|
|
}
|
|
|
|
TEST(TestMount, mkdirWithoutParentShouldThrowENOENT) {
|
|
FakeTreeBuilder builder;
|
|
TestMount testMount{builder};
|
|
|
|
try {
|
|
testMount.mkdir("x/y/z");
|
|
FAIL() << "ENOENT should be thrown";
|
|
} catch (const std::system_error& expected) {
|
|
ASSERT_EQ(ENOENT, expected.code().value());
|
|
}
|
|
}
|