sapling/eden/fs/inodes/test/RemoveTest.cpp
Marshall Cline d71a100be2 use rvalue-qual Future::get(): pass 5
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
2018-07-02 07:05:52 -07:00

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