mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
d71a100be2
Summary: This is part of "the great r-valuification of folly::Future": * This is something we should do for safety in general. * Context: `Future::get(...)` means both `Future::get()` and `Future::get(Duration)` * Using lvalue-qualified `Future::get(...)` has caused some failures around D7840699 since lvalue-qualification hides that operation's move-out semantics - leads to some use of future operations that are really not correct, but are not obviously incorrect. * Problems with `Future::get(...) &`: it moves-out the result but doesn't invalidate the Future - the Future remains (technically) valid even though it actually is partially moved-out. Callers can subsequently access that moved-out result via things like `future.get(...)`, `future.result()`, `future.value()`, etc. - these access an already-moved-out result which is/can be surprising. * Reasons `Future::get(...) &&` is better: its semantics are more obvious and user-testable. It moves-out the Future, leaving it with `future.valid() == false`. Reviewed By: yfeldblum Differential Revision: D8711368 fbshipit-source-id: fbfcb731097cdf9d8d98583956bc7fe614157a6b
176 lines
5.5 KiB
C++
176 lines
5.5 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 <folly/test/TestUtils.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "eden/fs/fuse/FileHandle.h"
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
#include "eden/fs/testharness/TestChecks.h"
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
|
|
using namespace facebook::eden;
|
|
using folly::StringPiece;
|
|
|
|
class UnlinkTest : 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"},
|
|
{"dir/b.txt", "This is b.txt.\n"},
|
|
{"dir/c.txt", "This is c.txt.\n"},
|
|
{"readme.txt", "File in the root directory.\n"},
|
|
});
|
|
mount_.initialize(builder);
|
|
}
|
|
|
|
TestMount mount_;
|
|
};
|
|
|
|
TEST_F(UnlinkTest, enoent) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto unlinkFuture = dir->unlink("notpresent.txt"_pc);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
EXPECT_THROW_ERRNO(std::move(unlinkFuture).get(), ENOENT);
|
|
}
|
|
|
|
TEST_F(UnlinkTest, notLoaded) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto childPath = "a.txt"_pc;
|
|
|
|
// Remove the child when it has not been loaded yet.
|
|
auto unlinkFuture = dir->unlink(childPath);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
std::move(unlinkFuture).get();
|
|
|
|
EXPECT_THROW_ERRNO(dir->getChildInodeNumber(childPath), ENOENT);
|
|
}
|
|
|
|
TEST_F(UnlinkTest, inodeAssigned) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto childPath = "a.txt"_pc;
|
|
|
|
// Assign an inode number to the child without loading it.
|
|
dir->getChildInodeNumber(childPath);
|
|
auto unlinkFuture = dir->unlink(childPath);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
std::move(unlinkFuture).get();
|
|
|
|
EXPECT_THROW_ERRNO(dir->getChildInodeNumber(childPath), ENOENT);
|
|
}
|
|
|
|
TEST_F(UnlinkTest, loaded) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto childPath = "a.txt"_pc;
|
|
|
|
// Load the child before removing it
|
|
auto file = mount_.getFileInode("dir/a.txt");
|
|
EXPECT_EQ(file->getNodeId(), dir->getChildInodeNumber(childPath));
|
|
auto unlinkFuture = dir->unlink(childPath);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
std::move(unlinkFuture).get();
|
|
|
|
EXPECT_THROW_ERRNO(dir->getChildInodeNumber(childPath), ENOENT);
|
|
// We should still be able to read from the FileInode
|
|
EXPECT_FILE_INODE(file, "This is a.txt.\n", 0644);
|
|
}
|
|
|
|
TEST_F(UnlinkTest, modified) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto childPath = "a.txt"_pc;
|
|
|
|
// Modify the child, so it is materialized before we remove it
|
|
auto file = mount_.getFileInode("dir/a.txt");
|
|
EXPECT_EQ(file->getNodeId(), dir->getChildInodeNumber(childPath));
|
|
auto handle = file->open(O_WRONLY).get();
|
|
auto newContents = StringPiece{
|
|
"new contents for the file\n"
|
|
"testing testing\n"
|
|
"123\n"
|
|
"testing testing\n"};
|
|
auto writeResult = handle->write(newContents, 0).get();
|
|
EXPECT_EQ(newContents.size(), writeResult);
|
|
|
|
// Now remove the child
|
|
auto unlinkFuture = dir->unlink(childPath);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
std::move(unlinkFuture).get();
|
|
|
|
EXPECT_THROW_ERRNO(dir->getChildInodeNumber(childPath), ENOENT);
|
|
// We should still be able to read from the FileInode
|
|
EXPECT_FILE_INODE(file, newContents, 0644);
|
|
}
|
|
|
|
TEST_F(UnlinkTest, created) {
|
|
auto dir = mount_.getTreeInode("dir");
|
|
auto childPath = "new.txt"_pc;
|
|
auto contents =
|
|
StringPiece{"This is a new file that does not exist in source control\n"};
|
|
mount_.addFile("dir/new.txt", contents);
|
|
auto file = mount_.getFileInode("dir/new.txt");
|
|
|
|
// Now remove the child
|
|
auto unlinkFuture = dir->unlink(childPath);
|
|
ASSERT_TRUE(unlinkFuture.isReady());
|
|
std::move(unlinkFuture).get();
|
|
|
|
EXPECT_THROW_ERRNO(dir->getChildInodeNumber(childPath), ENOENT);
|
|
// We should still be able to read from the FileInode
|
|
EXPECT_FILE_INODE(file, contents, 0644);
|
|
}
|
|
|
|
// TODO: It would be nice to adds some tests for concurrent load+unlink
|
|
// However, loading a FileInode does not wait for the file data to be loaded
|
|
// from the ObjectStore, so we currently don't have a good way to test
|
|
// various interleavings of the two operations.
|
|
|
|
// TODO
|
|
// - concurrent rename+unlink. We can block the rename on the destination
|
|
// directory load. This doesn't really test all corner cases, but is better
|
|
// than nothing.
|
|
|
|
// TODO rmdir tests:
|
|
//
|
|
// not empty
|
|
//
|
|
// not present
|
|
// not materialized, completely unloaded
|
|
// not materialized, inode assigned
|
|
// not materialized, loaded
|
|
// materialized, does not exist in source control
|
|
// materialized, modified from source control
|
|
//
|
|
// async:
|
|
// - concurrent load+rmdir
|
|
// - concurrent rename+rmdir
|
|
// - concurrent rmdir+rmdir
|
|
//
|
|
// - concurrent rename+rmdir+rmdir:
|
|
// 1. make sure a/b/c/ is not ready yet.
|
|
// 2. start rename(a/b/c --> other_dir/c)
|
|
// 3. start rmdir(a/b/c)
|
|
// 4. start rmdir(a/b/c)
|
|
// 5. make a/b/c ready
|
|
//
|
|
// - concurrent rename+rmdir+rmdir:
|
|
// 1. make sure neither a/b nor a/b/c/ are ready yet.
|
|
// 2. start rename(a/b/c --> other_dir/c).then(rmdir a/b)
|
|
// 3. start rmdir(a/b/c)
|
|
// 4. make a/b/c ready
|
|
// This should hopefully trigger the rmdir(a/b) to succeed before
|
|
// rmdir(a/b/c) completes.
|
|
//
|
|
// - attempt to create child in subdir after rmdir
|
|
// - attempt to mkdir child in subdir after rmdir
|