From 9046a54aaa2483ff56c4cb04f5f38ac199cdacf8 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Fri, 21 Nov 2014 20:46:39 +0100 Subject: [PATCH] Written tests for close() and flush() --- src/fspp/fuse/Fuse.cpp | 1 + .../fspp/fuse/closeFile/FuseCloseTest.cpp | 101 ++++++++++++++++++ .../fspp/fuse/flush/FuseFlushErrorTest.cpp | 34 ++++++ .../flush/FuseFlushFileDescriptorTest.cpp | 39 +++++++ .../fspp/fuse/flush/testutils/FuseFlushTest.h | 34 ++++++ 5 files changed, 209 insertions(+) create mode 100644 src/test/fspp/fuse/closeFile/FuseCloseTest.cpp create mode 100644 src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp create mode 100644 src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp create mode 100644 src/test/fspp/fuse/flush/testutils/FuseFlushTest.h diff --git a/src/fspp/fuse/Fuse.cpp b/src/fspp/fuse/Fuse.cpp index c1085bb2..117beeed 100644 --- a/src/fspp/fuse/Fuse.cpp +++ b/src/fspp/fuse/Fuse.cpp @@ -102,6 +102,7 @@ int fusepp_flush(const char *path, fuse_file_info *fileinfo) { } int fusepp_fsync(const char *path, int datasync, fuse_file_info *fileinfo) { + printf("Fsync\n");fflush(stdout); return FUSE_OBJ->fsync(bf::path(path), datasync, fileinfo); } diff --git a/src/test/fspp/fuse/closeFile/FuseCloseTest.cpp b/src/test/fspp/fuse/closeFile/FuseCloseTest.cpp new file mode 100644 index 00000000..7f1f9bee --- /dev/null +++ b/src/test/fspp/fuse/closeFile/FuseCloseTest.cpp @@ -0,0 +1,101 @@ +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "test/testutils/FuseTest.h" +#include + +using ::testing::_; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::WithParamInterface; +using ::testing::Values; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::AtLeast; + +using std::string; +using std::mutex; +using std::unique_lock; +using std::condition_variable; +using std::chrono::duration; +using std::chrono::seconds; + +// The fuse behaviour is: For each open(), there will be exactly one call to release(). +// Directly before this call to release(), flush() will be called. After flush() returns, +// the ::close() syscall (in the process using the filesystem) returns. So the fuse release() call is +// called asynchronously afterwards. Errors have to be returned in the implementation of flush(). + +// Citing FUSE spec: +// 1) Flush is called on each close() of a file descriptor. +// 2) Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all. +// I can't get these sentences together. For the test cases here, I go with the first one and assume that +// flush() will ALWAYS be called on a file close. + +class Barrier { +public: + template + void WaitAtMost(const duration &atMost) { + unique_lock lock(m); + if (!finished) { + cv.wait_for(lock, atMost, [this] () {return finished;}); + } + } + + void Release() { + unique_lock lock(m); + finished = true; + cv.notify_all(); + } +private: + mutex m; + condition_variable cv; + bool finished = false; +}; + +class FuseCloseTest: public FuseTest, public WithParamInterface { +public: + const string FILENAME = "/myfile"; + + void OpenAndCloseFile(const string &filename) { + auto fs = TestFS(); + int fd = OpenFile(fs.get(), filename); + CloseFile(fd); + } + + int OpenFile(const TempTestFS *fs, const string &filename) { + auto real_path = fs->mountDir() / filename; + int fd = ::open(real_path.c_str(), O_RDONLY); + EXPECT_GE(fd, 0) << "Opening file failed"; + return fd; + } + + void CloseFile(int fd) { + int retval = ::close(fd); + EXPECT_EQ(0, retval); + } +}; +INSTANTIATE_TEST_CASE_P(FuseCloseTest, FuseCloseTest, Values(0, 1, 2, 100, 1024*1024*1024)); + +//TODO Figure out what's wrong and enable this test +//Disabled, because it is flaky. libfuse seems to not send the release() event sometimes. +TEST_P(FuseCloseTest, DISABLED_CloseFile) { + Barrier barrier; + + ReturnIsFileOnLstat(FILENAME); + EXPECT_CALL(fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); + { + //InSequence fileCloseSequence; + EXPECT_CALL(fsimpl, flush(Eq(GetParam()))).Times(1); + EXPECT_CALL(fsimpl, closeFile(Eq(GetParam()))).Times(1).WillOnce(Invoke([&barrier] (int) { + // Release the waiting lock at the end of this test case, because the fuse release() came in now. + printf("RELEASING\n");fflush(stdout); + barrier.Release(); + })); + } + + OpenAndCloseFile(FILENAME); + + // Wait, until fuse release() was called, so we can check for the function call expectation. + barrier.WaitAtMost(seconds(10)); +} diff --git a/src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp b/src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp new file mode 100644 index 00000000..b9c0d31f --- /dev/null +++ b/src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp @@ -0,0 +1,34 @@ +#include "testutils/FuseFlushTest.h" + +#include "fspp/impl/FuseErrnoException.h" + +using ::testing::WithParamInterface; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::Return; +using ::testing::Throw; +using ::testing::AtLeast; +using ::testing::Values; +using ::testing::_; + +using fspp::FuseErrnoException; + +class FuseFlushErrorTest: public FuseFlushTest, public WithParamInterface { +}; +INSTANTIATE_TEST_CASE_P(FuseFlushErrorTest, FuseFlushErrorTest, Values(EBADF, EINTR, EIO)); + +TEST_P(FuseFlushErrorTest, ReturnErrorFromFlush) { + ReturnIsFileOnLstat(FILENAME); + + EXPECT_CALL(fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); + EXPECT_CALL(fsimpl, flush(Eq(GetParam()))).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); + // Allow calls to closeFile(), but don't enforce them + EXPECT_CALL(fsimpl, closeFile(Eq(GetParam()))).Times(AtLeast(0)); + + auto fs = TestFS(); + int fd = OpenFile(fs.get(), FILENAME); + + int close_result = ::close(fd); + EXPECT_EQ(-1, close_result); + EXPECT_EQ(GetParam(), errno); +} diff --git a/src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp b/src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp new file mode 100644 index 00000000..cf369062 --- /dev/null +++ b/src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp @@ -0,0 +1,39 @@ +#include "testutils/FuseFlushTest.h" + +using ::testing::_; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::WithParamInterface; +using ::testing::Values; +using ::testing::Return; +using ::testing::AtLeast; + +using std::string; + +// The fuse behaviour is: For each open(), there will be exactly one call to release(). +// Directly before this call to release(), flush() will be called. After flush() returns, +// the ::close() syscall (in the process using the filesystem) returns. So the fuse release() call is +// called asynchronously afterwards. Errors have to be returned in the implementation of flush(). + +// Citing FUSE spec: +// 1) Flush is called on each close() of a file descriptor. +// 2) Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all. +// I can't get these sentences together. For the test cases here, I go with the first one and assume that +// flush() will ALWAYS be called on a file close. + +class FuseFlushFileDescriptorTest: public FuseFlushTest, public WithParamInterface { +}; +INSTANTIATE_TEST_CASE_P(FuseFlushFileDescriptorTest, FuseFlushFileDescriptorTest, Values(0, 1, 2, 100, 1024*1024*1024)); + +TEST_P(FuseFlushFileDescriptorTest, FlushOnCloseFile) { + ReturnIsFileOnLstat(FILENAME); + + EXPECT_CALL(fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); + EXPECT_CALL(fsimpl, flush(Eq(GetParam()))).Times(1); + // Allow calls to closeFile(), but don't enforce them + EXPECT_CALL(fsimpl, closeFile(Eq(GetParam()))).Times(AtLeast(0)); + + OpenAndCloseFile(FILENAME); +} + + diff --git a/src/test/fspp/fuse/flush/testutils/FuseFlushTest.h b/src/test/fspp/fuse/flush/testutils/FuseFlushTest.h new file mode 100644 index 00000000..609e7b09 --- /dev/null +++ b/src/test/fspp/fuse/flush/testutils/FuseFlushTest.h @@ -0,0 +1,34 @@ +#pragma once +#ifndef TEST_FSPP_FUSE_FLUSH_TESTUTILS_FUSEFLUSHTEST_H_ +#define TEST_FSPP_FUSE_FLUSH_TESTUTILS_FUSEFLUSHTEST_H_ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "test/testutils/FuseTest.h" + +class FuseFlushTest: public FuseTest { +public: + const std::string FILENAME = "/myfile"; + + void OpenAndCloseFile(const std::string &filename) { + auto fs = TestFS(); + int fd = OpenFile(fs.get(), filename); + CloseFile(fd); + } + + int OpenFile(const TempTestFS *fs, const std::string &filename) { + auto real_path = fs->mountDir() / filename; + int fd = ::open(real_path.c_str(), O_RDONLY); + EXPECT_GE(fd, 0) << "Opening file failed"; + return fd; + } + + void CloseFile(int fd) { + int retval = ::close(fd); + EXPECT_EQ(0, retval); + } +}; + + +#endif