diff --git a/src/fspp/fuse/Fuse.cpp b/src/fspp/fuse/Fuse.cpp index c1085bb2..fc84a547 100644 --- a/src/fspp/fuse/Fuse.cpp +++ b/src/fspp/fuse/Fuse.cpp @@ -399,8 +399,6 @@ int Fuse::read(const bf::path &path, char *buf, size_t size, off_t offset, fuse_ //printf("read(%s, _, %zu, %zu, _)\n", path.c_str(), size, offset); UNUSED(path); try { - //printf("Reading from file %d\n", fileinfo->fh); - //fflush(stdout); return _fs->read(fileinfo->fh, buf, size, offset); } catch (FuseErrnoException &e) { return -e.getErrno(); diff --git a/src/fspp/impl/Filesystem.h b/src/fspp/impl/Filesystem.h index 4e7a69e6..dbdd67c6 100644 --- a/src/fspp/impl/Filesystem.h +++ b/src/fspp/impl/Filesystem.h @@ -20,8 +20,8 @@ public: virtual void fstat(int descriptor, struct ::stat *stbuf) = 0; virtual void truncate(const boost::filesystem::path &path, off_t size) = 0; virtual void ftruncate(int descriptor, off_t size) = 0; - //TODO Unit-Tests for all functions below virtual int read(int descriptor, void *buf, size_t count, off_t offset) = 0; + //TODO Unit-Tests for all functions below virtual void write(int descriptor, const void *buf, size_t count, off_t offset) = 0; virtual void fsync(int descriptor) = 0; virtual void fdatasync(int descriptor) = 0; diff --git a/src/test/fspp/fuse/read/FuseReadErrorTest.cpp b/src/test/fspp/fuse/read/FuseReadErrorTest.cpp new file mode 100644 index 00000000..1dcbd455 --- /dev/null +++ b/src/test/fspp/fuse/read/FuseReadErrorTest.cpp @@ -0,0 +1,64 @@ +#include "testutils/FuseReadTest.h" + +#include "fspp/impl/FuseErrnoException.h" + +using ::testing::_; +using ::testing::StrEq; +using ::testing::WithParamInterface; +using ::testing::Values; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::Throw; + +using namespace fspp; + +class FuseReadErrorTest: public FuseReadTest, public WithParamInterface { +public: + size_t FILESIZE = 1024*1024*1024; + size_t READCOUNT = 512*1024*1024; + + void SetUp() override { + //Make the file size big enough that fuse should issue at least two reads + ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); + OnOpenReturnFileDescriptor(FILENAME, 0); + } +}; +INSTANTIATE_TEST_CASE_P(FuseReadErrorTest, FuseReadErrorTest, Values(EAGAIN, EBADF, EFAULT, EINTR, EINVAL, EIO, EISDIR, EOVERFLOW, ESPIPE, ENXIO)); + + +TEST_P(FuseReadErrorTest, ReturnErrorOnFirstReadCall) { + EXPECT_CALL(fsimpl, read(0, _, _, _)) + .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); + + char *buf = new char[READCOUNT]; + errno = 0; + int retval = ReadFileAllowError(FILENAME, buf, READCOUNT, 0); + EXPECT_EQ(GetParam(), errno); + EXPECT_EQ(-1, retval); + delete[] buf; +} + +TEST_P(FuseReadErrorTest, ReturnErrorOnSecondReadCall) { + // The first read request is from the beginning of the file and works, but the later ones fail. + // We store the number of bytes the first call could successfully read and check later that our + // read syscall returns exactly this number of bytes + size_t successfullyReadBytes = -1; + EXPECT_CALL(fsimpl, read(0, _, _, Eq(0))) + .Times(1) + .WillOnce(Invoke([&successfullyReadBytes](int, void*, size_t count, off_t) { + // Store the number of successfully read bytes + successfullyReadBytes = count; + return count; + })); + EXPECT_CALL(fsimpl, read(0, _, _, Ne(0))) + .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); + + char *buf = new char[READCOUNT]; + errno = 0; + size_t retval = ReadFileAllowError(FILENAME, buf, READCOUNT, 0); + EXPECT_EQ(0, errno); + EXPECT_EQ(successfullyReadBytes, retval); // Check that we're getting the number of successfully read bytes (the first read call) returned + delete[] buf; +} diff --git a/src/test/fspp/fuse/read/FuseReadFileDescriptorTest.cpp b/src/test/fspp/fuse/read/FuseReadFileDescriptorTest.cpp new file mode 100644 index 00000000..560b9e3c --- /dev/null +++ b/src/test/fspp/fuse/read/FuseReadFileDescriptorTest.cpp @@ -0,0 +1,29 @@ +#include "testutils/FuseReadTest.h" + +#include "fspp/impl/FuseErrnoException.h" + +using ::testing::_; +using ::testing::StrEq; +using ::testing::WithParamInterface; +using ::testing::Values; +using ::testing::Eq; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::Throw; + +using namespace fspp; + +class FuseReadFileDescriptorTest: public FuseReadTest, public WithParamInterface { +}; +INSTANTIATE_TEST_CASE_P(FuseReadFileDescriptorTest, FuseReadFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); + + +TEST_P(FuseReadFileDescriptorTest, FileDescriptorIsCorrect) { + ReturnIsFileOnLstat(FILENAME); + OnOpenReturnFileDescriptor(FILENAME, GetParam()); + EXPECT_CALL(fsimpl, read(Eq(GetParam()), _, _, _)) + .Times(1).WillOnce(ReturnSuccessfulRead); + + char buf[1]; + ReadFile(FILENAME, buf, 0, 0); +} diff --git a/src/test/fspp/fuse/read/FuseReadOverflowTest.cpp b/src/test/fspp/fuse/read/FuseReadOverflowTest.cpp new file mode 100644 index 00000000..163104f8 --- /dev/null +++ b/src/test/fspp/fuse/read/FuseReadOverflowTest.cpp @@ -0,0 +1,40 @@ +#include "testutils/FuseReadTest.h" + +#include "fspp/impl/FuseErrnoException.h" + +using ::testing::_; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::Action; + +using std::min; + +using namespace fspp; + +class FuseReadOverflowTest: public FuseReadTest { +public: + const size_t FILESIZE = 1000; + const size_t READSIZE = 2000; + const size_t OFFSET = 500; + + void SetUp() override { + ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); + OnOpenReturnFileDescriptor(FILENAME, 0); + EXPECT_CALL(fsimpl, read(0, _, _, _)).WillRepeatedly(ReturnSuccessfulReadRegardingSize(FILESIZE)); + } +}; + + +TEST_F(FuseReadOverflowTest, ReadMoreThanFileSizeFromBeginning) { + char buf[READSIZE]; + size_t read_bytes = ReadFileAllowError(FILENAME, buf, READSIZE, 0); + EXPECT_EQ(FILESIZE, read_bytes); +} + +TEST_F(FuseReadOverflowTest, ReadMoreThanFileSizeFromMiddle) { + char buf[READSIZE]; + size_t read_bytes = ReadFileAllowError(FILENAME, buf, READSIZE, OFFSET); + EXPECT_EQ(FILESIZE-OFFSET, read_bytes); +} diff --git a/src/test/fspp/fuse/read/FuseReadReturnedDataTest.cpp b/src/test/fspp/fuse/read/FuseReadReturnedDataTest.cpp new file mode 100644 index 00000000..779f31af --- /dev/null +++ b/src/test/fspp/fuse/read/FuseReadReturnedDataTest.cpp @@ -0,0 +1,95 @@ +#include "testutils/FuseReadTest.h" + +#include "fspp/impl/FuseErrnoException.h" + +#include +#include + +using ::testing::_; +using ::testing::StrEq; +using ::testing::WithParamInterface; +using ::testing::Values; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::Action; + +using std::tuple; +using std::get; +using std::min; + +using namespace fspp; + +// We can't test the count or size parameter directly, because fuse doesn't pass them 1:1. +// It usually asks to read bigger blocks (probably does some caching). +// But we can test that the data returned from the ::read syscall is the correct data region. + +struct TestData { + TestData(): count(0), offset(0), additional_bytes_at_end_of_file(0) {} + TestData(const tuple &data): count(get<0>(data)), offset(get<1>(data)), additional_bytes_at_end_of_file(get<2>(data)) {} + size_t count; + off_t offset; + //How many more bytes does the file have after the read block? + size_t additional_bytes_at_end_of_file; + size_t fileSize() { + return count + offset + additional_bytes_at_end_of_file; + } +}; + +// The testcase creates random data in memory, offers a mock read() implementation to read from this +// memory region and check methods to check for data equality of a region. +class FuseReadReturnedDataTest: public FuseReadTest, public WithParamInterface> { +public: + char *fileData; + TestData testData; + + void SetUp() override { + testData = GetParam(); + setupFileData(); + + ReturnIsFileOnLstatWithSize(FILENAME, testData.fileSize()); + OnOpenReturnFileDescriptor(FILENAME, 0); + EXPECT_CALL(fsimpl, read(0, _, _, _)) + .WillRepeatedly(ReadFromFile); + } + + void TearDown() override { + delete[] fileData; + } + + // Return true, iff the given data is equal to the data of the file at the given offset. + bool fileContentCorrect(char *content, size_t count, off_t offset) { + return 0 == memcmp(content, fileData + offset, count); + } + + // This read() mock implementation reads from the stored random data. + Action ReadFromFile = Invoke([this](int, void *buf, size_t count, off_t offset) { + size_t realCount = min(count, testData.fileSize() - offset); + memcpy(buf, fileData+offset, realCount); + return realCount; + }); +private: + void setupFileData() { + fileData = new char[testData.fileSize()]; + fillFileWithRandomData(); + } + void fillFileWithRandomData() { + long long int val = 1; + for(unsigned int i=0; i(fileData)[i] = val; + } + } +}; +INSTANTIATE_TEST_CASE_P(FuseReadReturnedDataTest, FuseReadReturnedDataTest, Combine(Values(0,1,10,1000,1024, 10*1024*1024), Values(0, 1, 10, 1024, 10*1024*1024), Values(0, 1, 10, 1024, 10*1024*1024))); + + +TEST_P(FuseReadReturnedDataTest, ReturnedDataRangeIsCorrect) { + char *buf = new char[testData.count]; + ReadFile(FILENAME, buf, testData.count, testData.offset); + EXPECT_TRUE(fileContentCorrect(buf, testData.count, testData.offset)); + delete[] buf; +} diff --git a/src/test/fspp/fuse/read/testutils/FuseReadTest.cpp b/src/test/fspp/fuse/read/testutils/FuseReadTest.cpp new file mode 100644 index 00000000..32c8e1ed --- /dev/null +++ b/src/test/fspp/fuse/read/testutils/FuseReadTest.cpp @@ -0,0 +1,20 @@ +#include "FuseReadTest.h" + +void FuseReadTest::ReadFile(const char *filename, void *buf, size_t count, off_t offset) { + size_t retval = ReadFileAllowError(filename, buf, count, offset); + EXPECT_EQ(count, retval); +} + +size_t FuseReadTest::ReadFileAllowError(const char *filename, void *buf, size_t count, off_t offset) { + auto fs = TestFS(); + + int fd = OpenFile(fs.get(), filename); + return ::pread(fd, buf, count, offset); +} + +int FuseReadTest::OpenFile(const TempTestFS *fs, const char *filename) { + auto realpath = fs->mountDir() / filename; + int fd = ::open(realpath.c_str(), O_RDONLY); + EXPECT_GE(fd, 0) << "Error opening file"; + return fd; +} diff --git a/src/test/fspp/fuse/read/testutils/FuseReadTest.h b/src/test/fspp/fuse/read/testutils/FuseReadTest.h new file mode 100644 index 00000000..29037bc6 --- /dev/null +++ b/src/test/fspp/fuse/read/testutils/FuseReadTest.h @@ -0,0 +1,31 @@ +#pragma once +#ifndef TEST_FSPP_FUSE_READ_TESTUTILS_FUSEREADTEST_H_ +#define TEST_FSPP_FUSE_READ_TESTUTILS_FUSEREADTEST_H_ + +#include "test/testutils/FuseTest.h" + +class FuseReadTest: public FuseTest { +public: + const char *FILENAME = "/myfile"; + + void ReadFile(const char *filename, void *buf, size_t count, off_t offset); + size_t ReadFileAllowError(const char *filename, void *buf, size_t count, off_t offset); + + ::testing::Action ReturnSuccessfulRead = + ::testing::Invoke([](int, void *, size_t count, off_t) { + return count; + }); + + // This read() mock implementation reads from the stored random data. + ::testing::Action ReturnSuccessfulReadRegardingSize(size_t filesize) { + return ::testing::Invoke([filesize](int, void *, size_t count, off_t offset) { + size_t ableToReadCount = std::min(count, filesize - offset); + return ableToReadCount; + }); + } + +private: + int OpenFile(const TempTestFS *fs, const char *filename); +}; + +#endif diff --git a/src/test/testutils/FuseTest.cpp b/src/test/testutils/FuseTest.cpp index d032d79b..f239dbc0 100644 --- a/src/test/testutils/FuseTest.cpp +++ b/src/test/testutils/FuseTest.cpp @@ -15,6 +15,10 @@ void FuseTest::ReturnIsFileOnLstat(const bf::path &path) { EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsFile); } +void FuseTest::ReturnIsFileOnLstatWithSize(const bf::path &path, const size_t size) { + EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsFileWithSize(size)); +} + void FuseTest::ReturnIsDirOnLstat(const bf::path &path) { EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsDir); } diff --git a/src/test/testutils/FuseTest.h b/src/test/testutils/FuseTest.h index c1634b8f..0d06290e 100644 --- a/src/test/testutils/FuseTest.h +++ b/src/test/testutils/FuseTest.h @@ -125,11 +125,15 @@ public: MockFilesystem fsimpl; //TODO Combine ReturnIsFile and ReturnIsFileFstat. This should be possible in gmock by either (a) using ::testing::Undefined as parameter type or (b) using action macros - ::testing::Action ReturnIsFile = - ::testing::Invoke([](const char*, struct ::stat* result) { + ::testing::Action ReturnIsFile = ReturnIsFileWithSize(0); + + ::testing::Action ReturnIsFileWithSize(size_t size) { + return ::testing::Invoke([size](const char*, struct ::stat* result) { result->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; result->st_nlink = 1; + result->st_size = size; }); + } ::testing::Action ReturnIsFileFstat = ::testing::Invoke([](int, struct ::stat* result) { @@ -146,6 +150,7 @@ public: ::testing::Action ReturnDoesntExist = ::testing::Throw(fspp::FuseErrnoException(ENOENT)); void ReturnIsFileOnLstat(const bf::path &path); + void ReturnIsFileOnLstatWithSize(const bf::path &path, const size_t size); void ReturnIsDirOnLstat(const bf::path &path); void ReturnDoesntExistOnLstat(const bf::path &path); void OnOpenReturnFileDescriptor(const char *filename, int descriptor);