Create a FakeBlockStore and use it instead of InMemoryBlockStore in tests

This commit is contained in:
Sebastian Messmer 2014-12-11 01:31:21 +01:00
parent 1924c936a4
commit 15b10feeaf
9 changed files with 229 additions and 2 deletions

View File

@ -1,2 +1,3 @@
add_subdirectory(ondisk)
add_subdirectory(inmemory)
add_subdirectory(testfake)

View File

@ -20,12 +20,12 @@ unique_ptr<Block> InMemoryBlockStore::create(const Key &key, size_t size) {
return nullptr;
}
//Return a copy of the stored InMemoryBlock
//Return a pointer to the stored InMemoryBlock
return make_unique<InMemoryBlock>(insert_result.first->second);
}
unique_ptr<Block> InMemoryBlockStore::load(const Key &key) {
//Return a copy of the stored InMemoryBlock
//Return a pointer to the stored InMemoryBlock
try {
return make_unique<InMemoryBlock>(_blocks.at(key.AsString()));
} catch (const std::out_of_range &e) {

View File

@ -0,0 +1,3 @@
add_library(blockstore_testfake FakeBlock.cpp FakeBlockStore.cpp)
target_link_libraries(blockstore_testfake blockstore_interface blockstore_utils)

View File

@ -0,0 +1,42 @@
#include "FakeBlock.h"
#include "FakeBlockStore.h"
#include <cstring>
using std::unique_ptr;
using std::shared_ptr;
using std::istream;
using std::ostream;
using std::ifstream;
using std::ofstream;
using std::ios;
using std::string;
namespace blockstore {
namespace testfake {
FakeBlock::FakeBlock(FakeBlockStore *store, const string &key, shared_ptr<Data> data)
: _store(store), _key(key), _data(data) {
}
FakeBlock::~FakeBlock() {
flush();
}
void *FakeBlock::data() {
return _data->data();
}
const void *FakeBlock::data() const {
return _data->data();
}
size_t FakeBlock::size() const {
return _data->size();
}
void FakeBlock::flush() {
_store->updateData(_key, *_data);
}
}
}

View File

@ -0,0 +1,37 @@
#pragma once
#ifndef BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_
#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_
#include <blockstore/interface/Block.h>
#include <blockstore/utils/Data.h>
#include "fspp/utils/macros.h"
namespace blockstore {
namespace testfake {
class FakeBlockStore;
class FakeBlock: public Block {
public:
FakeBlock(FakeBlockStore *store, const std::string &key, std::shared_ptr<Data> data);
virtual ~FakeBlock();
void *data() override;
const void *data() const override;
void flush() override;
size_t size() const override;
private:
FakeBlockStore *_store;
const std::string _key;
std::shared_ptr<Data> _data;
DISALLOW_COPY_AND_ASSIGN(FakeBlock);
};
}
}
#endif

View File

@ -0,0 +1,52 @@
#include "FakeBlockStore.h"
#include "FakeBlock.h"
using std::unique_ptr;
using std::make_unique;
using std::make_shared;
using std::string;
using std::mutex;
using std::lock_guard;
namespace blockstore {
namespace testfake {
FakeBlockStore::FakeBlockStore()
: _blocks(), _used_dataregions_for_blocks() {}
unique_ptr<Block> FakeBlockStore::create(const Key &key, size_t size) {
string key_string = key.AsString();
auto insert_result = _blocks.emplace(key_string, size);
insert_result.first->second.FillWithZeroes();
if (!insert_result.second) {
return nullptr;
}
//Return a copy of the stored data
_used_dataregions_for_blocks.push_back(make_shared<Data>(size));
std::memcpy(_used_dataregions_for_blocks.back()->data(), insert_result.first->second.data(), size);
return make_unique<FakeBlock>(this, key_string, _used_dataregions_for_blocks.back());
}
unique_ptr<Block> FakeBlockStore::load(const Key &key) {
//Return a copy of the stored data
string key_string = key.AsString();
try {
const Data &data = _blocks.at(key_string);
_used_dataregions_for_blocks.push_back(make_shared<Data>(data.size()));
std::memcpy(_used_dataregions_for_blocks.back()->data(), data.data(), data.size());
return make_unique<FakeBlock>(this, key_string, _used_dataregions_for_blocks.back());
} catch (const std::out_of_range &e) {
return nullptr;
}
}
void FakeBlockStore::updateData(const std::string &key, const Data &data) {
Data &stored_data = _blocks.at(key);
assert(data.size() == stored_data.size());
std::memcpy(stored_data.data(), data.data(), data.size());
}
}
}

View File

@ -0,0 +1,55 @@
#pragma once
#ifndef BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE_H_
#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE_H_
#include <blockstore/interface/helpers/BlockStoreWithRandomKeys.h>
#include "blockstore/utils/Data.h"
#include "fspp/utils/macros.h"
#include <mutex>
#include <map>
namespace blockstore {
namespace testfake {
class FakeBlock;
/**
* This blockstore is meant to be used for unit tests when the module under test needs a blockstore to work with.
* It basically is the same as the InMemoryBlockStore, but much less forgiving for programming mistakes.
*
* InMemoryBlockStore for example simply ignores flushing and gives you access to the same data region each time
* you request a block. This is very performant, but also forgiving to mistakes. Say you write over the boundaries
* of a block, then you wouldn't notice, since the next time you access the block, the overflow data is (probably)
* still there. Or say an application is relying on flushing the block store in the right moment. Since flushing
* is a no-op in InMemoryBlockStore, you wouldn't notice either.
*
* So this FakeBlockStore has a background copy of each block. When you request a block, you will get a copy of
* the data (instead of a direct pointer as InMemoryBlockStore does) and flushing will copy the data back to the
* background. This way, tests are more likely to fail if they use the blockstore wrongly.
*/
class FakeBlockStore: public BlockStoreWithRandomKeys {
public:
FakeBlockStore();
std::unique_ptr<Block> create(const Key &key, size_t size) override;
std::unique_ptr<Block> load(const Key &key) override;
void updateData(const std::string &key, const Data &data);
private:
std::map<std::string, Data> _blocks;
//This vector keeps a handle of the data regions for all created FakeBlock objects.
//This way, it is ensured that no two created FakeBlock objects will work on the
//same data region. Without this, it could happen that a test case creates a FakeBlock,
//destructs it, creates another one, and the new one gets the same memory region.
//We want to avoid this for the reasons mentioned above (overflow data).
std::vector<std::shared_ptr<Data>> _used_dataregions_for_blocks;
DISALLOW_COPY_AND_ASSIGN(FakeBlockStore);
};
}
}
#endif

View File

@ -0,0 +1,31 @@
#include <blockstore/implementations/testfake/FakeBlock.h>
#include <blockstore/implementations/testfake/FakeBlockStore.h>
#include <test/blockstore/testutils/BlockStoreWithRandomKeysTest.h>
#include <test/blockstore/testutils/BlockStoreTest.h>
#include "gtest/gtest.h"
using blockstore::BlockStore;
using blockstore::BlockStoreWithRandomKeys;
using blockstore::testfake::FakeBlockStore;
using std::unique_ptr;
using std::make_unique;
class FakeBlockStoreTestFixture: public BlockStoreTestFixture {
public:
unique_ptr<BlockStore> createBlockStore() override {
return make_unique<FakeBlockStore>();
}
};
INSTANTIATE_TYPED_TEST_CASE_P(TestFake, BlockStoreTest, FakeBlockStoreTestFixture);
class FakeBlockStoreWithRandomKeysTestFixture: public BlockStoreWithRandomKeysTestFixture {
public:
unique_ptr<BlockStoreWithRandomKeys> createBlockStore() override {
return make_unique<FakeBlockStore>();
}
};
INSTANTIATE_TYPED_TEST_CASE_P(TestFake, BlockStoreWithRandomKeysTest, FakeBlockStoreWithRandomKeysTestFixture);

View File

@ -20,6 +20,12 @@ void DataBlockFixture::fillFileWithRandomData(long long int IV) {
val += 1442695040888963407;
reinterpret_cast<long long int*>(_fileData)[i] = val;
}
//Fill remaining bytes
for(size_t i=(_size/sizeof(long long int))*sizeof(long long int); i<_size; ++i) {
val *= 6364136223846793005L;
val += 1442695040888963407;
reinterpret_cast<char*>(_fileData)[i] = val;
}
}
const char *DataBlockFixture::data() const {