Written tests for close() and flush()
This commit is contained in:
parent
9b985d98db
commit
9046a54aaa
@ -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);
|
||||
}
|
||||
|
||||
|
101
src/test/fspp/fuse/closeFile/FuseCloseTest.cpp
Normal file
101
src/test/fspp/fuse/closeFile/FuseCloseTest.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "test/testutils/FuseTest.h"
|
||||
#include <condition_variable>
|
||||
|
||||
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<class A, class B>
|
||||
void WaitAtMost(const duration<A, B> &atMost) {
|
||||
unique_lock<mutex> lock(m);
|
||||
if (!finished) {
|
||||
cv.wait_for(lock, atMost, [this] () {return finished;});
|
||||
}
|
||||
}
|
||||
|
||||
void Release() {
|
||||
unique_lock<mutex> lock(m);
|
||||
finished = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
private:
|
||||
mutex m;
|
||||
condition_variable cv;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
class FuseCloseTest: public FuseTest, public WithParamInterface<int> {
|
||||
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));
|
||||
}
|
34
src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp
Normal file
34
src/test/fspp/fuse/flush/FuseFlushErrorTest.cpp
Normal file
@ -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<int> {
|
||||
};
|
||||
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);
|
||||
}
|
39
src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp
Normal file
39
src/test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp
Normal file
@ -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<int> {
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
|
34
src/test/fspp/fuse/flush/testutils/FuseFlushTest.h
Normal file
34
src/test/fspp/fuse/flush/testutils/FuseFlushTest.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user