mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 00:45:18 +03:00
1844b98457
Summary: We want to use these on Windows too, so let's rename them to not be FUSE specific. Reviewed By: wez Differential Revision: D21676317 fbshipit-source-id: 9f1bde9e0467d8828d4168a69581c8fea54669cd
188 lines
6.5 KiB
C++
188 lines
6.5 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This software may be used and distributed according to the terms of the
|
|
* GNU General Public License version 2.
|
|
*/
|
|
|
|
#include <chrono>
|
|
#include "eden/fs/testharness/FakeFuse.h"
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
#include "eden/fs/utils/UnboundedQueueExecutor.h"
|
|
|
|
#include <folly/executors/ManualExecutor.h>
|
|
#include <folly/io/async/EventBase.h>
|
|
#include <folly/io/async/ScopedEventBaseThread.h>
|
|
#include <folly/logging/xlog.h>
|
|
#include <folly/portability/GMock.h>
|
|
#include <folly/portability/GTest.h>
|
|
#include <folly/test/TestUtils.h>
|
|
|
|
using namespace facebook::eden;
|
|
using namespace std::chrono_literals;
|
|
using folly::Future;
|
|
using folly::Unit;
|
|
using std::make_shared;
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* The FUSE tests wait for work to finish on a thread pool. 250ms is too short
|
|
* for the test to reliably test under heavy system load (such as when stress
|
|
* testing), so wait for 10 seconds.
|
|
*/
|
|
constexpr std::chrono::seconds kWaitTimeout = 10s;
|
|
|
|
} // namespace
|
|
|
|
TEST(FuseTest, initMount) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
TestMount testMount{builder1};
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
auto initFuture =
|
|
testMount.getEdenMount()
|
|
->startChannel(false)
|
|
.thenValue([](auto&&) { XLOG(INFO) << "startChannel() succeeded"; })
|
|
.thenError([&](const folly::exception_wrapper& ew) {
|
|
ADD_FAILURE() << "startFuse() failed: " << folly::exceptionStr(ew);
|
|
});
|
|
|
|
auto reqID = fuse->sendInitRequest();
|
|
auto response = fuse->recvResponse();
|
|
EXPECT_EQ(reqID, response.header.unique);
|
|
EXPECT_EQ(0, response.header.error);
|
|
EXPECT_EQ(
|
|
sizeof(fuse_out_header) + sizeof(fuse_init_out), response.header.len);
|
|
|
|
// Wait for the mount to complete
|
|
testMount.drainServerExecutor();
|
|
std::move(initFuture).get(kWaitTimeout);
|
|
|
|
// Close the FakeFuse device, and ensure that the mount's FUSE completion
|
|
// future is then signalled.
|
|
fuse->close();
|
|
|
|
auto fuseCompletionFuture =
|
|
testMount.getEdenMount()->getChannelCompletionFuture();
|
|
|
|
// TestMount has a manual executor, but the fuse channel thread enqueues
|
|
// the work. Wait for the future to complete, driving the ManualExecutor
|
|
// all the while.
|
|
// TODO: It might be worth moving this logic into a TestMount::waitFuture
|
|
// method.
|
|
auto deadline = std::chrono::steady_clock::now() + kWaitTimeout;
|
|
do {
|
|
if (std::chrono::steady_clock::now() > deadline) {
|
|
FAIL() << "fuse completion future not ready within timeout";
|
|
}
|
|
testMount.drainServerExecutor();
|
|
} while (!fuseCompletionFuture.isReady());
|
|
|
|
auto mountInfo = std::move(fuseCompletionFuture.value());
|
|
|
|
// Since we closed the FUSE device from the "kernel" side the returned
|
|
// MountInfo should not contain a valid FUSE device any more.
|
|
EXPECT_FALSE(mountInfo.fuseFD);
|
|
}
|
|
|
|
// Test destroying the EdenMount object while FUSE initialization is still
|
|
// pending
|
|
TEST(FuseTest, destroyBeforeInitComplete) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
auto initFuture = Future<Unit>::makeEmpty();
|
|
{
|
|
// Create the TestMountj
|
|
TestMount testMount{builder1};
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
// Call startFuse() on the test mount.
|
|
initFuture = testMount.getEdenMount()->startChannel(false);
|
|
|
|
// Exit the scope to destroy the mount
|
|
}
|
|
|
|
// The initFuture() should have completed unsuccessfully.
|
|
EXPECT_THROW_RE(
|
|
std::move(initFuture).get(100ms),
|
|
std::runtime_error,
|
|
"FuseChannel for .* stopped while waiting for INIT packet");
|
|
}
|
|
|
|
// Test destroying the EdenMount object immediately after the FUSE INIT request
|
|
// has been received. We previously had some race conditions that could cause
|
|
// problems here.
|
|
TEST(FuseTest, destroyWithInitRace) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
auto initFuture = Future<Unit>::makeEmpty();
|
|
auto completionFuture = Future<TakeoverData::MountInfo>::makeEmpty();
|
|
{
|
|
// Create the TestMount.
|
|
TestMount testMount{builder1};
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
// Call startFuse() on the test mount.
|
|
initFuture = testMount.getEdenMount()->startChannel(false);
|
|
completionFuture = testMount.getEdenMount()->getChannelCompletionFuture();
|
|
|
|
// Send the FUSE INIT request.
|
|
auto reqID = fuse->sendInitRequest();
|
|
|
|
// Wait to receive the INIT reply from the FuseChannel code to confirm
|
|
// that it saw the INIT request.
|
|
auto response = fuse->recvResponse();
|
|
EXPECT_EQ(reqID, response.header.unique);
|
|
EXPECT_EQ(0, response.header.error);
|
|
EXPECT_EQ(
|
|
sizeof(fuse_out_header) + sizeof(fuse_init_out), response.header.len);
|
|
|
|
// Exit the scope to destroy the TestMount.
|
|
// This will call EdenMount::destroy() to start destroying the EdenMount.
|
|
// However, this may not complete immediately. Previously we had a bug
|
|
// where the ServerState object was not guaranteed to survive until the
|
|
// EdenMount was completely destroyed in this case.
|
|
}
|
|
|
|
// The EdenMount code will perform processing of the FUSE_INIT request in a
|
|
// separate thread. It may or may not have finished processing initialization
|
|
// before we destroyed it.
|
|
//
|
|
// It performs more EdenMount initialization after sending the FUSE_INIT
|
|
// reply, so it my still have failed even though we saw the FUSE_INIT reply
|
|
// sent back to the kernel.
|
|
auto initFuseSuccessful = false;
|
|
try {
|
|
std::move(initFuture).get(250ms);
|
|
initFuseSuccessful = true;
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_THAT(
|
|
ex.what(),
|
|
::testing::ContainsRegex(
|
|
"unable to transition mount .* to state RUNNING: "
|
|
"expected to be in state STARTING but actually in SHUT_DOWN"));
|
|
}
|
|
|
|
if (initFuseSuccessful) {
|
|
// The FUSE completion future should also be signalled when the FuseChannel
|
|
// is destroyed.
|
|
auto mountInfo = std::move(completionFuture).get(250ms);
|
|
// Since we just destroyed the EdenMount and the kernel-side of the FUSE
|
|
// channel was not stopped the returned MountInfo should contain the FUSE
|
|
// device.
|
|
EXPECT_TRUE(mountInfo.fuseFD);
|
|
}
|
|
}
|