From 15b10feeaf8dcba7c0e846349e1d6e32a196d6b2 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Thu, 11 Dec 2014 01:31:21 +0100 Subject: [PATCH] Create a FakeBlockStore and use it instead of InMemoryBlockStore in tests --- src/blockstore/implementations/CMakeLists.txt | 1 + .../inmemory/InMemoryBlockStore.cpp | 4 +- .../implementations/testfake/CMakeLists.txt | 3 + .../implementations/testfake/FakeBlock.cpp | 42 ++++++++++++++ .../implementations/testfake/FakeBlock.h | 37 +++++++++++++ .../testfake/FakeBlockStore.cpp | 52 ++++++++++++++++++ .../implementations/testfake/FakeBlockStore.h | 55 +++++++++++++++++++ .../testfake/TestFakeBlockStoreTest.cpp | 31 +++++++++++ src/test/testutils/DataBlockFixture.cpp | 6 ++ 9 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/blockstore/implementations/testfake/CMakeLists.txt create mode 100644 src/blockstore/implementations/testfake/FakeBlock.cpp create mode 100644 src/blockstore/implementations/testfake/FakeBlock.h create mode 100644 src/blockstore/implementations/testfake/FakeBlockStore.cpp create mode 100644 src/blockstore/implementations/testfake/FakeBlockStore.h create mode 100644 src/test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp diff --git a/src/blockstore/implementations/CMakeLists.txt b/src/blockstore/implementations/CMakeLists.txt index 22d8c52d..608fd1e6 100644 --- a/src/blockstore/implementations/CMakeLists.txt +++ b/src/blockstore/implementations/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(ondisk) add_subdirectory(inmemory) +add_subdirectory(testfake) \ No newline at end of file diff --git a/src/blockstore/implementations/inmemory/InMemoryBlockStore.cpp b/src/blockstore/implementations/inmemory/InMemoryBlockStore.cpp index df6ee78d..566a9894 100644 --- a/src/blockstore/implementations/inmemory/InMemoryBlockStore.cpp +++ b/src/blockstore/implementations/inmemory/InMemoryBlockStore.cpp @@ -20,12 +20,12 @@ unique_ptr 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(insert_result.first->second); } unique_ptr InMemoryBlockStore::load(const Key &key) { - //Return a copy of the stored InMemoryBlock + //Return a pointer to the stored InMemoryBlock try { return make_unique(_blocks.at(key.AsString())); } catch (const std::out_of_range &e) { diff --git a/src/blockstore/implementations/testfake/CMakeLists.txt b/src/blockstore/implementations/testfake/CMakeLists.txt new file mode 100644 index 00000000..e3dddf05 --- /dev/null +++ b/src/blockstore/implementations/testfake/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(blockstore_testfake FakeBlock.cpp FakeBlockStore.cpp) + +target_link_libraries(blockstore_testfake blockstore_interface blockstore_utils) diff --git a/src/blockstore/implementations/testfake/FakeBlock.cpp b/src/blockstore/implementations/testfake/FakeBlock.cpp new file mode 100644 index 00000000..58bfa7c4 --- /dev/null +++ b/src/blockstore/implementations/testfake/FakeBlock.cpp @@ -0,0 +1,42 @@ +#include "FakeBlock.h" +#include "FakeBlockStore.h" +#include + +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) + : _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); +} + +} +} diff --git a/src/blockstore/implementations/testfake/FakeBlock.h b/src/blockstore/implementations/testfake/FakeBlock.h new file mode 100644 index 00000000..79928cba --- /dev/null +++ b/src/blockstore/implementations/testfake/FakeBlock.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_ +#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_ + +#include +#include + +#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); + 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; + + DISALLOW_COPY_AND_ASSIGN(FakeBlock); +}; + +} +} + +#endif diff --git a/src/blockstore/implementations/testfake/FakeBlockStore.cpp b/src/blockstore/implementations/testfake/FakeBlockStore.cpp new file mode 100644 index 00000000..5f969af8 --- /dev/null +++ b/src/blockstore/implementations/testfake/FakeBlockStore.cpp @@ -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 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(size)); + std::memcpy(_used_dataregions_for_blocks.back()->data(), insert_result.first->second.data(), size); + return make_unique(this, key_string, _used_dataregions_for_blocks.back()); +} + +unique_ptr 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.size())); + std::memcpy(_used_dataregions_for_blocks.back()->data(), data.data(), data.size()); + return make_unique(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()); +} + +} +} diff --git a/src/blockstore/implementations/testfake/FakeBlockStore.h b/src/blockstore/implementations/testfake/FakeBlockStore.h new file mode 100644 index 00000000..51510ab6 --- /dev/null +++ b/src/blockstore/implementations/testfake/FakeBlockStore.h @@ -0,0 +1,55 @@ +#pragma once +#ifndef BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE_H_ +#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE_H_ + +#include +#include "blockstore/utils/Data.h" +#include "fspp/utils/macros.h" + +#include +#include + +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 create(const Key &key, size_t size) override; + std::unique_ptr load(const Key &key) override; + + void updateData(const std::string &key, const Data &data); + +private: + std::map _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> _used_dataregions_for_blocks; + + DISALLOW_COPY_AND_ASSIGN(FakeBlockStore); +}; + +} +} + +#endif diff --git a/src/test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp b/src/test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp new file mode 100644 index 00000000..ec1de72c --- /dev/null +++ b/src/test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#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 createBlockStore() override { + return make_unique(); + } +}; + +INSTANTIATE_TYPED_TEST_CASE_P(TestFake, BlockStoreTest, FakeBlockStoreTestFixture); + +class FakeBlockStoreWithRandomKeysTestFixture: public BlockStoreWithRandomKeysTestFixture { +public: + unique_ptr createBlockStore() override { + return make_unique(); + } +}; + +INSTANTIATE_TYPED_TEST_CASE_P(TestFake, BlockStoreWithRandomKeysTest, FakeBlockStoreWithRandomKeysTestFixture); diff --git a/src/test/testutils/DataBlockFixture.cpp b/src/test/testutils/DataBlockFixture.cpp index b4413121..ec50b91c 100644 --- a/src/test/testutils/DataBlockFixture.cpp +++ b/src/test/testutils/DataBlockFixture.cpp @@ -20,6 +20,12 @@ void DataBlockFixture::fillFileWithRandomData(long long int IV) { val += 1442695040888963407; reinterpret_cast(_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(_fileData)[i] = val; + } } const char *DataBlockFixture::data() const {