Test cases for read()

This commit is contained in:
Sebastian Messmer 2014-11-27 03:48:38 +01:00
parent 96826349e7
commit 6c86b4ab2a
10 changed files with 291 additions and 5 deletions

View File

@ -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); //printf("read(%s, _, %zu, %zu, _)\n", path.c_str(), size, offset);
UNUSED(path); UNUSED(path);
try { try {
//printf("Reading from file %d\n", fileinfo->fh);
//fflush(stdout);
return _fs->read(fileinfo->fh, buf, size, offset); return _fs->read(fileinfo->fh, buf, size, offset);
} catch (FuseErrnoException &e) { } catch (FuseErrnoException &e) {
return -e.getErrno(); return -e.getErrno();

View File

@ -20,8 +20,8 @@ public:
virtual void fstat(int descriptor, struct ::stat *stbuf) = 0; virtual void fstat(int descriptor, struct ::stat *stbuf) = 0;
virtual void truncate(const boost::filesystem::path &path, off_t size) = 0; virtual void truncate(const boost::filesystem::path &path, off_t size) = 0;
virtual void ftruncate(int descriptor, 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; 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 write(int descriptor, const void *buf, size_t count, off_t offset) = 0;
virtual void fsync(int descriptor) = 0; virtual void fsync(int descriptor) = 0;
virtual void fdatasync(int descriptor) = 0; virtual void fdatasync(int descriptor) = 0;

View File

@ -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<int> {
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;
}

View File

@ -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<int> {
};
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);
}

View File

@ -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);
}

View File

@ -0,0 +1,95 @@
#include "testutils/FuseReadTest.h"
#include "fspp/impl/FuseErrnoException.h"
#include <tuple>
#include <cstdlib>
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<size_t, off_t, size_t> &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<tuple<size_t, off_t, size_t>> {
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<int(int, void*, size_t, off_t)> 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<testData.fileSize()/sizeof(long long int); ++i) {
//MMIX linear congruential generator
val *= 6364136223846793005L;
val += 1442695040888963407;
reinterpret_cast<long long int*>(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;
}

View File

@ -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;
}

View File

@ -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<int(int, void*, size_t, off_t)> ReturnSuccessfulRead =
::testing::Invoke([](int, void *, size_t count, off_t) {
return count;
});
// This read() mock implementation reads from the stored random data.
::testing::Action<int(int, void*, size_t, off_t)> 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

View File

@ -15,6 +15,10 @@ void FuseTest::ReturnIsFileOnLstat(const bf::path &path) {
EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsFile); 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) { void FuseTest::ReturnIsDirOnLstat(const bf::path &path) {
EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsDir); EXPECT_CALL(fsimpl, lstat(::testing::StrEq(path.c_str()), ::testing::_)).WillRepeatedly(ReturnIsDir);
} }

View File

@ -125,11 +125,15 @@ public:
MockFilesystem fsimpl; 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 //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<void(const char*, struct ::stat*)> ReturnIsFile = ::testing::Action<void(const char*, struct ::stat*)> ReturnIsFile = ReturnIsFileWithSize(0);
::testing::Invoke([](const char*, struct ::stat* result) {
::testing::Action<void(const char*, struct ::stat*)> 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_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
result->st_nlink = 1; result->st_nlink = 1;
result->st_size = size;
}); });
}
::testing::Action<void(int, struct ::stat*)> ReturnIsFileFstat = ::testing::Action<void(int, struct ::stat*)> ReturnIsFileFstat =
::testing::Invoke([](int, struct ::stat* result) { ::testing::Invoke([](int, struct ::stat* result) {
@ -146,6 +150,7 @@ public:
::testing::Action<void(const char*, struct ::stat*)> ReturnDoesntExist = ::testing::Throw(fspp::FuseErrnoException(ENOENT)); ::testing::Action<void(const char*, struct ::stat*)> ReturnDoesntExist = ::testing::Throw(fspp::FuseErrnoException(ENOENT));
void ReturnIsFileOnLstat(const bf::path &path); void ReturnIsFileOnLstat(const bf::path &path);
void ReturnIsFileOnLstatWithSize(const bf::path &path, const size_t size);
void ReturnIsDirOnLstat(const bf::path &path); void ReturnIsDirOnLstat(const bf::path &path);
void ReturnDoesntExistOnLstat(const bf::path &path); void ReturnDoesntExistOnLstat(const bf::path &path);
void OnOpenReturnFileDescriptor(const char *filename, int descriptor); void OnOpenReturnFileDescriptor(const char *filename, int descriptor);