Added write() test cases

This commit is contained in:
Sebastian Messmer 2014-11-27 16:40:22 +01:00
parent b429432d83
commit 141c7d09cd
8 changed files with 364 additions and 15 deletions

View File

@ -0,0 +1,82 @@
#include "testutils/FuseWriteTest.h"
#include "test/testutils/VirtualTestFile.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 std::unique_ptr;
using std::make_unique;
using namespace fspp;
// We can't test the count or size parameter directly, because fuse doesn't pass them 1:1.
// But we can test that the data passed to the ::write syscall is correctly written.
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 write() implementation to write to this
// memory region and check methods to check for data equality of a region.
class FuseWriteDataTest: public FuseWriteTest, public WithParamInterface<tuple<size_t, off_t, size_t>> {
public:
unique_ptr<VirtualTestFileWriteable> testFile;
TestData testData;
FuseWriteDataTest() {
testData = GetParam();
testFile = make_unique<VirtualTestFileWriteable>(testData.fileSize(), 1);
ReturnIsFileOnLstatWithSize(FILENAME, testData.fileSize());
OnOpenReturnFileDescriptor(FILENAME, 0);
EXPECT_CALL(fsimpl, write(0, _, _, _))
.WillRepeatedly(WriteToFile);
}
// This write() mock implementation writes to the stored virtual file.
Action<void(int, const void*, size_t, off_t)> WriteToFile = Invoke([this](int, const void *buf, size_t count, off_t offset) {
testFile->write(buf, count, offset);
});
};
INSTANTIATE_TEST_CASE_P(FuseWriteDataTest, FuseWriteDataTest, 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(FuseWriteDataTest, DataWasCorrectlyWritten) {
VirtualTestFile randomWriteData(testData.count, 2);
WriteFile(FILENAME, randomWriteData.data(), testData.count, testData.offset);
EXPECT_TRUE(testFile->fileContentEqual(randomWriteData.data(), testData.count, testData.offset));
}
TEST_P(FuseWriteDataTest, RestOfFileIsUnchanged) {
VirtualTestFile randomWriteData(testData.count, 2);
WriteFile(FILENAME, randomWriteData.data(), testData.count, testData.offset);
EXPECT_TRUE(testFile->sizeUnchanged());
EXPECT_TRUE(testFile->regionUnchanged(0, testData.offset));
EXPECT_TRUE(testFile->regionUnchanged(testData.offset + testData.count, testData.additional_bytes_at_end_of_file));
}

View File

@ -0,0 +1,64 @@
#include "testutils/FuseWriteTest.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 FuseWriteErrorTest: public FuseWriteTest, public WithParamInterface<int> {
public:
size_t FILESIZE = 1024*1024*1024;
size_t WRITECOUNT = 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(FuseWriteErrorTest, FuseWriteErrorTest, Values(EAGAIN, EBADF, EDESTADDRREQ, EDQUOT, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENOSPC, EPIPE, EOVERFLOW, ESPIPE, ENXIO));
TEST_P(FuseWriteErrorTest, ReturnErrorOnFirstWriteCall) {
EXPECT_CALL(fsimpl, write(0, _, _, _))
.WillRepeatedly(Throw(FuseErrnoException(GetParam())));
char *buf = new char[WRITECOUNT];
errno = 0;
int retval = WriteFileAllowError(FILENAME, buf, WRITECOUNT, 0);
EXPECT_EQ(GetParam(), errno);
EXPECT_EQ(-1, retval);
delete[] buf;
}
TEST_P(FuseWriteErrorTest, ReturnErrorOnSecondWriteCall) {
// The first write 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 write and check later that our
// write syscall returns exactly this number of bytes
size_t successfullyWrittenBytes = -1;
EXPECT_CALL(fsimpl, write(0, _, _, Eq(0)))
.Times(1)
.WillOnce(Invoke([&successfullyWrittenBytes](int, const void*, size_t count, off_t) {
// Store the number of successfully written bytes
successfullyWrittenBytes = count;
}));
EXPECT_CALL(fsimpl, write(0, _, _, Ne(0)))
.WillRepeatedly(Throw(FuseErrnoException(GetParam())));
char *buf = new char[WRITECOUNT];
errno = 0;
size_t retval = WriteFileAllowError(FILENAME, buf, WRITECOUNT, 0);
EXPECT_EQ(0, errno);
EXPECT_EQ(successfullyWrittenBytes, retval); // Check that we're getting the number of successfully written bytes (the first write call) returned
delete[] buf;
}

View File

@ -0,0 +1,29 @@
#include "testutils/FuseWriteTest.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 FuseWriteFileDescriptorTest: public FuseWriteTest, public WithParamInterface<int> {
};
INSTANTIATE_TEST_CASE_P(FuseWriteFileDescriptorTest, FuseWriteFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024));
TEST_P(FuseWriteFileDescriptorTest, FileDescriptorIsCorrect) {
ReturnIsFileOnLstat(FILENAME);
OnOpenReturnFileDescriptor(FILENAME, GetParam());
EXPECT_CALL(fsimpl, write(Eq(GetParam()), _, _, _))
.Times(1).WillOnce(Return());
char buf[1];
WriteFile(FILENAME, buf, 1, 0);
}

View File

@ -0,0 +1,86 @@
#include "testutils/FuseWriteTest.h"
#include "test/testutils/VirtualTestFile.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 FuseWriteOverflowTest: public FuseWriteTest {
public:
size_t FILESIZE;
size_t WRITESIZE;
size_t OFFSET;
VirtualTestFileWriteable testFile;
VirtualTestFile writeData;
FuseWriteOverflowTest(size_t filesize, size_t writesize, size_t offset)
: FILESIZE(filesize), WRITESIZE(writesize), OFFSET(offset), testFile(FILESIZE), writeData(WRITESIZE) {
ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE);
OnOpenReturnFileDescriptor(FILENAME, 0);
EXPECT_CALL(fsimpl, write(0, _, _, _)).WillRepeatedly(WriteToFile);
}
// This write() mock implementation writes to the stored virtual file.
Action<void(int, const void*, size_t, off_t)> WriteToFile =
Invoke([this](int, const void *buf, size_t count, off_t offset) {
testFile.write(buf, count, offset);
});
};
class FuseWriteOverflowTestWithNonemptyFile: public FuseWriteOverflowTest {
public:
FuseWriteOverflowTestWithNonemptyFile(): FuseWriteOverflowTest(1000, 2000, 500) {}
};
TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteMoreThanFileSizeFromBeginning) {
WriteFile(FILENAME, writeData.data(), WRITESIZE, 0);
EXPECT_EQ(WRITESIZE, testFile.size());
EXPECT_TRUE(testFile.fileContentEqual(writeData.data(), WRITESIZE, 0));
}
TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteMoreThanFileSizeFromMiddle) {
WriteFile(FILENAME, writeData.data(), WRITESIZE, OFFSET);
EXPECT_EQ(OFFSET + WRITESIZE, testFile.size());
EXPECT_TRUE(testFile.regionUnchanged(0, OFFSET));
EXPECT_TRUE(testFile.fileContentEqual(writeData.data(), WRITESIZE, OFFSET));
}
TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteAfterFileEnd) {
WriteFile(FILENAME, writeData.data(), WRITESIZE, FILESIZE + OFFSET);
EXPECT_EQ(FILESIZE + OFFSET + WRITESIZE, testFile.size());
EXPECT_TRUE(testFile.regionUnchanged(0, FILESIZE));
EXPECT_TRUE(testFile.fileContentEqual(writeData.data(), WRITESIZE, FILESIZE + OFFSET));
}
class FuseWriteOverflowTestWithEmptyFile: public FuseWriteOverflowTest {
public:
FuseWriteOverflowTestWithEmptyFile(): FuseWriteOverflowTest(0, 2000, 500) {}
};
TEST_F(FuseWriteOverflowTestWithEmptyFile, WriteToBeginOfEmptyFile) {
WriteFile(FILENAME, writeData.data(), WRITESIZE, 0);
EXPECT_EQ(WRITESIZE, testFile.size());
EXPECT_TRUE(testFile.fileContentEqual(writeData.data(), WRITESIZE, 0));
}
TEST_F(FuseWriteOverflowTestWithEmptyFile, WriteAfterFileEnd) {
WriteFile(FILENAME, writeData.data(), WRITESIZE, OFFSET);
EXPECT_EQ(OFFSET + WRITESIZE, testFile.size());
EXPECT_TRUE(testFile.fileContentEqual(writeData.data(), WRITESIZE, OFFSET));
}

View File

@ -0,0 +1,20 @@
#include "FuseWriteTest.h"
void FuseWriteTest::WriteFile(const char *filename, const void *buf, size_t count, off_t offset) {
size_t retval = WriteFileAllowError(filename, buf, count, offset);
EXPECT_EQ(count, retval);
}
size_t FuseWriteTest::WriteFileAllowError(const char *filename, const void *buf, size_t count, off_t offset) {
auto fs = TestFS();
int fd = OpenFile(fs.get(), filename);
return ::pwrite(fd, buf, count, offset);
}
int FuseWriteTest::OpenFile(const TempTestFS *fs, const char *filename) {
auto realpath = fs->mountDir() / filename;
int fd = ::open(realpath.c_str(), O_WRONLY);
EXPECT_GE(fd, 0) << "Error opening file";
return fd;
}

View File

@ -0,0 +1,18 @@
#pragma once
#ifndef TEST_FSPP_FUSE_WRITE_TESTUTILS_FUSEWRITETEST_H_
#define TEST_FSPP_FUSE_WRITE_TESTUTILS_FUSEWRITETEST_H_
#include "test/testutils/FuseTest.h"
class FuseWriteTest: public FuseTest {
public:
const char *FILENAME = "/myfile";
void WriteFile(const char *filename, const void *buf, size_t count, off_t offset);
size_t WriteFileAllowError(const char *filename, const void *buf, size_t count, off_t offset);
private:
int OpenFile(const TempTestFS *fs, const char *filename);
};
#endif

View File

@ -5,16 +5,16 @@
using std::min; using std::min;
VirtualTestFile::VirtualTestFile(size_t size): _fileData(new char[size]), _size(size) { VirtualTestFile::VirtualTestFile(size_t size, long long int IV): _fileData(new char[size]), _size(size) {
fillFileWithRandomData(); fillFileWithRandomData(IV);
} }
VirtualTestFile::~VirtualTestFile() { VirtualTestFile::~VirtualTestFile() {
delete[] _fileData; delete[] _fileData;
} }
void VirtualTestFile::fillFileWithRandomData() { void VirtualTestFile::fillFileWithRandomData(long long int IV) {
long long int val = 1; long long int val = IV;
for(size_t i=0; i<_size/sizeof(long long int); ++i) { for(size_t i=0; i<_size/sizeof(long long int); ++i) {
//MMIX linear congruential generator //MMIX linear congruential generator
val *= 6364136223846793005L; val *= 6364136223846793005L;
@ -23,22 +23,58 @@ void VirtualTestFile::fillFileWithRandomData() {
} }
} }
const char *VirtualTestFile::data() {
return _fileData;
}
int VirtualTestFile::read(void *buf, size_t count, off_t offset) { int VirtualTestFile::read(void *buf, size_t count, off_t offset) {
size_t realCount = min(count, _size - offset); size_t realCount = min(count, _size - offset);
memcpy(buf, _fileData+offset, realCount); memcpy(buf, _fileData+offset, realCount);
return realCount; return realCount;
} }
bool VirtualTestFile::fileContentEqual(char *content, size_t count, off_t offset) { size_t VirtualTestFile::size() {
return _size;
}
bool VirtualTestFile::fileContentEqual(const char *content, size_t count, off_t offset) {
return 0 == memcmp(content, _fileData + offset, count); return 0 == memcmp(content, _fileData + offset, count);
} }
VirtualTestFileWriteable::VirtualTestFileWriteable(size_t size) VirtualTestFileWriteable::VirtualTestFileWriteable(size_t size, long long int IV)
:VirtualTestFile(size) { :VirtualTestFile(size, IV), _originalSize(size) {
originalFileData = new char[size]; _originalFileData = new char[size];
memcpy(originalFileData, _fileData, size); memcpy(_originalFileData, _fileData, size);
} }
VirtualTestFileWriteable::~VirtualTestFileWriteable() { VirtualTestFileWriteable::~VirtualTestFileWriteable() {
delete[] originalFileData; delete[] _originalFileData;
}
void VirtualTestFileWriteable::write(const void *buf, size_t count, off_t offset) {
extendFileSizeIfNecessary(count + offset);
memcpy(_fileData+offset, buf, count);
}
void VirtualTestFileWriteable::extendFileSizeIfNecessary(size_t size) {
if (size > _size) {
extendFileSize(size);
}
}
void VirtualTestFileWriteable::extendFileSize(size_t size) {
char *newfile = new char[size];
memcpy(newfile, _fileData, _size);
delete[] _fileData;
_fileData = newfile;
_size = size;
}
bool VirtualTestFileWriteable::sizeUnchanged() {
return _size == _originalSize;
}
bool VirtualTestFileWriteable::regionUnchanged(off_t offset, size_t count) {
return 0 == memcmp(_fileData+offset, _originalFileData+offset, count);
} }

View File

@ -6,28 +6,42 @@
class VirtualTestFile { class VirtualTestFile {
public: public:
VirtualTestFile(size_t size); VirtualTestFile(size_t size, long long int IV = 1);
virtual ~VirtualTestFile(); virtual ~VirtualTestFile();
int read(void *buf, size_t count, off_t offset); int read(void *buf, size_t count, off_t offset);
// Return true, iff the given data is equal to the data of the file at the given offset. // Return true, iff the given data is equal to the data of the file at the given offset.
bool fileContentEqual(char *content, size_t count, off_t offset); bool fileContentEqual(const char *content, size_t count, off_t offset);
const char *data();
size_t size();
protected: protected:
char *_fileData; char *_fileData;
size_t _size; size_t _size;
private: private:
void fillFileWithRandomData(); void fillFileWithRandomData(long long int IV);
}; };
class VirtualTestFileWriteable: public VirtualTestFile { class VirtualTestFileWriteable: public VirtualTestFile {
public: public:
VirtualTestFileWriteable(size_t size); VirtualTestFileWriteable(size_t size, long long int IV = 1);
virtual ~VirtualTestFileWriteable(); virtual ~VirtualTestFileWriteable();
void write(const void *buf, size_t count, off_t offset);
bool sizeUnchanged();
bool regionUnchanged(off_t offset, size_t count);
private: private:
char *originalFileData; void extendFileSizeIfNecessary(size_t size);
void extendFileSize(size_t size);
char *_originalFileData;
size_t _originalSize;
}; };
#endif #endif