Remove tests
This commit is contained in:
parent
31c831f984
commit
3df8023b21
@ -1,16 +0,0 @@
|
||||
if (BUILD_TESTING)
|
||||
include_directories(../src)
|
||||
|
||||
add_subdirectory(my-gtest-main)
|
||||
add_subdirectory(gitversion)
|
||||
add_subdirectory(cpp-utils)
|
||||
if (NOT MSVC)
|
||||
# TODO Make this build on Windows
|
||||
add_subdirectory(fspp)
|
||||
endif()
|
||||
add_subdirectory(parallelaccessstore)
|
||||
add_subdirectory(blockstore)
|
||||
add_subdirectory(blobstore)
|
||||
add_subdirectory(cryfs)
|
||||
add_subdirectory(cryfs-cli)
|
||||
endif(BUILD_TESTING)
|
@ -1,34 +0,0 @@
|
||||
project (blobstore-test)
|
||||
|
||||
set(SOURCES
|
||||
implementations/onblocks/utils/MaxZeroSubtractionTest.cpp
|
||||
implementations/onblocks/utils/CeilDivisionTest.cpp
|
||||
implementations/onblocks/utils/IntPowTest.cpp
|
||||
implementations/onblocks/utils/CeilLogTest.cpp
|
||||
implementations/onblocks/testutils/BlobStoreTest.cpp
|
||||
implementations/onblocks/BlobStoreTest.cpp
|
||||
implementations/onblocks/datanodestore/DataLeafNodeTest.cpp
|
||||
implementations/onblocks/datanodestore/DataInnerNodeTest.cpp
|
||||
implementations/onblocks/datanodestore/DataNodeViewTest.cpp
|
||||
implementations/onblocks/datanodestore/DataNodeStoreTest.cpp
|
||||
implementations/onblocks/datatreestore/testutils/DataTreeTest.cpp
|
||||
implementations/onblocks/datatreestore/impl/GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest.cpp
|
||||
implementations/onblocks/datatreestore/impl/GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest.cpp
|
||||
implementations/onblocks/datatreestore/DataTreeTest_Performance.cpp
|
||||
implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp
|
||||
implementations/onblocks/datatreestore/DataTreeTest_NumStoredBytes.cpp
|
||||
implementations/onblocks/datatreestore/DataTreeTest_ResizeNumBytes.cpp
|
||||
implementations/onblocks/datatreestore/DataTreeStoreTest.cpp
|
||||
implementations/onblocks/datatreestore/LeafTraverserTest.cpp
|
||||
implementations/onblocks/BlobSizeTest.cpp
|
||||
implementations/onblocks/BlobReadWriteTest.cpp
|
||||
implementations/onblocks/BigBlobsTest.cpp
|
||||
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blobstore)
|
||||
add_test(${PROJECT_NAME} ${PROJECT_NAME})
|
||||
|
||||
target_enable_style_warnings(${PROJECT_NAME})
|
||||
target_activate_cpp14(${PROJECT_NAME})
|
@ -1,136 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <blockstore/implementations/compressing/CompressingBlockStore.h>
|
||||
#include <blockstore/implementations/compressing/compressors/RunLengthEncoding.h>
|
||||
#include <blockstore/implementations/inmemory/InMemoryBlockStore2.h>
|
||||
#include <blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <cpp-utils/data/Data.h>
|
||||
#include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h"
|
||||
#include "blobstore/implementations/onblocks/BlobOnBlocks.h"
|
||||
|
||||
using namespace blobstore;
|
||||
using namespace blobstore::onblocks;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::Data;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::compressing::CompressingBlockStore;
|
||||
using blockstore::compressing::RunLengthEncoding;
|
||||
|
||||
// Test cases, ensuring that big blobs (>4G) work (i.e. testing that we don't use any 32bit variables for blob size, etc.)
|
||||
class BigBlobsTest : public ::testing::Test {
|
||||
public:
|
||||
static constexpr size_t BLOCKSIZE = 32 * 1024;
|
||||
static constexpr uint64_t SMALL_BLOB_SIZE = UINT64_C(1024)*1024*1024*3.95; // 3.95 GB (<4GB)
|
||||
static constexpr uint64_t LARGE_BLOB_SIZE = UINT64_C(1024)*1024*1024*4.05; // 4.05 GB (>4GB)
|
||||
|
||||
static constexpr uint64_t max_uint_32 = std::numeric_limits<uint32_t>::max();
|
||||
static_assert(SMALL_BLOB_SIZE < max_uint_32, "LARGE_BLOB_SIZE should need 64bit or the test case is mute");
|
||||
static_assert(LARGE_BLOB_SIZE > max_uint_32, "LARGE_BLOB_SIZE should need 64bit or the test case is mute");
|
||||
|
||||
unique_ref<BlobStore> blobStore = make_unique_ref<BlobStoreOnBlocks>(make_unique_ref<CompressingBlockStore<RunLengthEncoding>>(make_unique_ref<LowToHighLevelBlockStore>(make_unique_ref<InMemoryBlockStore2>())), BLOCKSIZE);
|
||||
unique_ref<Blob> blob = blobStore->create();
|
||||
};
|
||||
|
||||
constexpr size_t BigBlobsTest::BLOCKSIZE;
|
||||
constexpr uint64_t BigBlobsTest::SMALL_BLOB_SIZE;
|
||||
constexpr uint64_t BigBlobsTest::LARGE_BLOB_SIZE;
|
||||
|
||||
TEST_F(BigBlobsTest, Resize) {
|
||||
//These operations are in one test case and not in many small ones, because it takes quite long to create a >4GB blob.
|
||||
|
||||
//Resize to >4GB
|
||||
blob->resize(LARGE_BLOB_SIZE);
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE, blob->size());
|
||||
|
||||
//Grow while >4GB
|
||||
blob->resize(LARGE_BLOB_SIZE + 1024);
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE + 1024, blob->size());
|
||||
|
||||
//Shrink while >4GB
|
||||
blob->resize(LARGE_BLOB_SIZE);
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE, blob->size());
|
||||
|
||||
//Shrink to <4GB
|
||||
blob->resize(SMALL_BLOB_SIZE);
|
||||
EXPECT_EQ(SMALL_BLOB_SIZE, blob->size());
|
||||
|
||||
//Grow to >4GB
|
||||
blob->resize(LARGE_BLOB_SIZE);
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE, blob->size());
|
||||
|
||||
//Flush >4GB blob
|
||||
blob->flush();
|
||||
|
||||
//Destruct >4GB blob
|
||||
auto blockId = blob->blockId();
|
||||
cpputils::destruct(std::move(blob));
|
||||
|
||||
//Load >4GB blob
|
||||
blob = blobStore->load(blockId).value();
|
||||
|
||||
//Remove >4GB blob
|
||||
blobStore->remove(std::move(blob));
|
||||
}
|
||||
|
||||
TEST_F(BigBlobsTest, GrowByWriting_Crossing4GBBorder) {
|
||||
Data fixture = DataFixture::generate(2*(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE));
|
||||
blob->write(fixture.data(), SMALL_BLOB_SIZE, fixture.size());
|
||||
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE), blob->size());
|
||||
|
||||
Data loaded(fixture.size());
|
||||
blob->read(loaded.data(), SMALL_BLOB_SIZE, loaded.size());
|
||||
EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size()));
|
||||
}
|
||||
|
||||
TEST_F(BigBlobsTest, GrowByWriting_Outside4GBBorder_StartingSizeZero) {
|
||||
Data fixture = DataFixture::generate(1024);
|
||||
blob->write(fixture.data(), LARGE_BLOB_SIZE, fixture.size());
|
||||
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE+1024, blob->size());
|
||||
|
||||
Data loaded(fixture.size());
|
||||
blob->read(loaded.data(), LARGE_BLOB_SIZE, loaded.size());
|
||||
EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size()));
|
||||
}
|
||||
|
||||
TEST_F(BigBlobsTest, GrowByWriting_Outside4GBBorder_StartingSizeOutside4GBBorder) {
|
||||
blob->resize(LARGE_BLOB_SIZE);
|
||||
Data fixture = DataFixture::generate(1024);
|
||||
blob->write(fixture.data(), LARGE_BLOB_SIZE+1024, fixture.size());
|
||||
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE+2048, blob->size());
|
||||
|
||||
Data loaded(fixture.size());
|
||||
blob->read(loaded.data(), LARGE_BLOB_SIZE+1024, loaded.size());
|
||||
EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size()));
|
||||
}
|
||||
|
||||
TEST_F(BigBlobsTest, ReadWriteAfterGrown_Crossing4GBBorder) {
|
||||
blob->resize(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)+1024);
|
||||
Data fixture = DataFixture::generate(2*(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE));
|
||||
blob->write(fixture.data(), SMALL_BLOB_SIZE, fixture.size());
|
||||
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)+1024, blob->size());
|
||||
|
||||
Data loaded(fixture.size());
|
||||
blob->read(loaded.data(), SMALL_BLOB_SIZE, loaded.size());
|
||||
EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size()));
|
||||
}
|
||||
|
||||
TEST_F(BigBlobsTest, ReadWriteAfterGrown_Outside4GBBorder) {
|
||||
blob->resize(LARGE_BLOB_SIZE+2048);
|
||||
Data fixture = DataFixture::generate(1024);
|
||||
blob->write(fixture.data(), LARGE_BLOB_SIZE, fixture.size());
|
||||
|
||||
EXPECT_EQ(LARGE_BLOB_SIZE+2048, blob->size());
|
||||
|
||||
Data loaded(fixture.size());
|
||||
blob->read(loaded.data(), LARGE_BLOB_SIZE, loaded.size());
|
||||
EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size()));
|
||||
}
|
||||
|
||||
//TODO Test Blob::readAll (only on 64bit systems)
|
@ -1,256 +0,0 @@
|
||||
#include "testutils/BlobStoreTest.h"
|
||||
#include <cpp-utils/data/Data.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeView.h"
|
||||
|
||||
using cpputils::unique_ref;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
|
||||
using namespace blobstore;
|
||||
using blobstore::onblocks::datanodestore::DataNodeLayout;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
|
||||
namespace {
|
||||
|
||||
class BlobReadWriteTest: public BlobStoreTest {
|
||||
public:
|
||||
static constexpr uint32_t LARGE_SIZE = 10 * 1024 * 1024;
|
||||
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||
|
||||
BlobReadWriteTest()
|
||||
:randomData(DataFixture::generate(LARGE_SIZE)),
|
||||
blob(blobStore->create()) {
|
||||
}
|
||||
|
||||
Data readBlob(const Blob &blob) {
|
||||
Data data(blob.size());
|
||||
blob.read(data.data(), 0, data.size());
|
||||
return data;
|
||||
}
|
||||
|
||||
template<class DataClass>
|
||||
void EXPECT_DATA_READS_AS(const DataClass &expected, const Blob &actual, uint64_t offset, uint64_t size) {
|
||||
Data read(size);
|
||||
actual.read(read.data(), offset, size);
|
||||
EXPECT_EQ(0, std::memcmp(expected.data(), read.data(), size));
|
||||
}
|
||||
|
||||
Data randomData;
|
||||
unique_ref<Blob> blob;
|
||||
};
|
||||
constexpr uint32_t BlobReadWriteTest::LARGE_SIZE;
|
||||
constexpr DataNodeLayout BlobReadWriteTest::LAYOUT;
|
||||
|
||||
TEST_F(BlobReadWriteTest, WritingImmediatelyFlushes_SmallSize) {
|
||||
blob->resize(5);
|
||||
blob->write(randomData.data(), 0, 5);
|
||||
auto loaded = loadBlob(blob->blockId());
|
||||
EXPECT_DATA_READS_AS(randomData, *loaded, 0, 5);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, WritingImmediatelyFlushes_LargeSize) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->write(randomData.data(), 0, LARGE_SIZE);
|
||||
auto loaded = loadBlob(blob->blockId());
|
||||
EXPECT_DATA_READS_AS(randomData, *loaded, 0, LARGE_SIZE);
|
||||
}
|
||||
|
||||
// Regression test for a strange bug we had
|
||||
TEST_F(BlobReadWriteTest, WritingCloseTo16ByteLimitDoesntDestroySize) {
|
||||
blob->resize(1);
|
||||
blob->write(randomData.data(), 32776, 4);
|
||||
EXPECT_EQ(32780u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenTryReadInFirstLeaf_thenFails) {
|
||||
Data data(5);
|
||||
size_t read = blob->tryRead(data.data(), 3, 5);
|
||||
EXPECT_EQ(0, read);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenTryReadInLaterLeaf_thenFails) {
|
||||
Data data(5);
|
||||
size_t read = blob->tryRead(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5);
|
||||
EXPECT_EQ(0, read);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadInFirstLeaf_thenFails) {
|
||||
Data data(5);
|
||||
EXPECT_ANY_THROW(
|
||||
blob->read(data.data(), 3, 5)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadInLaterLeaf_thenFails) {
|
||||
Data data(5);
|
||||
EXPECT_ANY_THROW(
|
||||
blob->read(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadAll_thenReturnsZeroSizedData) {
|
||||
Data data = blob->readAll();
|
||||
EXPECT_EQ(0, data.size());
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenWrite_thenGrows) {
|
||||
Data data(5);
|
||||
blob->write(data.data(), 4, 5);
|
||||
EXPECT_EQ(9, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenEmptyBlob_whenWriteZeroBytes_thenDoesntGrow) {
|
||||
Data data(5);
|
||||
blob->write(data.data(), 4, 0);
|
||||
EXPECT_EQ(0, blob->size());;
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenTryReadInFirstLeaf_thenFails) {
|
||||
Data data(5);
|
||||
size_t read = blob->tryRead(data.data(), 3, 5);
|
||||
EXPECT_EQ(0, read);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenTryReadInLaterLeaf_thenFails) {
|
||||
Data data(5);
|
||||
size_t read = blob->tryRead(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5);
|
||||
EXPECT_EQ(0, read);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadInFirstLeaf_thenFails) {
|
||||
Data data(5);
|
||||
EXPECT_ANY_THROW(
|
||||
blob->read(data.data(), 3, 5)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadInLaterLeaf_thenFails) {
|
||||
Data data(5);
|
||||
EXPECT_ANY_THROW(
|
||||
blob->read(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadAll_thenReturnsZeroSizedData) {
|
||||
Data data = blob->readAll();
|
||||
EXPECT_EQ(0, data.size());
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenWrite_thenGrows) {
|
||||
Data data(5);
|
||||
blob->write(data.data(), 4, 5);
|
||||
EXPECT_EQ(9, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenWriteZeroBytes_thenDoesntGrow) {
|
||||
Data data(5);
|
||||
blob->write(data.data(), 4, 0);
|
||||
EXPECT_EQ(0, blob->size());
|
||||
}
|
||||
|
||||
struct DataRange {
|
||||
uint64_t blobsize;
|
||||
uint64_t offset;
|
||||
uint64_t count;
|
||||
};
|
||||
class BlobReadWriteDataTest: public BlobReadWriteTest, public WithParamInterface<DataRange> {
|
||||
public:
|
||||
Data foregroundData;
|
||||
Data backgroundData;
|
||||
|
||||
BlobReadWriteDataTest()
|
||||
: foregroundData(DataFixture::generate(GetParam().count, 0)),
|
||||
backgroundData(DataFixture::generate(GetParam().blobsize, 1)) {
|
||||
}
|
||||
|
||||
template<class DataClass>
|
||||
void EXPECT_DATA_READS_AS_OUTSIDE_OF(const DataClass &expected, const Blob &blob, uint64_t start, uint64_t count) {
|
||||
Data begin(start);
|
||||
Data end(GetParam().blobsize - count - start);
|
||||
|
||||
std::memcpy(begin.data(), expected.data(), start);
|
||||
std::memcpy(end.data(), expected.dataOffset(start+count), end.size());
|
||||
|
||||
EXPECT_DATA_READS_AS(begin, blob, 0, start);
|
||||
EXPECT_DATA_READS_AS(end, blob, start + count, end.size());
|
||||
}
|
||||
|
||||
void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const Blob &blob, uint64_t start, uint64_t count) {
|
||||
Data ZEROES(GetParam().blobsize);
|
||||
ZEROES.FillWithZeroes();
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, blob, start, count);
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(BlobReadWriteDataTest, BlobReadWriteDataTest, Values(
|
||||
//Blob with only one leaf
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()}, // full size leaf, access beginning to end
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // full size leaf, access middle to middle
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access beginning to middle
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access middle to end
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // non-full size leaf, access beginning to end
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-300}, // non-full size leaf, access middle to middle
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access beginning to middle
|
||||
DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access middle to end
|
||||
//Larger blob
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE}, // access beginning to end
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, 100, BlobReadWriteDataTest::LARGE_SIZE-200}, // access middle first leaf to middle last leaf
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE-100}, // access beginning to middle last leaf
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, 100, BlobReadWriteDataTest::LARGE_SIZE-100}, // access middle first leaf to end
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*1/3}, // access middle to middle
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE*2/3}, // access beginning to middle
|
||||
DataRange{BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*2/3} // access middle to end
|
||||
));
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, WritingDoesntChangeSize) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
EXPECT_EQ(GetParam().blobsize, blob->size());
|
||||
}
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, WriteAndReadImmediately) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *blob, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*blob, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, WriteAndReadAfterLoading) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
auto loaded = loadBlob(blob->blockId());
|
||||
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *loaded, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, OverwriteAndRead) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->backgroundData.data(), 0, GetParam().blobsize);
|
||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *blob, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(this->backgroundData, *blob, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, WriteWholeAndReadPart) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->backgroundData.data(), 0, GetParam().blobsize);
|
||||
Data read(GetParam().count);
|
||||
blob->read(read.data(), GetParam().offset, GetParam().count);
|
||||
EXPECT_EQ(0, std::memcmp(read.data(), this->backgroundData.dataOffset(GetParam().offset), GetParam().count));
|
||||
}
|
||||
|
||||
TEST_P(BlobReadWriteDataTest, WritePartAndReadWhole) {
|
||||
blob->resize(GetParam().blobsize);
|
||||
blob->write(this->backgroundData.data(), 0, GetParam().blobsize);
|
||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
Data read = readBlob(*blob);
|
||||
EXPECT_EQ(0, std::memcmp(read.data(), this->backgroundData.data(), GetParam().offset));
|
||||
EXPECT_EQ(0, std::memcmp(read.dataOffset(GetParam().offset), this->foregroundData.data(), GetParam().count));
|
||||
EXPECT_EQ(0, std::memcmp(read.dataOffset(GetParam().offset+GetParam().count), this->backgroundData.dataOffset(GetParam().offset+GetParam().count), GetParam().blobsize-GetParam().count-GetParam().offset));
|
||||
}
|
||||
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
#include "testutils/BlobStoreTest.h"
|
||||
#include <cpp-utils/data/Data.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using namespace blobstore;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class BlobSizeTest: public BlobStoreTest {
|
||||
public:
|
||||
BlobSizeTest(): blob(blobStore->create()) {}
|
||||
|
||||
static constexpr uint32_t MEDIUM_SIZE = 5 * 1024 * 1024;
|
||||
static constexpr uint32_t LARGE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
unique_ref<Blob> blob;
|
||||
};
|
||||
constexpr uint32_t BlobSizeTest::MEDIUM_SIZE;
|
||||
constexpr uint32_t BlobSizeTest::LARGE_SIZE;
|
||||
|
||||
TEST_F(BlobSizeTest, CreatedBlobIsEmpty) {
|
||||
EXPECT_EQ(0u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, Growing_1Byte) {
|
||||
blob->resize(1);
|
||||
EXPECT_EQ(1u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, Growing_Large) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
EXPECT_EQ(LARGE_SIZE, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, Shrinking_Empty) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->resize(0);
|
||||
EXPECT_EQ(0u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, Shrinking_1Byte) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->resize(1);
|
||||
EXPECT_EQ(1u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, ResizingToItself_Empty) {
|
||||
blob->resize(0);
|
||||
EXPECT_EQ(0u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, ResizingToItself_1Byte) {
|
||||
blob->resize(1);
|
||||
blob->resize(1);
|
||||
EXPECT_EQ(1u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, ResizingToItself_Large) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->resize(LARGE_SIZE);
|
||||
EXPECT_EQ(LARGE_SIZE, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, EmptyBlobStaysEmptyWhenLoading) {
|
||||
BlockId blockId = blob->blockId();
|
||||
reset(std::move(blob));
|
||||
auto loaded = loadBlob(blockId);
|
||||
EXPECT_EQ(0u, loaded->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, BlobSizeStaysIntactWhenLoading) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
BlockId blockId = blob->blockId();
|
||||
reset(std::move(blob));
|
||||
auto loaded = loadBlob(blockId);
|
||||
EXPECT_EQ(LARGE_SIZE, loaded->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, WritingAtEndOfBlobGrowsBlob_Empty) {
|
||||
int value = 0;
|
||||
blob->write(&value, 0, 4);
|
||||
EXPECT_EQ(4u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_Empty) {
|
||||
int value = 0;
|
||||
blob->write(&value, 2, 4);
|
||||
EXPECT_EQ(6u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, WritingOverEndOfBlobGrowsBlob_NonEmpty) {
|
||||
blob->resize(1);
|
||||
int value = 0;
|
||||
blob->write(&value, 0, 4);
|
||||
EXPECT_EQ(4u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, WritingAtEndOfBlobGrowsBlob_NonEmpty) {
|
||||
blob->resize(1);
|
||||
int value = 0;
|
||||
blob->write(&value, 1, 4);
|
||||
EXPECT_EQ(5u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_NonEmpty) {
|
||||
blob->resize(1);
|
||||
int value = 0;
|
||||
blob->write(&value, 2, 4);
|
||||
EXPECT_EQ(6u, blob->size());
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeTest, ChangingSizeImmediatelyFlushes) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
auto loaded = loadBlob(blob->blockId());
|
||||
EXPECT_EQ(LARGE_SIZE, loaded->size());
|
||||
}
|
||||
|
||||
class BlobSizeDataTest: public BlobSizeTest {
|
||||
public:
|
||||
BlobSizeDataTest()
|
||||
:ZEROES(LARGE_SIZE),
|
||||
randomData(DataFixture::generate(LARGE_SIZE)) {
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
|
||||
Data readBlob(const Blob &blob) {
|
||||
Data data(blob.size());
|
||||
blob.read(data.data(), 0, data.size());
|
||||
return data;
|
||||
}
|
||||
|
||||
Data ZEROES;
|
||||
Data randomData;
|
||||
};
|
||||
|
||||
TEST_F(BlobSizeDataTest, BlobIsZeroedOutAfterGrowing) {
|
||||
//uint32_t LARGE_SIZE = 2*1024*1024;
|
||||
blob->resize(LARGE_SIZE);
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), ZEROES.data(), LARGE_SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeDataTest, BlobIsZeroedOutAfterGrowingAndLoading) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
auto loaded = loadBlob(blob->blockId());
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*loaded).data(), ZEROES.data(), LARGE_SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeDataTest, DataStaysIntactWhenGrowing) {
|
||||
blob->resize(MEDIUM_SIZE);
|
||||
blob->write(randomData.data(), 0, MEDIUM_SIZE);
|
||||
blob->resize(LARGE_SIZE);
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE));
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).dataOffset(MEDIUM_SIZE), ZEROES.data(), LARGE_SIZE-MEDIUM_SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeDataTest, DataStaysIntactWhenShrinking) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->write(randomData.data(), 0, LARGE_SIZE);
|
||||
blob->resize(MEDIUM_SIZE);
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlobSizeDataTest, ChangedAreaIsZeroedOutWhenShrinkingAndRegrowing) {
|
||||
blob->resize(LARGE_SIZE);
|
||||
blob->write(randomData.data(), 0, LARGE_SIZE);
|
||||
blob->resize(MEDIUM_SIZE);
|
||||
blob->resize(LARGE_SIZE);
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE));
|
||||
EXPECT_EQ(0, std::memcmp(readBlob(*blob).dataOffset(MEDIUM_SIZE), ZEROES.data(), LARGE_SIZE-MEDIUM_SIZE));
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#include "testutils/BlobStoreTest.h"
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using blockstore::BlockId;
|
||||
using boost::none;
|
||||
|
||||
TEST_F(BlobStoreTest, LoadNonexistingKeyOnEmptyBlobstore) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(none, blobStore->load(blockId));
|
||||
}
|
||||
|
||||
TEST_F(BlobStoreTest, LoadNonexistingKeyOnNonEmptyBlobstore) {
|
||||
blobStore->create();
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(none, blobStore->load(blockId));
|
||||
}
|
||||
|
||||
TEST_F(BlobStoreTest, TwoCreatedBlobsHaveDifferentKeys) {
|
||||
auto blob1 = blobStore->create();
|
||||
auto blob2 = blobStore->create();
|
||||
EXPECT_NE(blob1->blockId(), blob2->blockId());
|
||||
}
|
||||
|
||||
TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteDirectly) {
|
||||
auto blob = blobStore->create();
|
||||
BlockId blockId = blob->blockId();
|
||||
blobStore->remove(std::move(blob));
|
||||
EXPECT_FALSE(static_cast<bool>(blobStore->load(blockId)));
|
||||
}
|
||||
|
||||
TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteByKey) {
|
||||
auto blockId = blobStore->create()->blockId();
|
||||
blobStore->remove(blockId);
|
||||
EXPECT_FALSE(static_cast<bool>(blobStore->load(blockId)));
|
||||
}
|
||||
|
||||
TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteAfterLoading) {
|
||||
auto blob = blobStore->create();
|
||||
BlockId blockId = blob->blockId();
|
||||
reset(std::move(blob));
|
||||
blobStore->remove(loadBlob(blockId));
|
||||
EXPECT_FALSE(static_cast<bool>(blobStore->load(blockId)));
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h"
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <blockstore/implementations/testfake/FakeBlock.h>
|
||||
|
||||
#include <memory>
|
||||
#include <cpp-utils/pointer/cast.h>
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::dynamic_pointer_move;
|
||||
|
||||
using blockstore::BlockId;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using blockstore::BlockStore;
|
||||
using cpputils::Data;
|
||||
using namespace blobstore;
|
||||
using namespace blobstore::onblocks;
|
||||
using namespace blobstore::onblocks::datanodestore;
|
||||
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using std::vector;
|
||||
|
||||
class DataInnerNodeTest: public Test {
|
||||
public:
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 1024;
|
||||
|
||||
DataInnerNodeTest() :
|
||||
_blockStore(make_unique_ref<FakeBlockStore>()),
|
||||
blockStore(_blockStore.get()),
|
||||
nodeStore(make_unique_ref<DataNodeStore>(std::move(_blockStore), BLOCKSIZE_BYTES)),
|
||||
ZEROES(nodeStore->layout().maxBytesPerLeaf()),
|
||||
leaf(nodeStore->createNewLeafNode(Data(0))),
|
||||
node(nodeStore->createNewInnerNode(1, {leaf->blockId()})) {
|
||||
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> LoadInnerNode(const BlockId &blockId) {
|
||||
auto node = nodeStore->load(blockId).value();
|
||||
return dynamic_pointer_move<DataInnerNode>(node).value();
|
||||
}
|
||||
|
||||
BlockId CreateNewInnerNodeReturnKey(const DataNode &firstChild) {
|
||||
return nodeStore->createNewInnerNode(firstChild.depth()+1, {firstChild.blockId()})->blockId();
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CreateNewInnerNode() {
|
||||
auto new_leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
return nodeStore->createNewInnerNode(1, {new_leaf->blockId()});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CreateAndLoadNewInnerNode(const DataNode &firstChild) {
|
||||
auto blockId = CreateNewInnerNodeReturnKey(firstChild);
|
||||
return LoadInnerNode(blockId);
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CreateNewInnerNode(uint8_t depth, const vector<blockstore::BlockId> &children) {
|
||||
return nodeStore->createNewInnerNode(depth, children);
|
||||
}
|
||||
|
||||
BlockId CreateNewInnerNodeReturnKey(uint8_t depth, const vector<blockstore::BlockId> &children) {
|
||||
return CreateNewInnerNode(depth, children)->blockId();
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CreateAndLoadNewInnerNode(uint8_t depth, const vector<blockstore::BlockId> &children) {
|
||||
auto blockId = CreateNewInnerNodeReturnKey(depth, children);
|
||||
return LoadInnerNode(blockId);
|
||||
}
|
||||
|
||||
BlockId AddALeafTo(DataInnerNode *node) {
|
||||
auto leaf2 = nodeStore->createNewLeafNode(Data(0));
|
||||
node->addChild(*leaf2);
|
||||
return leaf2->blockId();
|
||||
}
|
||||
|
||||
BlockId CreateNodeWithDataConvertItToInnerNodeAndReturnKey() {
|
||||
auto node = CreateNewInnerNode();
|
||||
AddALeafTo(node.get());
|
||||
AddALeafTo(node.get());
|
||||
auto child = nodeStore->createNewLeafNode(Data(0));
|
||||
unique_ref<DataInnerNode> converted = DataNode::convertToNewInnerNode(std::move(node), nodeStore->layout(), *child);
|
||||
return converted->blockId();
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CopyInnerNode(const DataInnerNode &node) {
|
||||
auto copied = nodeStore->createNewNodeAsCopyFrom(node);
|
||||
return dynamic_pointer_move<DataInnerNode>(copied).value();
|
||||
}
|
||||
|
||||
BlockId InitializeInnerNodeAddLeafReturnKey() {
|
||||
auto node = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()});
|
||||
AddALeafTo(node.get());
|
||||
return node->blockId();
|
||||
}
|
||||
|
||||
unique_ref<BlockStore> _blockStore;
|
||||
BlockStore *blockStore;
|
||||
unique_ref<DataNodeStore> nodeStore;
|
||||
Data ZEROES;
|
||||
unique_ref<DataLeafNode> leaf;
|
||||
unique_ref<DataInnerNode> node;
|
||||
|
||||
private:
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DataInnerNodeTest);
|
||||
};
|
||||
|
||||
constexpr uint32_t DataInnerNodeTest::BLOCKSIZE_BYTES;
|
||||
|
||||
TEST_F(DataInnerNodeTest, CorrectKeyReturnedAfterLoading) {
|
||||
BlockId blockId = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()})->blockId();
|
||||
|
||||
auto loaded = nodeStore->load(blockId).value();
|
||||
EXPECT_EQ(blockId, loaded->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, InitializesCorrectly) {
|
||||
auto node = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()});
|
||||
|
||||
EXPECT_EQ(1u, node->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, ReinitializesCorrectly) {
|
||||
auto blockId = DataLeafNode::CreateNewNode(blockStore, nodeStore->layout(), Data(0))->blockId();
|
||||
auto node = DataInnerNode::InitializeNewNode(blockStore->load(blockId).value(), nodeStore->layout(), 1, {leaf->blockId()});
|
||||
|
||||
EXPECT_EQ(1u, node->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, IsCorrectlyInitializedAfterLoading) {
|
||||
auto loaded = CreateAndLoadNewInnerNode(*leaf);
|
||||
|
||||
EXPECT_EQ(1u, loaded->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), loaded->readChild(0).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, AddingASecondLeaf) {
|
||||
BlockId leaf2_blockId = AddALeafTo(node.get());
|
||||
|
||||
EXPECT_EQ(2u, node->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId());
|
||||
EXPECT_EQ(leaf2_blockId, node->readChild(1).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, AddingASecondLeafAndReload) {
|
||||
auto leaf2 = nodeStore->createNewLeafNode(Data(0));
|
||||
auto loaded = CreateAndLoadNewInnerNode(1, {leaf->blockId(), leaf2->blockId()});
|
||||
|
||||
EXPECT_EQ(2u, loaded->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), loaded->readChild(0).blockId());
|
||||
EXPECT_EQ(leaf2->blockId(), loaded->readChild(1).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, BuildingAThreeLevelTree) {
|
||||
auto node2 = CreateNewInnerNode();
|
||||
auto parent = CreateNewInnerNode(node->depth()+1, {node->blockId(), node2->blockId()});
|
||||
|
||||
EXPECT_EQ(2u, parent->numChildren());
|
||||
EXPECT_EQ(node->blockId(), parent->readChild(0).blockId());
|
||||
EXPECT_EQ(node2->blockId(), parent->readChild(1).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, BuildingAThreeLevelTreeAndReload) {
|
||||
auto node2 = CreateNewInnerNode();
|
||||
auto parent = CreateAndLoadNewInnerNode(node->depth()+1, {node->blockId(), node2->blockId()});
|
||||
|
||||
EXPECT_EQ(2u, parent->numChildren());
|
||||
EXPECT_EQ(node->blockId(), parent->readChild(0).blockId());
|
||||
EXPECT_EQ(node2->blockId(), parent->readChild(1).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, ConvertToInternalNode) {
|
||||
auto child = nodeStore->createNewLeafNode(Data(0));
|
||||
BlockId node_blockId = node->blockId();
|
||||
unique_ref<DataInnerNode> converted = DataNode::convertToNewInnerNode(std::move(node), nodeStore->layout(), *child);
|
||||
|
||||
EXPECT_EQ(1u, converted->numChildren());
|
||||
EXPECT_EQ(child->blockId(), converted->readChild(0).blockId());
|
||||
EXPECT_EQ(node_blockId, converted->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, ConvertToInternalNodeZeroesOutChildrenRegion) {
|
||||
BlockId blockId = CreateNodeWithDataConvertItToInnerNodeAndReturnKey();
|
||||
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast<const uint8_t*>(block->data())+DataNodeLayout::HEADERSIZE_BYTES+sizeof(DataInnerNode::ChildEntry), nodeStore->layout().maxBytesPerLeaf()-sizeof(DataInnerNode::ChildEntry)));
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, CopyingCreatesNewNode) {
|
||||
auto copied = CopyInnerNode(*node);
|
||||
EXPECT_NE(node->blockId(), copied->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, CopyInnerNodeWithOneChild) {
|
||||
auto copied = CopyInnerNode(*node);
|
||||
|
||||
EXPECT_EQ(node->numChildren(), copied->numChildren());
|
||||
EXPECT_EQ(node->readChild(0).blockId(), copied->readChild(0).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, CopyInnerNodeWithTwoChildren) {
|
||||
AddALeafTo(node.get());
|
||||
auto copied = CopyInnerNode(*node);
|
||||
|
||||
EXPECT_EQ(node->numChildren(), copied->numChildren());
|
||||
EXPECT_EQ(node->readChild(0).blockId(), copied->readChild(0).blockId());
|
||||
EXPECT_EQ(node->readChild(1).blockId(), copied->readChild(1).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, LastChildWhenOneChild) {
|
||||
EXPECT_EQ(leaf->blockId(), node->readLastChild().blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, LastChildWhenTwoChildren) {
|
||||
BlockId blockId = AddALeafTo(node.get());
|
||||
EXPECT_EQ(blockId, node->readLastChild().blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataInnerNodeTest, LastChildWhenThreeChildren) {
|
||||
AddALeafTo(node.get());
|
||||
BlockId blockId = AddALeafTo(node.get());
|
||||
EXPECT_EQ(blockId, node->readLastChild().blockId());
|
||||
}
|
@ -1,345 +0,0 @@
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h"
|
||||
#include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/pointer/cast.h>
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <blockstore/implementations/testfake/FakeBlock.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using std::string;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::deserialize;
|
||||
|
||||
//TODO Split into multiple files
|
||||
|
||||
using cpputils::dynamic_pointer_move;
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using cpputils::Data;
|
||||
using blockstore::BlockId;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using namespace blobstore;
|
||||
using namespace blobstore::onblocks;
|
||||
using namespace blobstore::onblocks::datanodestore;
|
||||
|
||||
namespace {
|
||||
|
||||
#define EXPECT_IS_PTR_TYPE(Type, ptr) EXPECT_NE(nullptr, dynamic_cast<Type*>(ptr)) << "Given pointer cannot be cast to the given type"
|
||||
|
||||
class DataLeafNodeTest: public Test {
|
||||
public:
|
||||
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 1024;
|
||||
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||
|
||||
DataLeafNodeTest():
|
||||
_blockStore(make_unique_ref<FakeBlockStore>()),
|
||||
blockStore(_blockStore.get()),
|
||||
nodeStore(make_unique_ref<DataNodeStore>(std::move(_blockStore), BLOCKSIZE_BYTES)),
|
||||
ZEROES(nodeStore->layout().maxBytesPerLeaf()),
|
||||
randomData(nodeStore->layout().maxBytesPerLeaf()),
|
||||
leaf(nodeStore->createNewLeafNode(Data(0))) {
|
||||
|
||||
ZEROES.FillWithZeroes();
|
||||
|
||||
Data dataFixture(DataFixture::generate(nodeStore->layout().maxBytesPerLeaf()));
|
||||
|
||||
std::memcpy(randomData.data(), dataFixture.data(), randomData.size());
|
||||
}
|
||||
|
||||
Data loadData(const DataLeafNode &leaf) {
|
||||
Data data(leaf.numBytes());
|
||||
leaf.read(data.data(), 0, leaf.numBytes());
|
||||
return data;
|
||||
}
|
||||
|
||||
BlockId WriteDataToNewLeafBlockAndReturnKey() {
|
||||
auto newleaf = nodeStore->createNewLeafNode(Data(0));
|
||||
newleaf->resize(randomData.size());
|
||||
newleaf->write(randomData.data(), 0, randomData.size());
|
||||
return newleaf->blockId();
|
||||
}
|
||||
|
||||
void FillLeafBlockWithData() {
|
||||
FillLeafBlockWithData(leaf.get());
|
||||
}
|
||||
|
||||
void FillLeafBlockWithData(DataLeafNode *leaf_to_fill) {
|
||||
leaf_to_fill->resize(randomData.size());
|
||||
leaf_to_fill->write(randomData.data(), 0, randomData.size());
|
||||
}
|
||||
|
||||
unique_ref<DataLeafNode> LoadLeafNode(const BlockId &blockId) {
|
||||
auto leaf = nodeStore->load(blockId).value();
|
||||
return dynamic_pointer_move<DataLeafNode>(leaf).value();
|
||||
}
|
||||
|
||||
void ResizeLeaf(const BlockId &blockId, size_t size) {
|
||||
auto leaf = LoadLeafNode(blockId);
|
||||
EXPECT_IS_PTR_TYPE(DataLeafNode, leaf.get());
|
||||
leaf->resize(size);
|
||||
}
|
||||
|
||||
BlockId CreateLeafWithDataConvertItToInnerNodeAndReturnKey() {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
FillLeafBlockWithData(leaf.get());
|
||||
auto child = nodeStore->createNewLeafNode(Data(0));
|
||||
unique_ref<DataInnerNode> converted = DataNode::convertToNewInnerNode(std::move(leaf), LAYOUT, *child);
|
||||
return converted->blockId();
|
||||
}
|
||||
|
||||
unique_ref<DataLeafNode> CopyLeafNode(const DataLeafNode &node) {
|
||||
auto copied = nodeStore->createNewNodeAsCopyFrom(node);
|
||||
return dynamic_pointer_move<DataLeafNode>(copied).value();
|
||||
}
|
||||
|
||||
BlockId InitializeLeafGrowAndReturnKey() {
|
||||
auto leaf = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(LAYOUT.maxBytesPerLeaf()));
|
||||
leaf->resize(5);
|
||||
return leaf->blockId();
|
||||
}
|
||||
|
||||
unique_ref<BlockStore> _blockStore;
|
||||
BlockStore *blockStore;
|
||||
unique_ref<DataNodeStore> nodeStore;
|
||||
Data ZEROES;
|
||||
Data randomData;
|
||||
unique_ref<DataLeafNode> leaf;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DataLeafNodeTest);
|
||||
};
|
||||
|
||||
constexpr uint32_t DataLeafNodeTest::BLOCKSIZE_BYTES;
|
||||
constexpr DataNodeLayout DataLeafNodeTest::LAYOUT;
|
||||
|
||||
TEST_F(DataLeafNodeTest, CorrectKeyReturnedAfterLoading) {
|
||||
BlockId blockId = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(LAYOUT.maxBytesPerLeaf()))->blockId();
|
||||
|
||||
auto loaded = nodeStore->load(blockId).value();
|
||||
EXPECT_EQ(blockId, loaded->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, InitializesCorrectly) {
|
||||
auto leaf = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(5));
|
||||
EXPECT_EQ(5u, leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, ReadWrittenDataAfterReloadingBlock) {
|
||||
BlockId blockId = WriteDataToNewLeafBlockAndReturnKey();
|
||||
|
||||
auto loaded = LoadLeafNode(blockId);
|
||||
|
||||
EXPECT_EQ(randomData.size(), loaded->numBytes());
|
||||
EXPECT_EQ(randomData, loadData(*loaded));
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, NewLeafNodeHasSizeZero) {
|
||||
EXPECT_EQ(0u, leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, NewLeafNodeHasSizeZero_AfterLoading) {
|
||||
BlockId blockId = nodeStore->createNewLeafNode(Data(0))->blockId();
|
||||
auto leaf = LoadLeafNode(blockId);
|
||||
|
||||
EXPECT_EQ(0u, leaf->numBytes());
|
||||
}
|
||||
|
||||
class DataLeafNodeSizeTest: public DataLeafNodeTest, public WithParamInterface<unsigned int> {
|
||||
public:
|
||||
BlockId CreateLeafResizeItAndReturnKey() {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
leaf->resize(GetParam());
|
||||
return leaf->blockId();
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataLeafNodeSizeTest, DataLeafNodeSizeTest, Values(0, 1, 5, 16, 32, 512, DataNodeLayout(DataLeafNodeTest::BLOCKSIZE_BYTES).maxBytesPerLeaf()));
|
||||
|
||||
TEST_P(DataLeafNodeSizeTest, ResizeNode_ReadSizeImmediately) {
|
||||
leaf->resize(GetParam());
|
||||
EXPECT_EQ(GetParam(), leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_P(DataLeafNodeSizeTest, ResizeNode_ReadSizeAfterLoading) {
|
||||
BlockId blockId = CreateLeafResizeItAndReturnKey();
|
||||
|
||||
auto leaf = LoadLeafNode(blockId);
|
||||
EXPECT_EQ(GetParam(), leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, SpaceIsZeroFilledWhenGrowing) {
|
||||
leaf->resize(randomData.size());
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), loadData(*leaf).data(), randomData.size()));
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, SpaceGetsZeroFilledWhenShrinkingAndRegrowing) {
|
||||
FillLeafBlockWithData();
|
||||
// resize it smaller and then back to original size
|
||||
uint32_t smaller_size = randomData.size() - 100;
|
||||
leaf->resize(smaller_size);
|
||||
leaf->resize(randomData.size());
|
||||
|
||||
//Check that the space was filled with zeroes
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast<const uint8_t*>(loadData(*leaf).data())+smaller_size, 100));
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, DataGetsZeroFilledWhenShrinking) {
|
||||
BlockId blockId = WriteDataToNewLeafBlockAndReturnKey();
|
||||
uint32_t smaller_size = randomData.size() - 100;
|
||||
{
|
||||
//At first, we expect there to be random data in the underlying data block
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(randomData.dataOffset(smaller_size), static_cast<const uint8_t*>(block->data())+DataNodeLayout::HEADERSIZE_BYTES+smaller_size, 100));
|
||||
}
|
||||
|
||||
//After shrinking, we expect there to be zeroes in the underlying data block
|
||||
ResizeLeaf(blockId, smaller_size);
|
||||
{
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast<const uint8_t*>(block->data())+DataNodeLayout::HEADERSIZE_BYTES+smaller_size, 100));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, ShrinkingDoesntDestroyValidDataRegion) {
|
||||
FillLeafBlockWithData();
|
||||
uint32_t smaller_size = randomData.size() - 100;
|
||||
leaf->resize(smaller_size);
|
||||
|
||||
//Check that the remaining data region is unchanged
|
||||
EXPECT_EQ(0, std::memcmp(randomData.data(), loadData(*leaf).data(), smaller_size));
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, ConvertToInternalNode) {
|
||||
auto child = nodeStore->createNewLeafNode(Data(0));
|
||||
BlockId leaf_blockId = leaf->blockId();
|
||||
unique_ref<DataInnerNode> converted = DataNode::convertToNewInnerNode(std::move(leaf), LAYOUT, *child);
|
||||
|
||||
EXPECT_EQ(1u, converted->numChildren());
|
||||
EXPECT_EQ(child->blockId(), converted->readChild(0).blockId());
|
||||
EXPECT_EQ(leaf_blockId, converted->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, ConvertToInternalNodeZeroesOutChildrenRegion) {
|
||||
BlockId blockId = CreateLeafWithDataConvertItToInnerNodeAndReturnKey();
|
||||
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast<const uint8_t*>(block->data())+DataNodeLayout::HEADERSIZE_BYTES+sizeof(DataInnerNode::ChildEntry), nodeStore->layout().maxBytesPerLeaf()-sizeof(DataInnerNode::ChildEntry)));
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, CopyingCreatesANewLeaf) {
|
||||
auto copied = CopyLeafNode(*leaf);
|
||||
EXPECT_NE(leaf->blockId(), copied->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, CopyEmptyLeaf) {
|
||||
auto copied = CopyLeafNode(*leaf);
|
||||
EXPECT_EQ(leaf->numBytes(), copied->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataLeafNodeTest, CopyDataLeaf) {
|
||||
FillLeafBlockWithData();
|
||||
auto copied = CopyLeafNode(*leaf);
|
||||
|
||||
EXPECT_EQ(leaf->numBytes(), copied->numBytes());
|
||||
EXPECT_EQ(0, std::memcmp(loadData(*leaf).data(), loadData(*copied).data(), leaf->numBytes()));
|
||||
|
||||
//Test that they have different data regions (changing the original one doesn't change the copy)
|
||||
uint8_t data = 0;
|
||||
leaf->write(&data, 0, 1);
|
||||
EXPECT_EQ(data, deserialize<uint8_t>(loadData(*leaf).data()));
|
||||
EXPECT_NE(data, deserialize<uint8_t>(loadData(*copied).data()));
|
||||
}
|
||||
|
||||
|
||||
struct DataRange {
|
||||
uint64_t leafsize;
|
||||
uint64_t offset;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
class DataLeafNodeDataTest: public DataLeafNodeTest, public WithParamInterface<DataRange> {
|
||||
public:
|
||||
Data foregroundData;
|
||||
Data backgroundData;
|
||||
|
||||
DataLeafNodeDataTest():
|
||||
foregroundData(DataFixture::generate(GetParam().count, 0)),
|
||||
backgroundData(DataFixture::generate(GetParam().leafsize, 1)) {
|
||||
}
|
||||
|
||||
BlockId CreateLeafWriteToItAndReturnKey(const Data &to_write) {
|
||||
auto newleaf = nodeStore->createNewLeafNode(Data(0));
|
||||
|
||||
newleaf->resize(GetParam().leafsize);
|
||||
newleaf->write(to_write.data(), GetParam().offset, GetParam().count);
|
||||
return newleaf->blockId();
|
||||
}
|
||||
|
||||
void EXPECT_DATA_READS_AS(const Data &expected, const DataLeafNode &leaf, uint64_t offset, uint64_t count) {
|
||||
Data read(count);
|
||||
leaf.read(read.data(), offset, count);
|
||||
EXPECT_EQ(expected, read);
|
||||
}
|
||||
|
||||
void EXPECT_DATA_READS_AS_OUTSIDE_OF(const Data &expected, const DataLeafNode &leaf, uint64_t start, uint64_t count) {
|
||||
Data begin(start);
|
||||
Data end(GetParam().leafsize - count - start);
|
||||
|
||||
std::memcpy(begin.data(), expected.data(), start);
|
||||
std::memcpy(end.data(), expected.dataOffset(start+count), end.size());
|
||||
|
||||
EXPECT_DATA_READS_AS(begin, leaf, 0, start);
|
||||
EXPECT_DATA_READS_AS(end, leaf, start + count, end.size());
|
||||
}
|
||||
|
||||
void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const DataLeafNode &leaf, uint64_t start, uint64_t count) {
|
||||
Data ZEROES(GetParam().leafsize);
|
||||
ZEROES.FillWithZeroes();
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, leaf, start, count);
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataLeafNodeDataTest, DataLeafNodeDataTest, Values(
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()}, // full size leaf, access beginning to end
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200}, // full size leaf, access middle to middle
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access beginning to middle
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access middle to end
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // non-full size leaf, access beginning to end
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-300}, // non-full size leaf, access middle to middle
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access beginning to middle
|
||||
DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200} // non-full size leaf, access middle to end
|
||||
));
|
||||
|
||||
TEST_P(DataLeafNodeDataTest, WriteAndReadImmediately) {
|
||||
leaf->resize(GetParam().leafsize);
|
||||
leaf->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *leaf, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*leaf, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
TEST_P(DataLeafNodeDataTest, WriteAndReadAfterLoading) {
|
||||
BlockId blockId = CreateLeafWriteToItAndReturnKey(this->foregroundData);
|
||||
|
||||
auto loaded_leaf = LoadLeafNode(blockId);
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *loaded_leaf, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded_leaf, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
TEST_P(DataLeafNodeDataTest, OverwriteAndRead) {
|
||||
leaf->resize(GetParam().leafsize);
|
||||
leaf->write(this->backgroundData.data(), 0, GetParam().leafsize);
|
||||
leaf->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_READS_AS(this->foregroundData, *leaf, GetParam().offset, GetParam().count);
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(this->backgroundData, *leaf, GetParam().offset, GetParam().count);
|
||||
}
|
||||
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h"
|
||||
#include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <blockstore/implementations/testfake/FakeBlock.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using ::testing::Test;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using std::string;
|
||||
using boost::none;
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using namespace blobstore;
|
||||
using namespace blobstore::onblocks;
|
||||
using namespace blobstore::onblocks::datanodestore;
|
||||
|
||||
class DataNodeStoreTest: public Test {
|
||||
public:
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 1024;
|
||||
|
||||
unique_ref<BlockStore> _blockStore = make_unique_ref<FakeBlockStore>();
|
||||
BlockStore *blockStore = _blockStore.get();
|
||||
unique_ref<DataNodeStore> nodeStore = make_unique_ref<DataNodeStore>(std::move(_blockStore), BLOCKSIZE_BYTES);
|
||||
};
|
||||
|
||||
constexpr uint32_t DataNodeStoreTest::BLOCKSIZE_BYTES;
|
||||
|
||||
#define EXPECT_IS_PTR_TYPE(Type, ptr) EXPECT_NE(nullptr, dynamic_cast<Type*>(ptr)) << "Given pointer cannot be cast to the given type"
|
||||
|
||||
TEST_F(DataNodeStoreTest, CreateLeafNodeCreatesLeafNode) {
|
||||
auto node = nodeStore->createNewLeafNode(Data(0));
|
||||
EXPECT_IS_PTR_TYPE(DataLeafNode, node.get());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, CreateInnerNodeCreatesInnerNode) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
|
||||
auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
EXPECT_IS_PTR_TYPE(DataInnerNode, node.get());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, LeafNodeIsRecognizedAfterStoreAndLoad) {
|
||||
BlockId blockId = nodeStore->createNewLeafNode(Data(0))->blockId();
|
||||
|
||||
auto loaded_node = nodeStore->load(blockId).value();
|
||||
|
||||
EXPECT_IS_PTR_TYPE(DataLeafNode, loaded_node.get());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, InnerNodeWithDepth1IsRecognizedAfterStoreAndLoad) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
BlockId blockId = nodeStore->createNewInnerNode(1, {leaf->blockId()})->blockId();
|
||||
|
||||
auto loaded_node = nodeStore->load(blockId).value();
|
||||
|
||||
EXPECT_IS_PTR_TYPE(DataInnerNode, loaded_node.get());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, InnerNodeWithDepth2IsRecognizedAfterStoreAndLoad) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto inner = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
BlockId blockId = nodeStore->createNewInnerNode(2, {inner->blockId()})->blockId();
|
||||
|
||||
auto loaded_node = nodeStore->load(blockId).value();
|
||||
|
||||
EXPECT_IS_PTR_TYPE(DataInnerNode, loaded_node.get());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, DataNodeCrashesOnLoadIfDepthIsTooHigh) {
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
BlockId blockId = block->blockId();
|
||||
{
|
||||
DataNodeView view(std::move(block));
|
||||
view.setDepth(DataNodeStore::MAX_DEPTH + 1);
|
||||
}
|
||||
|
||||
EXPECT_ANY_THROW(
|
||||
nodeStore->load(blockId)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, CreatedInnerNodeIsInitialized) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
EXPECT_EQ(1u, node->numChildren());
|
||||
EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, CreatedLeafNodeIsInitialized) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
EXPECT_EQ(0u, leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NodeIsNotLoadableAfterDeleting) {
|
||||
auto nodekey = nodeStore->createNewLeafNode(Data(0))->blockId();
|
||||
auto node = nodeStore->load(nodekey);
|
||||
EXPECT_NE(none, node);
|
||||
nodeStore->remove(std::move(*node));
|
||||
EXPECT_EQ(none, nodeStore->load(nodekey));
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NumNodesIsCorrectOnEmptyNodestore) {
|
||||
EXPECT_EQ(0u, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterAddingOneLeafNode) {
|
||||
nodeStore->createNewLeafNode(Data(0));
|
||||
EXPECT_EQ(1u, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterRemovingTheLastNode) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
nodeStore->remove(std::move(leaf));
|
||||
EXPECT_EQ(0u, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterAddingTwoNodes) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
EXPECT_EQ(2u, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterRemovingANode) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
nodeStore->remove(std::move(node));
|
||||
EXPECT_EQ(1u, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, PhysicalBlockSize_Leaf) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto block = blockStore->load(leaf->blockId()).value();
|
||||
EXPECT_EQ(BLOCKSIZE_BYTES, block->size());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeStoreTest, PhysicalBlockSize_Inner) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()});
|
||||
auto block = blockStore->load(node->blockId()).value();
|
||||
EXPECT_EQ(BLOCKSIZE_BYTES, block->size());
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeView.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <blockstore/implementations/testfake/FakeBlock.h>
|
||||
#include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h"
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using std::string;
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using namespace blobstore;
|
||||
using namespace blobstore::onblocks;
|
||||
using namespace blobstore::onblocks::datanodestore;
|
||||
|
||||
class DataNodeViewTest: public Test {
|
||||
public:
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 1024;
|
||||
static constexpr uint32_t DATASIZE_BYTES = DataNodeLayout(DataNodeViewTest::BLOCKSIZE_BYTES).datasizeBytes();
|
||||
|
||||
unique_ref<BlockStore> blockStore = make_unique_ref<FakeBlockStore>();
|
||||
};
|
||||
|
||||
class DataNodeViewDepthTest: public DataNodeViewTest, public WithParamInterface<uint8_t> {
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataNodeViewDepthTest, DataNodeViewDepthTest, Values(0, 1, 3, 10, 100));
|
||||
|
||||
TEST_P(DataNodeViewDepthTest, DepthIsStored) {
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
auto blockId = block->blockId();
|
||||
{
|
||||
DataNodeView view(std::move(block));
|
||||
view.setDepth(GetParam());
|
||||
}
|
||||
DataNodeView view(blockStore->load(blockId).value());
|
||||
EXPECT_EQ(GetParam(), view.Depth());
|
||||
}
|
||||
|
||||
class DataNodeViewSizeTest: public DataNodeViewTest, public WithParamInterface<uint32_t> {
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataNodeViewSizeTest, DataNodeViewSizeTest, Values(0, 50, 64, 1024, 1024*1024*1024));
|
||||
|
||||
TEST_P(DataNodeViewSizeTest, SizeIsStored) {
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
auto blockId = block->blockId();
|
||||
{
|
||||
DataNodeView view(std::move(block));
|
||||
view.setSize(GetParam());
|
||||
}
|
||||
DataNodeView view(blockStore->load(blockId).value());
|
||||
EXPECT_EQ(GetParam(), view.Size());
|
||||
}
|
||||
|
||||
TEST_F(DataNodeViewTest, DataIsStored) {
|
||||
Data randomData = DataFixture::generate(DATASIZE_BYTES);
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
auto blockId = block->blockId();
|
||||
{
|
||||
DataNodeView view(std::move(block));
|
||||
view.write(randomData.data(), 0, randomData.size());
|
||||
}
|
||||
DataNodeView view(blockStore->load(blockId).value());
|
||||
EXPECT_EQ(0, std::memcmp(view.data(), randomData.data(), randomData.size()));
|
||||
}
|
||||
|
||||
TEST_F(DataNodeViewTest, HeaderAndBodyDontOverlap) {
|
||||
Data randomData = DataFixture::generate(DATASIZE_BYTES);
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
auto blockId = block->blockId();
|
||||
{
|
||||
DataNodeView view(std::move(block));
|
||||
view.setDepth(3);
|
||||
view.setSize(1000000000u);
|
||||
view.write(randomData.data(), 0, DATASIZE_BYTES);
|
||||
}
|
||||
DataNodeView view(blockStore->load(blockId).value());
|
||||
EXPECT_EQ(3, view.Depth());
|
||||
EXPECT_EQ(1000000000u, view.Size());
|
||||
EXPECT_EQ(0, std::memcmp(view.data(), randomData.data(), DATASIZE_BYTES));
|
||||
}
|
||||
|
||||
TEST_F(DataNodeViewTest, Data) {
|
||||
auto block = blockStore->create(Data(BLOCKSIZE_BYTES));
|
||||
const uint8_t *blockBegin = static_cast<const uint8_t*>(block->data());
|
||||
DataNodeView view(std::move(block));
|
||||
|
||||
EXPECT_EQ(blockBegin+DataNodeLayout::HEADERSIZE_BYTES, static_cast<const uint8_t*>(view.data()));
|
||||
}
|
||||
|
||||
//TODO Test that header fields (and data) are also stored over reloads
|
@ -1,69 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h"
|
||||
#include "blobstore/implementations/onblocks/datatreestore/DataTreeStore.h"
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using blockstore::BlockId;
|
||||
using boost::none;
|
||||
|
||||
using namespace blobstore::onblocks::datatreestore;
|
||||
|
||||
class DataTreeStoreTest: public DataTreeTest {
|
||||
};
|
||||
|
||||
TEST_F(DataTreeStoreTest, CorrectKeyReturned) {
|
||||
BlockId blockId = treeStore.createNewTree()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(blockId, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, CreatedTreeIsLoadable) {
|
||||
auto blockId = treeStore.createNewTree()->blockId();
|
||||
auto loaded = treeStore.load(blockId);
|
||||
EXPECT_NE(none, loaded);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, NewTreeIsLeafOnly) {
|
||||
auto tree = treeStore.createNewTree();
|
||||
|
||||
EXPECT_IS_LEAF_NODE(tree->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, TreeIsNotLoadableAfterRemove_DeleteByTree) {
|
||||
BlockId blockId = treeStore.createNewTree()->blockId();
|
||||
auto tree = treeStore.load(blockId);
|
||||
EXPECT_NE(none, tree);
|
||||
treeStore.remove(std::move(*tree));
|
||||
EXPECT_EQ(none, treeStore.load(blockId));
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, TreeIsNotLoadableAfterRemove_DeleteByKey) {
|
||||
BlockId blockId = treeStore.createNewTree()->blockId();
|
||||
treeStore.remove(blockId);
|
||||
EXPECT_EQ(none, treeStore.load(blockId));
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, RemovingTreeRemovesAllNodesOfTheTree_DeleteByTree) {
|
||||
auto tree1_blockId = CreateThreeLevelMinData()->blockId();
|
||||
auto tree2_blockId = treeStore.createNewTree()->blockId();
|
||||
|
||||
auto tree1 = treeStore.load(tree1_blockId).value();
|
||||
treeStore.remove(std::move(tree1));
|
||||
|
||||
//Check that the only remaining node is tree2
|
||||
EXPECT_EQ(1u, nodeStore->numNodes());
|
||||
EXPECT_NE(none, treeStore.load(tree2_blockId));
|
||||
}
|
||||
|
||||
TEST_F(DataTreeStoreTest, RemovingTreeRemovesAllNodesOfTheTree_DeleteByKey) {
|
||||
auto tree1_blockId = CreateThreeLevelMinData()->blockId();
|
||||
auto tree2_blockId = treeStore.createNewTree()->blockId();
|
||||
|
||||
treeStore.remove(tree1_blockId);
|
||||
|
||||
//Check that the only remaining node is tree2
|
||||
EXPECT_EQ(1u, nodeStore->numNodes());
|
||||
EXPECT_NE(none, treeStore.load(tree2_blockId));
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataNodeLayout;
|
||||
using blockstore::BlockId;
|
||||
|
||||
class DataTreeTest_NumStoredBytes: public DataTreeTest {
|
||||
public:
|
||||
};
|
||||
|
||||
TEST_F(DataTreeTest_NumStoredBytes, CreatedTreeIsEmpty) {
|
||||
auto tree = treeStore.createNewTree();
|
||||
EXPECT_EQ(0u, tree->numBytes());
|
||||
}
|
||||
|
||||
class DataTreeTest_NumStoredBytes_P: public DataTreeTest_NumStoredBytes, public WithParamInterface<uint32_t> {};
|
||||
INSTANTIATE_TEST_SUITE_P(EmptyLastLeaf, DataTreeTest_NumStoredBytes_P, Values(0u));
|
||||
INSTANTIATE_TEST_SUITE_P(HalfFullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(5u, 10u));
|
||||
INSTANTIATE_TEST_SUITE_P(FullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(static_cast<uint32_t>(DataNodeLayout(DataTreeTest_NumStoredBytes::BLOCKSIZE_BYTES).maxBytesPerLeaf())));
|
||||
|
||||
//TODO Test numLeaves() and numNodes() also two configurations with same number of bytes but different number of leaves (last leaf has 0 bytes)
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, SingleLeaf) {
|
||||
BlockId blockId = CreateLeafWithSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(1, tree->numLeaves());
|
||||
EXPECT_EQ(1, tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, TwoLeafTree) {
|
||||
BlockId blockId = CreateTwoLeafWithSecondLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(2, tree->numLeaves());
|
||||
EXPECT_EQ(3, tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, FullTwolevelTree) {
|
||||
BlockId blockId = CreateFullTwoLevelWithLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithOneChild) {
|
||||
BlockId blockId = CreateThreeLevelWithOneChildAndLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(2, tree->numLeaves());
|
||||
EXPECT_EQ(4, tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithTwoChildren) {
|
||||
BlockId blockId = CreateThreeLevelWithTwoChildrenAndLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(2 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||
EXPECT_EQ(5 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithThreeChildren) {
|
||||
BlockId blockId = CreateThreeLevelWithThreeChildrenAndLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(2*nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(2 + 2*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||
EXPECT_EQ(6 + 2*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, FullThreeLevelTree) {
|
||||
BlockId blockId = CreateFullThreeLevelWithLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_NumStoredBytes_P, FourLevelMinDataTree) {
|
||||
BlockId blockId = CreateFourLevelMinDataWithLastLeafSize(GetParam())->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + GetParam(), tree->numBytes());
|
||||
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||
EXPECT_EQ(5 + nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||
}
|
@ -1,558 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
using blobstore::onblocks::datatreestore::DataTree;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
|
||||
class DataTreeTest_Performance: public DataTreeTest {
|
||||
public:
|
||||
void TraverseByWriting(DataTree *tree, uint64_t beginIndex, uint64_t endIndex) {
|
||||
uint64_t offset = beginIndex * maxBytesPerLeaf;
|
||||
uint64_t count = endIndex * maxBytesPerLeaf - offset;
|
||||
Data data(count);
|
||||
data.FillWithZeroes();
|
||||
tree->writeBytes(data.data(), offset, count);
|
||||
}
|
||||
|
||||
void TraverseByReading(DataTree *tree, uint64_t beginIndex, uint64_t endIndex) {
|
||||
uint64_t offset = beginIndex * maxBytesPerLeaf;
|
||||
uint64_t count = endIndex * maxBytesPerLeaf - offset;
|
||||
Data data(count);
|
||||
tree->readBytes(data.data(), offset, count);
|
||||
}
|
||||
|
||||
uint64_t maxChildrenPerInnerNode = nodeStore->layout().maxChildrenPerInnerNode();
|
||||
uint64_t maxBytesPerLeaf = nodeStore->layout().maxBytesPerLeaf();
|
||||
};
|
||||
|
||||
TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Twolevel_DeleteByTree) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
treeStore.remove(std::move(tree));
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Twolevel_DeleteByKey) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
blockStore->resetCounters();
|
||||
|
||||
treeStore.remove(blockId);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Threelevel_DeleteByTree) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
treeStore.remove(std::move(tree));
|
||||
|
||||
EXPECT_EQ(maxChildrenPerInnerNode, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode + maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Threelevel_DeleteByKey) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
blockStore->resetCounters();
|
||||
|
||||
treeStore.remove(blockId);
|
||||
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode + maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_All_ByWriting) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 0, maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Has to load the rightmost leaf once to adapt its size, rest of the leaves aren't loaded but just overwritten
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_All_ByReading) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), 0, maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Has to read the rightmost leaf an additional time in the beginning to determine size.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_Some_ByWriting) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 3, 5);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_Some_ByReading) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), 3, 5);
|
||||
|
||||
EXPECT_EQ(3u, blockStore->loadedBlocks().size()); // reads 2 leaves and the rightmost leaf to determine size
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_All_ByWriting) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 0, maxChildrenPerInnerNode * maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(maxChildrenPerInnerNode + 1, blockStore->loadedBlocks().size()); // Loads inner nodes and has to load the rightmost leaf once to adapt its size, rest of the leaves aren't loaded but just overwritten.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_All_ByReading) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), 0, maxChildrenPerInnerNode * maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(maxChildrenPerInnerNode*maxChildrenPerInnerNode + maxChildrenPerInnerNode + 2, blockStore->loadedBlocks().size()); // Loads inner nodes and leaves. Has to load the rightmost inner node and leaf an additional time at the beginning to compute size
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InOneInner_ByWriting) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 3, 5);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads inner node. Doesn't load the leaves, they're just overwritten.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InOneInner_ByReading) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), 3, 5);
|
||||
|
||||
EXPECT_EQ(5u, blockStore->loadedBlocks().size()); // reads 2 leaves and the inner node, also has to read the rightmost inner node and leaf additionally at the beginning to determine size
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InTwoInner_ByWriting) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 3, 3 + maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads both inner node
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InTwoInner_ByReading) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), 3, 3 + maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(4u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Loads both inner nodes and the requested leaves. Also has to load rightmost inner node and leaf additionally in the beginning to determine size.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_WholeInner_ByWriting) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), maxChildrenPerInnerNode, 2*maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads inner node. Doesn't load the leaves, they're just overwritten.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_WholeInner_ByReading) {
|
||||
auto blockId = CreateFullThreeLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByReading(tree.get(), maxChildrenPerInnerNode, 2*maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Loads inner node and all requested leaves. Also has to load rightmost inner node and leaf additionally in the beginning to determine size.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingInside) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 1, 4);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old child (for growing it)
|
||||
EXPECT_EQ(2u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // write the data and add children to inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingOutside_TwoLevel) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 4, 5);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it
|
||||
EXPECT_EQ(3u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add child to inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingOutside_ThreeLevel) {
|
||||
auto blockId = CreateInner({CreateFullTwoLevel(), CreateFullTwoLevel()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 2*maxChildrenPerInnerNode+1, 2*maxChildrenPerInnerNode+2);
|
||||
|
||||
EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads last old leaf (and its inner node) for growing it
|
||||
EXPECT_EQ(3u, blockStore->createdBlocks()); // inner node and two leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingAtBeginOfChild) {
|
||||
auto blockId = CreateInner({CreateFullTwoLevel(), CreateFullTwoLevel()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), maxChildrenPerInnerNode, 3*maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads inner node and one leaf to check whether we have to grow it. Doesn't load the leaves, but returns the keys of the leaves to the callback.
|
||||
EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // Creates an inner node and its leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(maxChildrenPerInnerNode + 1u, blockStore->distinctWrittenBlocks().size()); // write data and add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInOldDepth) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 4, maxChildrenPerInnerNode+2);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it
|
||||
EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // Add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInOldDepth_ResizeLastLeaf) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), 4, maxChildrenPerInnerNode+2);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it
|
||||
EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // Resize last leaf and add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInNewDepth) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), maxChildrenPerInnerNode, maxChildrenPerInnerNode+2);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it
|
||||
EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // Add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInNewDepth_ResizeLastLeaf) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
TraverseByWriting(tree.get(), maxChildrenPerInnerNode, maxChildrenPerInnerNode+2);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it
|
||||
EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // Resize last leaf and add children to existing inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_ZeroToZero) {
|
||||
auto blockId = CreateLeafWithSize(0)->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(0);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowOneLeaf) {
|
||||
auto blockId = CreateLeafWithSize(0)->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(5);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkOneLeaf) {
|
||||
auto blockId = CreateLeafWithSize(5)->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(2);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkOneLeafToZero) {
|
||||
auto blockId = CreateLeafWithSize(5)->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(0);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowOneLeafInLargerTree) {
|
||||
auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf(), CreateLeafWithSize(5)})})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*(maxChildrenPerInnerNode+1)+6); // Grow by one byte
|
||||
|
||||
EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Load inner node and leaf
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowByOneLeaf) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*2+1); // Grow by one byte
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add child to inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowByOneLeaf_GrowLastLeaf) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*2+1); // Grow by one byte
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // add child to inner node and resize old last leaf
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkByOneLeaf) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(2*maxBytesPerLeaf-1);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(1u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // resize new last leaf and remove leaf from inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_0to1) {
|
||||
auto blockId = CreateLeaf()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf+1);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_1to2) {
|
||||
auto blockId = CreateFullTwoLevel()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode+1);
|
||||
|
||||
EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // check whether we have to grow last leaf
|
||||
EXPECT_EQ(3u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_0to2) {
|
||||
auto blockId = CreateLeaf()->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode+1);
|
||||
|
||||
EXPECT_EQ(0u, blockStore->loadedBlocks().size());
|
||||
EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->createdBlocks());
|
||||
EXPECT_EQ(0u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_1to0) {
|
||||
auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf);
|
||||
|
||||
EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // read content of first leaf and load first leaf to replace root with it
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(2u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be a leaf
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_2to1) {
|
||||
auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode);
|
||||
|
||||
EXPECT_EQ(4u, blockStore->loadedBlocks().size()); // load new last leaf (+inner node), load second inner node to remove its subtree, then load first child of root to replace root with its child.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(3u, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be a leaf
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_2to0) {
|
||||
auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})})->blockId();
|
||||
auto tree = treeStore.load(blockId).value();
|
||||
blockStore->resetCounters();
|
||||
|
||||
tree->resizeNumBytes(maxBytesPerLeaf);
|
||||
|
||||
EXPECT_EQ(5u, blockStore->loadedBlocks().size()); // load new last leaf (+inner node), load second inner node to remove its subtree, then 2x load first child of root to replace root with its child.
|
||||
EXPECT_EQ(0u, blockStore->createdBlocks());
|
||||
EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->removedBlocks().size());
|
||||
EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // remove children from inner node and rewrite root node to be a leaf
|
||||
EXPECT_EQ(0u, blockStore->resizedBlocks().size());
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
#include "testutils/TwoLevelDataFixture.h"
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
#include <cpp-utils/data/Data.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using ::testing::Combine;
|
||||
using std::tuple;
|
||||
using std::get;
|
||||
using std::function;
|
||||
using std::mem_fn;
|
||||
using cpputils::dynamic_pointer_move;
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataLeafNode;
|
||||
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||
using blobstore::onblocks::datanodestore::DataNode;
|
||||
using blobstore::onblocks::datanodestore::DataNodeLayout;
|
||||
using blobstore::onblocks::datatreestore::DataTree;
|
||||
using blobstore::onblocks::utils::ceilDivision;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using boost::none;
|
||||
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class DataTreeTest_ResizeByTraversing: public DataTreeTest {
|
||||
public:
|
||||
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||
|
||||
unique_ref<DataTree> CreateTree(unique_ref<DataNode> root) {
|
||||
BlockId blockId = root->blockId();
|
||||
cpputils::destruct(std::move(root));
|
||||
return treeStore.load(blockId).value();
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateLeafTreeWithSize(uint32_t size) {
|
||||
return CreateTree(CreateLeafWithSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateTwoLeafWithSecondLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullTwoLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullThreeLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
void EXPECT_IS_LEFTMAXDATA_TREE(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||
if (inner != nullptr) {
|
||||
for (uint32_t i = 0; i < inner->numChildren()-1; ++i) {
|
||||
EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId());
|
||||
}
|
||||
EXPECT_IS_LEFTMAXDATA_TREE(inner->readLastChild().blockId());
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
void EXPECT_IS_MAXDATA_TREE(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||
if (inner != nullptr) {
|
||||
for (uint32_t i = 0; i < inner->numChildren(); ++i) {
|
||||
EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId());
|
||||
}
|
||||
} else {
|
||||
DataLeafNode *leaf = dynamic_cast<DataLeafNode*>(root.get());
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes());
|
||||
}
|
||||
}
|
||||
};
|
||||
constexpr DataNodeLayout DataTreeTest_ResizeByTraversing::LAYOUT;
|
||||
|
||||
class DataTreeTest_ResizeByTraversing_P: public DataTreeTest_ResizeByTraversing, public WithParamInterface<tuple<function<unique_ref<DataTree>(DataTreeTest_ResizeByTraversing*, uint32_t)>, uint32_t, uint32_t, std::function<uint32_t (uint32_t oldNumberOfLeaves, uint32_t newNumberOfLeaves)>>> {
|
||||
public:
|
||||
DataTreeTest_ResizeByTraversing_P()
|
||||
: oldLastLeafSize(get<1>(GetParam())),
|
||||
tree(get<0>(GetParam())(this, oldLastLeafSize)),
|
||||
numberOfLeavesToAdd(get<2>(GetParam())),
|
||||
newNumberOfLeaves(tree->numLeaves()+numberOfLeavesToAdd),
|
||||
traversalBeginIndex(get<3>(GetParam())(tree->numLeaves(), newNumberOfLeaves)),
|
||||
ZEROES(LAYOUT.maxBytesPerLeaf())
|
||||
{
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
|
||||
void GrowTree(const BlockId &blockId) {
|
||||
auto tree = treeStore.load(blockId);
|
||||
GrowTree(tree.get().get());
|
||||
}
|
||||
|
||||
void GrowTree(DataTree *tree) {
|
||||
uint64_t maxBytesPerLeaf = tree->maxBytesPerLeaf();
|
||||
uint64_t offset = traversalBeginIndex * maxBytesPerLeaf;
|
||||
uint64_t count = newNumberOfLeaves * maxBytesPerLeaf - offset;
|
||||
Data data(count);
|
||||
data.FillWithZeroes();
|
||||
tree->writeBytes(data.data(), offset, count);
|
||||
tree->flush();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
unique_ref<DataLeafNode> LastLeaf(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
auto leaf = dynamic_pointer_move<DataLeafNode>(root);
|
||||
if (leaf != none) {
|
||||
return std::move(*leaf);
|
||||
}
|
||||
auto inner = dynamic_pointer_move<DataInnerNode>(root).value();
|
||||
return LastLeaf(inner->readLastChild().blockId());
|
||||
}
|
||||
|
||||
uint32_t oldLastLeafSize;
|
||||
unique_ref<DataTree> tree;
|
||||
uint32_t numberOfLeavesToAdd;
|
||||
uint32_t newNumberOfLeaves;
|
||||
uint32_t traversalBeginIndex;
|
||||
Data ZEROES;
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataTreeTest_ResizeByTraversing_P, DataTreeTest_ResizeByTraversing_P,
|
||||
Combine(
|
||||
//Tree we're starting with
|
||||
Values<function<unique_ref<DataTree>(DataTreeTest_ResizeByTraversing*, uint32_t)>>(
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateLeafTreeWithSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateTwoLeafTreeWithSecondLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullTwoLevelTreeWithLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullThreeLevelTreeWithLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFourLevelMinDataTreeWithLastLeafSize)
|
||||
),
|
||||
//Last leaf size of the start tree
|
||||
Values(
|
||||
0u,
|
||||
1u,
|
||||
10u,
|
||||
DataTreeTest_ResizeByTraversing::LAYOUT.maxBytesPerLeaf()
|
||||
),
|
||||
//Number of leaves we're adding
|
||||
Values(
|
||||
1u,
|
||||
2u,
|
||||
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full two level tree
|
||||
2* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with two children
|
||||
3* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with three children
|
||||
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full three level tree
|
||||
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() + 1 //Four level mindata tree
|
||||
),
|
||||
//Decide the traversal begin index
|
||||
Values(
|
||||
[] (uint32_t /*oldNumberOfLeaves*/, uint32_t newNumberOfLeaves) {return newNumberOfLeaves-1;}, // Traverse last leaf (begin==end-1)
|
||||
[] (uint32_t oldNumberOfLeaves, uint32_t newNumberOfLeaves) {return (oldNumberOfLeaves+newNumberOfLeaves)/2;}, // Start traversal in middle of new leaves
|
||||
[] (uint32_t oldNumberOfLeaves, uint32_t /*newNumberOfLeaves*/) {return oldNumberOfLeaves-1;}, // Start traversal with last old leaf
|
||||
[] (uint32_t oldNumberOfLeaves, uint32_t /*newNumberOfLeaves*/) {return oldNumberOfLeaves;}, // Start traversal with first new leaf
|
||||
[] (uint32_t /*oldNumberOfLeaves*/, uint32_t /*newNumberOfLeaves*/) {return 0;}, // Traverse full tree
|
||||
[] (uint32_t /*oldNumberOfLeaves*/, uint32_t /*newNumberOfLeaves*/) {return 1;} // Traverse full tree except first leaf
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, StructureIsValid) {
|
||||
GrowTree(tree.get());
|
||||
EXPECT_IS_LEFTMAXDATA_TREE(tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, NumLeavesIsCorrect_FromCache) {
|
||||
tree->numLeaves(); // fill cache with old value
|
||||
GrowTree(tree.get());
|
||||
// tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||
// This is what the StructureIsValid test case is for.
|
||||
EXPECT_EQ(newNumberOfLeaves, tree->numLeaves());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, NumLeavesIsCorrect) {
|
||||
GrowTree(tree.get());
|
||||
// tree->forceComputeNumLeaves() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||
// This is what the StructureIsValid test case is for.
|
||||
EXPECT_EQ(newNumberOfLeaves, tree->forceComputeNumLeaves());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, DepthFlagsAreCorrect) {
|
||||
GrowTree(tree.get());
|
||||
uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode()));
|
||||
CHECK_DEPTH(depth, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, KeyDoesntChange) {
|
||||
BlockId blockId = tree->blockId();
|
||||
tree->flush();
|
||||
GrowTree(tree.get());
|
||||
EXPECT_EQ(blockId, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeByTraversing_P, DataStaysIntact) {
|
||||
uint32_t oldNumberOfLeaves = std::max(UINT64_C(1), ceilDivision(tree->numBytes(), static_cast<uint64_t>(nodeStore->layout().maxBytesPerLeaf())));
|
||||
|
||||
TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged);
|
||||
BlockId blockId = tree->blockId();
|
||||
cpputils::destruct(std::move(tree));
|
||||
data.FillInto(nodeStore->load(blockId).get().get());
|
||||
|
||||
GrowTree(blockId);
|
||||
|
||||
if (traversalBeginIndex < oldNumberOfLeaves) {
|
||||
// Traversal wrote over part of the pre-existing data, we can only check the data before it.
|
||||
if (traversalBeginIndex != 0) {
|
||||
data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), static_cast<int>(traversalBeginIndex - 1));
|
||||
}
|
||||
} else {
|
||||
// Here, traversal was entirely outside the preexisting data, we can check all preexisting data.
|
||||
data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), oldNumberOfLeaves, oldLastLeafSize);
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
#include "testutils/TwoLevelDataFixture.h"
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
#include <cpp-utils/data/Data.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using ::testing::Combine;
|
||||
using std::tuple;
|
||||
using std::get;
|
||||
using std::function;
|
||||
using std::mem_fn;
|
||||
using cpputils::dynamic_pointer_move;
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataLeafNode;
|
||||
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||
using blobstore::onblocks::datanodestore::DataNode;
|
||||
using blobstore::onblocks::datanodestore::DataNodeLayout;
|
||||
using blobstore::onblocks::datatreestore::DataTree;
|
||||
using blobstore::onblocks::utils::ceilDivision;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using boost::none;
|
||||
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class DataTreeTest_ResizeNumBytes: public DataTreeTest {
|
||||
public:
|
||||
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||
|
||||
unique_ref<DataTree> CreateTree(unique_ref<DataNode> root) {
|
||||
BlockId blockId = root->blockId();
|
||||
cpputils::destruct(std::move(root));
|
||||
return treeStore.load(blockId).value();
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateLeafTreeWithSize(uint32_t size) {
|
||||
return CreateTree(CreateLeafWithSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateTwoLeafWithSecondLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullTwoLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullThreeLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ref<DataTree> CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
void EXPECT_IS_LEFTMAXDATA_TREE(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||
if (inner != nullptr) {
|
||||
for (uint32_t i = 0; i < inner->numChildren()-1; ++i) {
|
||||
EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId());
|
||||
}
|
||||
EXPECT_IS_LEFTMAXDATA_TREE(inner->readLastChild().blockId());
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
void EXPECT_IS_MAXDATA_TREE(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||
if (inner != nullptr) {
|
||||
for (uint32_t i = 0; i < inner->numChildren(); ++i) {
|
||||
EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId());
|
||||
}
|
||||
} else {
|
||||
DataLeafNode *leaf = dynamic_cast<DataLeafNode*>(root.get());
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes());
|
||||
}
|
||||
}
|
||||
};
|
||||
constexpr DataNodeLayout DataTreeTest_ResizeNumBytes::LAYOUT;
|
||||
|
||||
class DataTreeTest_ResizeNumBytes_P: public DataTreeTest_ResizeNumBytes, public WithParamInterface<tuple<function<unique_ref<DataTree>(DataTreeTest_ResizeNumBytes*, uint32_t)>, uint32_t, uint32_t, uint32_t>> {
|
||||
public:
|
||||
DataTreeTest_ResizeNumBytes_P()
|
||||
: oldLastLeafSize(get<1>(GetParam())),
|
||||
tree(get<0>(GetParam())(this, oldLastLeafSize)),
|
||||
newNumberOfLeaves(get<2>(GetParam())),
|
||||
newLastLeafSize(get<3>(GetParam())),
|
||||
newSize((newNumberOfLeaves-1) * LAYOUT.maxBytesPerLeaf() + newLastLeafSize),
|
||||
ZEROES(LAYOUT.maxBytesPerLeaf())
|
||||
{
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
|
||||
void ResizeTree(const BlockId &blockId, uint64_t size) {
|
||||
treeStore.load(blockId).get()->resizeNumBytes(size);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
unique_ref<DataLeafNode> LastLeaf(const BlockId &blockId) {
|
||||
auto root = nodeStore->load(blockId).value();
|
||||
auto leaf = dynamic_pointer_move<DataLeafNode>(root);
|
||||
if (leaf != none) {
|
||||
return std::move(*leaf);
|
||||
}
|
||||
auto inner = dynamic_pointer_move<DataInnerNode>(root).value();
|
||||
return LastLeaf(inner->readLastChild().blockId());
|
||||
}
|
||||
|
||||
uint32_t oldLastLeafSize;
|
||||
unique_ref<DataTree> tree;
|
||||
uint32_t newNumberOfLeaves;
|
||||
uint32_t newLastLeafSize;
|
||||
uint64_t newSize;
|
||||
Data ZEROES;
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DataTreeTest_ResizeNumBytes_P, DataTreeTest_ResizeNumBytes_P,
|
||||
Combine(
|
||||
//Tree we're starting with
|
||||
Values<function<unique_ref<DataTree>(DataTreeTest_ResizeNumBytes*, uint32_t)>>(
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateLeafTreeWithSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateTwoLeafTreeWithSecondLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateFullTwoLevelTreeWithLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateFullThreeLevelTreeWithLastLeafSize),
|
||||
mem_fn(&DataTreeTest_ResizeNumBytes::CreateFourLevelMinDataTreeWithLastLeafSize)
|
||||
),
|
||||
//Last leaf size of the start tree
|
||||
Values(
|
||||
0u,
|
||||
1u,
|
||||
10u,
|
||||
DataTreeTest_ResizeNumBytes::LAYOUT.maxBytesPerLeaf()
|
||||
),
|
||||
//Number of leaves we're resizing to
|
||||
Values(
|
||||
1u,
|
||||
2u,
|
||||
DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Full two level tree
|
||||
2* DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with two children
|
||||
3* DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with three children
|
||||
DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Full three level tree
|
||||
DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() + 1 //Four level mindata tree
|
||||
),
|
||||
//Last leaf size of the resized tree
|
||||
Values(
|
||||
1u,
|
||||
10u,
|
||||
DataTreeTest_ResizeNumBytes::LAYOUT.maxBytesPerLeaf()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, StructureIsValid) {
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
EXPECT_IS_LEFTMAXDATA_TREE(tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, NumBytesIsCorrect) {
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
// tree->numBytes() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||
// This is what the StructureIsValid test case is for.
|
||||
EXPECT_EQ(newSize, tree->numBytes());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, NumLeavesIsCorrect) {
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
// tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||
// This is what the StructureIsValid test case is for.
|
||||
EXPECT_EQ(newNumberOfLeaves, tree->forceComputeNumLeaves());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, NumLeavesIsCorrect_FromCache) {
|
||||
tree->numLeaves(); // fill cache with old value
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
// tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||
// This is what the StructureIsValid test case is for.
|
||||
EXPECT_EQ(newNumberOfLeaves, tree->numLeaves());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, DepthFlagsAreCorrect) {
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode()) - 0.00000000001); // The subtraction takes care of double inaccuracies if newNumberOfLeaves == maxChildrenPerInnerNode
|
||||
CHECK_DEPTH(depth, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, KeyDoesntChange) {
|
||||
BlockId blockId = tree->blockId();
|
||||
tree->flush();
|
||||
tree->resizeNumBytes(newSize);
|
||||
EXPECT_EQ(blockId, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, DataStaysIntact) {
|
||||
uint32_t oldNumberOfLeaves = std::max(UINT64_C(1), ceilDivision(tree->numBytes(), static_cast<uint64_t>(nodeStore->layout().maxBytesPerLeaf())));
|
||||
TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged);
|
||||
BlockId blockId = tree->blockId();
|
||||
cpputils::destruct(std::move(tree));
|
||||
data.FillInto(nodeStore->load(blockId).get().get());
|
||||
|
||||
ResizeTree(blockId, newSize);
|
||||
|
||||
if (oldNumberOfLeaves < newNumberOfLeaves || (oldNumberOfLeaves == newNumberOfLeaves && oldLastLeafSize < newLastLeafSize)) {
|
||||
data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), oldNumberOfLeaves, oldLastLeafSize);
|
||||
} else {
|
||||
data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), newNumberOfLeaves, newLastLeafSize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DataTreeTest_ResizeNumBytes_P, UnneededBlocksGetDeletedWhenShrinking) {
|
||||
tree->resizeNumBytes(newSize);
|
||||
tree->flush();
|
||||
|
||||
uint64_t expectedNumNodes = 1; // 1 for the root node
|
||||
uint64_t nodesOnCurrentLevel = newNumberOfLeaves;
|
||||
while (nodesOnCurrentLevel > 1) {
|
||||
expectedNumNodes += nodesOnCurrentLevel;
|
||||
nodesOnCurrentLevel = ceilDivision(nodesOnCurrentLevel, nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
EXPECT_EQ(expectedNumNodes, nodeStore->numNodes());
|
||||
}
|
||||
|
||||
//Resize to zero is not caught in the parametrized test above, in the following, we test it separately.
|
||||
|
||||
TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_NumBytesIsCorrect) {
|
||||
auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u);
|
||||
tree->resizeNumBytes(0);
|
||||
BlockId blockId = tree->blockId();
|
||||
cpputils::destruct(std::move(tree));
|
||||
auto leaf = LoadLeafNode(blockId);
|
||||
EXPECT_EQ(0u, leaf->numBytes());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_blockIdDoesntChange) {
|
||||
auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u);
|
||||
BlockId blockId = tree->blockId();
|
||||
tree->resizeNumBytes(0);
|
||||
tree->flush();
|
||||
EXPECT_EQ(blockId, tree->blockId());
|
||||
}
|
||||
|
||||
TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_UnneededBlocksGetDeletedWhenShrinking) {
|
||||
auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u);
|
||||
tree->resizeNumBytes(0);
|
||||
tree->flush();
|
||||
EXPECT_EQ(1u, nodeStore->numNodes());
|
||||
}
|
@ -1,449 +0,0 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
#include <blobstore/implementations/onblocks/datatreestore/impl/LeafTraverser.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Eq;
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataLeafNode;
|
||||
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||
using blobstore::onblocks::datanodestore::DataNode;
|
||||
using blobstore::onblocks::datatreestore::LeafHandle;
|
||||
using blobstore::onblocks::datatreestore::LeafTraverser;
|
||||
using blockstore::BlockId;
|
||||
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::Data;
|
||||
using std::shared_ptr;
|
||||
using std::make_shared;
|
||||
|
||||
class TraversorMock {
|
||||
public:
|
||||
MOCK_METHOD(void, calledExistingLeaf, (DataLeafNode*, bool, uint32_t));
|
||||
MOCK_METHOD(shared_ptr<Data>, calledCreateLeaf, (uint32_t));
|
||||
};
|
||||
|
||||
MATCHER_P(KeyEq, expected, "node blockId equals") {
|
||||
return arg->blockId() == expected;
|
||||
}
|
||||
|
||||
class LeafTraverserTest: public DataTreeTest {
|
||||
public:
|
||||
LeafTraverserTest() :traversor() {}
|
||||
|
||||
unique_ref<DataInnerNode> CreateThreeLevel() {
|
||||
return CreateInner({
|
||||
CreateFullTwoLevel(),
|
||||
CreateFullTwoLevel(),
|
||||
CreateFullTwoLevel(),
|
||||
CreateFullTwoLevel(),
|
||||
CreateFullTwoLevel(),
|
||||
CreateInner({CreateLeaf(), CreateLeaf(), CreateLeaf()})});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> CreateFourLevel() {
|
||||
return CreateInner({
|
||||
CreateFullThreeLevel(),
|
||||
CreateFullThreeLevel(),
|
||||
CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})})
|
||||
});
|
||||
}
|
||||
|
||||
void EXPECT_CREATE_LEAF(uint32_t leafIndex) {
|
||||
uint64_t maxBytesPerLeaf = nodeStore->layout().maxBytesPerLeaf();
|
||||
EXPECT_CALL(traversor, calledCreateLeaf(Eq(leafIndex))).Times(1).WillOnce(Invoke([maxBytesPerLeaf] (uint32_t) {
|
||||
return make_shared<Data>(maxBytesPerLeaf);
|
||||
}));
|
||||
}
|
||||
|
||||
void EXPECT_TRAVERSE_LEAF(const BlockId &blockId, bool isRightBorderLeaf, uint32_t leafIndex) {
|
||||
EXPECT_CALL(traversor, calledExistingLeaf(KeyEq(blockId), isRightBorderLeaf, leafIndex)).Times(1);
|
||||
}
|
||||
|
||||
void EXPECT_TRAVERSE_ALL_CHILDREN_OF(const DataInnerNode &node, bool isRightBorderNode, uint32_t firstLeafIndex) {
|
||||
for (unsigned int i = 0; i < node.numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(node.readChild(i).blockId(), isRightBorderNode && i == node.numChildren()-1, firstLeafIndex+i);
|
||||
}
|
||||
}
|
||||
|
||||
void EXPECT_DONT_TRAVERSE_ANY_LEAVES() {
|
||||
EXPECT_CALL(traversor, calledExistingLeaf(testing::_, testing::_, testing::_)).Times(0);
|
||||
EXPECT_CALL(traversor, calledCreateLeaf(testing::_)).Times(0);
|
||||
}
|
||||
|
||||
void TraverseLeaves(unique_ref<DataNode> root, uint32_t beginIndex, uint32_t endIndex, bool expectReadOnly) {
|
||||
root->flush();
|
||||
auto tree = treeStore.load(root->blockId()).value();
|
||||
auto* old_root = root.get();
|
||||
LeafTraverser(nodeStore, expectReadOnly).traverseAndUpdateRoot(&root, beginIndex, endIndex, [this] (uint32_t nodeIndex, bool isRightBorderNode,LeafHandle leaf) {
|
||||
traversor.calledExistingLeaf(leaf.node(), isRightBorderNode, nodeIndex);
|
||||
}, [this] (uint32_t nodeIndex) -> Data {
|
||||
return traversor.calledCreateLeaf(nodeIndex)->copy();
|
||||
}, [] (auto) {});
|
||||
if (expectReadOnly) {
|
||||
EXPECT_EQ(old_root, root.get());
|
||||
} else {
|
||||
EXPECT_NE(old_root, root.get());
|
||||
}
|
||||
}
|
||||
|
||||
TraversorMock traversor;
|
||||
};
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseSingleLeafTree) {
|
||||
unique_ref<DataNode> root = CreateLeaf();
|
||||
EXPECT_TRAVERSE_LEAF(root->blockId(), true, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseNothingInSingleLeafTree1) {
|
||||
unique_ref<DataNode> root = CreateLeaf();
|
||||
EXPECT_DONT_TRAVERSE_ANY_LEAVES();
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 0, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseNothingInSingleLeafTree2) {
|
||||
unique_ref<DataNode> root = CreateLeaf();
|
||||
EXPECT_DONT_TRAVERSE_ANY_LEAVES();
|
||||
|
||||
TraverseLeaves(std::move(root), 1, 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstLeafOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(0).blockId(), false, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddleLeafOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(5).blockId(), false, 5);
|
||||
|
||||
TraverseLeaves(std::move(root), 5, 6, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastLeafOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(nodeStore->layout().maxChildrenPerInnerNode()-1).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode()-1);
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode()-1, nodeStore->layout().maxChildrenPerInnerNode(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseNothingInFullTwolevelTree1) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_DONT_TRAVERSE_ANY_LEAVES();
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 0, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseNothingInFullTwolevelTree2) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_DONT_TRAVERSE_ANY_LEAVES();
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode(), nodeStore->layout().maxChildrenPerInnerNode(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstLeafOfThreeLevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(0).blockId(), false, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddleLeafOfThreeLevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(5).blockId(), false, 5);
|
||||
|
||||
TraverseLeaves(std::move(root), 5, 6, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastLeafOfThreeLevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode());
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode(), nodeStore->layout().maxChildrenPerInnerNode()+1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseAllLeavesOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*root, true, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseAllLeavesOfThreelevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(0).blockId()), false, 0);
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode());
|
||||
|
||||
TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode()+1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstChildOfThreelevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(0).blockId()), false, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstPartOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
for (unsigned int i = 0; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), false, i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 5, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseInnerPartOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
for (unsigned int i = 5; i < 10; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), false, i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 5, 10, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastPartOfFullTwolevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
for (unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), i==nodeStore->layout().maxChildrenPerInnerNode()-1, i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 5, nodeStore->layout().maxChildrenPerInnerNode(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstPartOfThreelevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
auto node = LoadInnerNode(root->readChild(0).blockId());
|
||||
for (unsigned int i = 0; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 5, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseInnerPartOfThreelevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
auto node = LoadInnerNode(root->readChild(0).blockId());
|
||||
for (unsigned int i = 5; i < 10; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 5, 10, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastPartOfThreelevelMinDataTree) {
|
||||
auto root = CreateThreeLevelMinData();
|
||||
auto node = LoadInnerNode(root->readChild(0).blockId());
|
||||
for (unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i);
|
||||
}
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode());
|
||||
|
||||
TraverseLeaves(std::move(root), 5, nodeStore->layout().maxChildrenPerInnerNode()+1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstLeafOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(0).blockId(), false, 0);
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastLeafOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
uint32_t numLeaves = nodeStore->layout().maxChildrenPerInnerNode() * 5 + 3;
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readLastChild().blockId())->readLastChild().blockId(), true, numLeaves-1);
|
||||
|
||||
TraverseLeaves(std::move(root), numLeaves-1, numLeaves, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddleLeafOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
uint32_t wantedLeafIndex = nodeStore->layout().maxChildrenPerInnerNode() * 2 + 5;
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(2).blockId())->readChild(5).blockId(), false, wantedLeafIndex);
|
||||
|
||||
TraverseLeaves(std::move(root), wantedLeafIndex, wantedLeafIndex+1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseFirstPartOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
//Traverse all leaves in the first two children of the root
|
||||
for(unsigned int i = 0; i < 2; ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse some of the leaves in the third child of the root
|
||||
auto child = LoadInnerNode(root->readChild(2).blockId());
|
||||
for(unsigned int i = 0; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 2 * nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 2 * nodeStore->layout().maxChildrenPerInnerNode() + 5, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddlePartOfThreelevelTree_OnlyFullChildren) {
|
||||
auto root = CreateThreeLevel();
|
||||
//Traverse some of the leaves in the second child of the root
|
||||
auto child = LoadInnerNode(root->readChild(1).blockId());
|
||||
for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
//Traverse all leaves in the third and fourth child of the root
|
||||
for(unsigned int i = 2; i < 4; ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()),false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse some of the leaves in the fifth child of the root
|
||||
child = LoadInnerNode(root->readChild(4).blockId());
|
||||
for(unsigned int i = 0; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 4 * nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 4 * nodeStore->layout().maxChildrenPerInnerNode() + 5, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddlePartOfThreelevelTree_AlsoLastNonfullChild) {
|
||||
auto root = CreateThreeLevel();
|
||||
//Traverse some of the leaves in the second child of the root
|
||||
auto child = LoadInnerNode(root->readChild(1).blockId());
|
||||
for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
//Traverse all leaves in the third, fourth and fifth child of the root
|
||||
for(unsigned int i = 2; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse some of the leaves in the sixth child of the root
|
||||
child = LoadInnerNode(root->readChild(5).blockId());
|
||||
for(unsigned int i = 0; i < 2; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 5 * nodeStore->layout().maxChildrenPerInnerNode() + 2, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseLastPartOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
//Traverse some of the leaves in the second child of the root
|
||||
auto child = LoadInnerNode(root->readChild(1).blockId());
|
||||
for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
//Traverse all leaves in the third, fourth and fifth child of the root
|
||||
for(unsigned int i = 2; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse all of the leaves in the sixth child of the root
|
||||
child = LoadInnerNode(root->readChild(5).blockId());
|
||||
for(unsigned int i = 0; i < child->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), i == child->numChildren()-1, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 5 * nodeStore->layout().maxChildrenPerInnerNode() + child->numChildren(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseAllLeavesOfThreelevelTree) {
|
||||
auto root = CreateThreeLevel();
|
||||
//Traverse all leaves in the third, fourth and fifth child of the root
|
||||
for(unsigned int i = 0; i < 5; ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse all of the leaves in the sixth child of the root
|
||||
auto child = LoadInnerNode(root->readChild(5).blockId());
|
||||
for(unsigned int i = 0; i < child->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), i==child->numChildren()-1, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 5 * nodeStore->layout().maxChildrenPerInnerNode() + child->numChildren(), true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseAllLeavesOfFourLevelTree) {
|
||||
auto root = CreateFourLevel();
|
||||
//Traverse all leaves of the full threelevel tree in the first child
|
||||
auto firstChild = LoadInnerNode(root->readChild(0).blockId());
|
||||
for(unsigned int i = 0; i < firstChild->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(firstChild->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse all leaves of the full threelevel tree in the second child
|
||||
auto secondChild = LoadInnerNode(root->readChild(1).blockId());
|
||||
for(unsigned int i = 0; i < secondChild->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(secondChild->readChild(i).blockId()), false, (nodeStore->layout().maxChildrenPerInnerNode() + i) * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse all leaves of the non-full threelevel tree in the third child
|
||||
auto thirdChild = LoadInnerNode(root->readChild(2).blockId());
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(thirdChild->readChild(0).blockId()), false, 2 * nodeStore->layout().maxChildrenPerInnerNode() * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
EXPECT_TRAVERSE_LEAF(LoadInnerNode(thirdChild->readChild(1).blockId())->readChild(0).blockId(), true, 2 * nodeStore->layout().maxChildrenPerInnerNode() * nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode());
|
||||
|
||||
TraverseLeaves(std::move(root), 0, 2*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode() + 1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, TraverseMiddlePartOfFourLevelTree) {
|
||||
auto root = CreateFourLevel();
|
||||
//Traverse some leaves of the full threelevel tree in the first child
|
||||
auto firstChild = LoadInnerNode(root->readChild(0).blockId());
|
||||
auto secondChildOfFirstChild = LoadInnerNode(firstChild->readChild(1).blockId());
|
||||
for(unsigned int i = 5; i < secondChildOfFirstChild->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(secondChildOfFirstChild->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode()+i);
|
||||
}
|
||||
for(unsigned int i = 2; i < firstChild->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(firstChild->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse all leaves of the full threelevel tree in the second child
|
||||
auto secondChild = LoadInnerNode(root->readChild(1).blockId());
|
||||
for(unsigned int i = 0; i < secondChild->numChildren(); ++i) {
|
||||
EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(secondChild->readChild(i).blockId()), false, (nodeStore->layout().maxChildrenPerInnerNode() + i) * nodeStore->layout().maxChildrenPerInnerNode());
|
||||
}
|
||||
//Traverse some leaves of the non-full threelevel tree in the third child
|
||||
auto thirdChild = LoadInnerNode(root->readChild(2).blockId());
|
||||
auto firstChildOfThirdChild = LoadInnerNode(thirdChild->readChild(0).blockId());
|
||||
for(unsigned int i = 0; i < firstChildOfThirdChild->numChildren()-1; ++i) {
|
||||
EXPECT_TRAVERSE_LEAF(firstChildOfThirdChild->readChild(i).blockId(), false, 2 * nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode()+i);
|
||||
}
|
||||
|
||||
TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode()+5, 2*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode() -1, true);
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, LastLeafIsAlreadyResizedInCallback) {
|
||||
unique_ref<DataNode> root = CreateLeaf();
|
||||
root->flush();
|
||||
auto* old_root = root.get();
|
||||
auto tree = treeStore.load(root->blockId()).value();
|
||||
LeafTraverser(nodeStore, false).traverseAndUpdateRoot(&root, 0, 2, [this] (uint32_t leafIndex, bool /*isRightBorderNode*/, LeafHandle leaf) {
|
||||
if (leafIndex == 0) {
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf.node()->numBytes());
|
||||
} else {
|
||||
EXPECT_TRUE(false) << "only two nodes";
|
||||
}
|
||||
}, [] (uint32_t /*nodeIndex*/) -> Data {
|
||||
return Data(1);
|
||||
}, [] (auto) {});
|
||||
EXPECT_NE(old_root, root.get()); // expect that we grew the tree
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, LastLeafIsAlreadyResizedInCallback_TwoLevel) {
|
||||
unique_ref<DataNode> root = CreateFullTwoLevelWithLastLeafSize(5);
|
||||
root->flush();
|
||||
auto* old_root = root.get();
|
||||
auto tree = treeStore.load(root->blockId()).value();
|
||||
LeafTraverser(nodeStore, false).traverseAndUpdateRoot(&root, 0, nodeStore->layout().maxChildrenPerInnerNode()+1, [this] (uint32_t /*leafIndex*/, bool /*isRightBorderNode*/, LeafHandle leaf) {
|
||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf.node()->numBytes());
|
||||
}, [] (uint32_t /*nodeIndex*/) -> Data {
|
||||
return Data(1);
|
||||
}, [] (auto) {});
|
||||
EXPECT_NE(old_root, root.get()); // expect that we grew the tree
|
||||
}
|
||||
|
||||
TEST_F(LeafTraverserTest, ResizeFromOneLeafToMultipleLeaves) {
|
||||
auto root = CreateLeaf();
|
||||
EXPECT_TRAVERSE_LEAF(root->blockId(), false, 0);
|
||||
//EXPECT_CALL(traversor, calledExistingLeaf(_, false, 0)).Times(1);
|
||||
for (uint32_t i = 1; i < 10; ++i) {
|
||||
EXPECT_CREATE_LEAF(i);
|
||||
}
|
||||
TraverseLeaves(std::move(root), 0, 10, false);
|
||||
}
|
||||
|
||||
////TODO Refactor the test cases that are too long
|
@ -1,87 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "../testutils/DataTreeTest.h"
|
||||
#include "blobstore/implementations/onblocks/datatreestore/DataTree.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include "blobstore/implementations/onblocks/datatreestore/impl/algorithms.h"
|
||||
|
||||
|
||||
using blockstore::BlockId;
|
||||
using cpputils::Data;
|
||||
using namespace blobstore::onblocks::datatreestore::algorithms;
|
||||
|
||||
class GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest: public DataTreeTest {
|
||||
public:
|
||||
struct TestData {
|
||||
BlockId rootNode;
|
||||
BlockId expectedResult;
|
||||
};
|
||||
|
||||
void check(const TestData &testData) {
|
||||
auto root = nodeStore->load(testData.rootNode).value();
|
||||
auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get());
|
||||
EXPECT_EQ(testData.expectedResult, result->blockId());
|
||||
}
|
||||
|
||||
TestData CreateTwoRightBorderNodes() {
|
||||
auto node = CreateInner({CreateLeaf()});
|
||||
return TestData{node->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateThreeRightBorderNodes() {
|
||||
auto node = CreateInner({CreateLeaf()});
|
||||
auto root = CreateInner({node.get()});
|
||||
return TestData{root->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateThreeRightBorderNodes_LastFull() {
|
||||
auto root = CreateInner({CreateFullTwoLevel()});
|
||||
return TestData{root->blockId(), root->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateLargerTree() {
|
||||
auto node = CreateInner({CreateLeaf(), CreateLeaf()});
|
||||
auto root = CreateInner({CreateFullTwoLevel().get(), node.get()});
|
||||
return TestData{root->blockId(), node->blockId()};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, Leaf) {
|
||||
auto leaf = nodeStore->createNewLeafNode(Data(0));
|
||||
auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, leaf.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, TwoRightBorderNodes) {
|
||||
auto testData = CreateTwoRightBorderNodes();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, ThreeRightBorderNodes) {
|
||||
auto testData = CreateThreeRightBorderNodes();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, ThreeRightBorderNodes_LastFull) {
|
||||
auto testData = CreateThreeRightBorderNodes_LastFull();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, LargerTree) {
|
||||
auto testData = CreateLargerTree();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, FullTwoLevelTree) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
||||
|
||||
TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, FullThreeLevelTree) {
|
||||
auto root = CreateFullThreeLevel();
|
||||
auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "../testutils/DataTreeTest.h"
|
||||
#include "blobstore/implementations/onblocks/datatreestore/DataTree.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include "blobstore/implementations/onblocks/datatreestore/impl/algorithms.h"
|
||||
|
||||
|
||||
using blockstore::BlockId;
|
||||
using namespace blobstore::onblocks::datatreestore::algorithms;
|
||||
|
||||
class GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest: public DataTreeTest {
|
||||
public:
|
||||
struct TestData {
|
||||
BlockId rootNode;
|
||||
BlockId expectedResult;
|
||||
};
|
||||
|
||||
void check(const TestData &testData) {
|
||||
auto root = nodeStore->load(testData.rootNode).value();
|
||||
auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, root.get());
|
||||
EXPECT_EQ(testData.expectedResult, result->blockId());
|
||||
}
|
||||
|
||||
BlockId CreateLeafOnlyTree() {
|
||||
return CreateLeaf()->blockId();
|
||||
}
|
||||
|
||||
BlockId CreateTwoRightBorderNodes() {
|
||||
return CreateInner({CreateLeaf()})->blockId();
|
||||
}
|
||||
|
||||
BlockId CreateThreeRightBorderNodes() {
|
||||
return CreateInner({CreateInner({CreateLeaf()})})->blockId();
|
||||
}
|
||||
|
||||
TestData CreateThreeRightBorderNodes_LastFull() {
|
||||
auto node = CreateFullTwoLevel();
|
||||
auto root = CreateInner({node.get()});
|
||||
return TestData{root->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateLargerTree() {
|
||||
auto node = CreateInner({CreateLeaf(), CreateLeaf()});
|
||||
auto root = CreateInner({CreateFullTwoLevel().get(), node.get()});
|
||||
return TestData{root->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateThreeLevelTreeWithRightBorderSingleNodeChain() {
|
||||
auto root = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})});
|
||||
return TestData{root->blockId(), root->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateThreeLevelTree() {
|
||||
auto node = CreateInner({CreateLeaf(), CreateLeaf()});
|
||||
auto root = CreateInner({CreateFullTwoLevel().get(), node.get()});
|
||||
return TestData{root->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateFullTwoLevelTree() {
|
||||
auto node = CreateFullTwoLevel();
|
||||
return TestData{node->blockId(), node->blockId()};
|
||||
}
|
||||
|
||||
TestData CreateFullThreeLevelTree() {
|
||||
auto root = CreateFullThreeLevel();
|
||||
return TestData{root->blockId(), root->readLastChild().blockId()};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, Leaf) {
|
||||
auto leaf = nodeStore->load(CreateLeafOnlyTree()).value();
|
||||
auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, leaf.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, TwoRightBorderNodes) {
|
||||
auto node = nodeStore->load(CreateTwoRightBorderNodes()).value();
|
||||
auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, node.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeRightBorderNodes) {
|
||||
auto node = nodeStore->load(CreateThreeRightBorderNodes()).value();
|
||||
auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, node.get());
|
||||
EXPECT_EQ(nullptr, result.get());
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeRightBorderNodes_LastFull) {
|
||||
auto testData = CreateThreeRightBorderNodes_LastFull();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, LargerTree) {
|
||||
auto testData = CreateLargerTree();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, FullTwoLevelTree) {
|
||||
auto testData = CreateFullTwoLevelTree();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, FullThreeLevelTree) {
|
||||
auto testData = CreateFullThreeLevelTree();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeLevelTreeWithRightBorderSingleNodeChain) {
|
||||
auto testData = CreateThreeLevelTreeWithRightBorderSingleNodeChain();
|
||||
check(testData);
|
||||
}
|
||||
|
||||
TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeLevelTree) {
|
||||
auto testData = CreateThreeLevelTree();
|
||||
check(testData);
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
#include "DataTreeTest.h"
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include <cpp-utils/pointer/cast.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataNodeStore;
|
||||
using blobstore::onblocks::datanodestore::DataNode;
|
||||
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||
using blobstore::onblocks::datanodestore::DataLeafNode;
|
||||
using blobstore::onblocks::datatreestore::DataTree;
|
||||
using blockstore::mock::MockBlockStore;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using std::initializer_list;
|
||||
using std::vector;
|
||||
using boost::none;
|
||||
using cpputils::dynamic_pointer_move;
|
||||
using cpputils::Data;
|
||||
|
||||
constexpr uint32_t DataTreeTest::BLOCKSIZE_BYTES;
|
||||
|
||||
DataTreeTest::DataTreeTest()
|
||||
:_blockStore(make_unique_ref<MockBlockStore>()),
|
||||
blockStore(_blockStore.get()),
|
||||
_nodeStore(make_unique_ref<DataNodeStore>(std::move(_blockStore), BLOCKSIZE_BYTES)),
|
||||
nodeStore(_nodeStore.get()),
|
||||
treeStore(std::move(_nodeStore)) {
|
||||
}
|
||||
|
||||
unique_ref<DataLeafNode> DataTreeTest::CreateLeaf() {
|
||||
return nodeStore->createNewLeafNode(Data(nodeStore->layout().maxBytesPerLeaf()));
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateInner(initializer_list<unique_ref<DataNode>> children) {
|
||||
vector<const DataNode*> childrenVector(children.size());
|
||||
std::transform(children.begin(), children.end(), childrenVector.begin(), [](const unique_ref<DataNode> &ptr) {return ptr.get();});
|
||||
return CreateInner(childrenVector);
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateInner(initializer_list<const DataNode*> children) {
|
||||
return CreateInner(vector<const DataNode*>(children));
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateInner(vector<const DataNode*> children) {
|
||||
ASSERT(children.size() >= 1, "An inner node must have at least one child");
|
||||
vector<BlockId> childrenKeys;
|
||||
childrenKeys.reserve(children.size());
|
||||
for (const DataNode *child : children) {
|
||||
ASSERT(child->depth() == (*children.begin())->depth(), "Children with different depth");
|
||||
childrenKeys.push_back(child->blockId());
|
||||
}
|
||||
auto node = nodeStore->createNewInnerNode((*children.begin())->depth()+1, childrenKeys);
|
||||
return node;
|
||||
}
|
||||
|
||||
unique_ref<DataTree> DataTreeTest::CreateLeafOnlyTree() {
|
||||
auto blockId = CreateLeaf()->blockId();
|
||||
return treeStore.load(blockId).value();
|
||||
}
|
||||
|
||||
void DataTreeTest::FillNode(DataInnerNode *node) {
|
||||
for(unsigned int i=node->numChildren(); i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
node->addChild(*CreateLeaf());
|
||||
}
|
||||
}
|
||||
|
||||
void DataTreeTest::FillNodeTwoLevel(DataInnerNode *node) {
|
||||
for(unsigned int i=node->numChildren(); i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) {
|
||||
node->addChild(*CreateFullTwoLevel());
|
||||
}
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFullTwoLevel() {
|
||||
auto root = CreateInner({CreateLeaf().get()});
|
||||
FillNode(root.get());
|
||||
return root;
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateThreeLevelMinData() {
|
||||
return CreateInner({
|
||||
CreateFullTwoLevel(),
|
||||
CreateInner({CreateLeaf()})
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFourLevelMinData() {
|
||||
return CreateInner({
|
||||
CreateFullThreeLevel(),
|
||||
CreateInner({CreateInner({CreateLeaf()})})
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFullThreeLevel() {
|
||||
auto root = CreateInner({CreateFullTwoLevel().get()});
|
||||
FillNodeTwoLevel(root.get());
|
||||
return root;
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::LoadInnerNode(const BlockId &blockId) {
|
||||
auto node = nodeStore->load(blockId).value();
|
||||
auto casted = dynamic_pointer_move<DataInnerNode>(node);
|
||||
EXPECT_NE(none, casted) << "Is not an inner node";
|
||||
return std::move(*casted);
|
||||
}
|
||||
|
||||
unique_ref<DataLeafNode> DataTreeTest::LoadLeafNode(const BlockId &blockId) {
|
||||
auto node = nodeStore->load(blockId).value();
|
||||
auto casted = dynamic_pointer_move<DataLeafNode>(node);
|
||||
EXPECT_NE(none, casted) << "Is not a leaf node";
|
||||
return std::move(*casted);
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateTwoLeaf() {
|
||||
return CreateInner({CreateLeaf().get(), CreateLeaf().get()});
|
||||
}
|
||||
|
||||
unique_ref<DataTree> DataTreeTest::CreateTwoLeafTree() {
|
||||
auto blockId = CreateTwoLeaf()->blockId();
|
||||
return treeStore.load(blockId).value();
|
||||
}
|
||||
|
||||
unique_ref<DataLeafNode> DataTreeTest::CreateLeafWithSize(uint32_t size) {
|
||||
auto leaf = CreateLeaf();
|
||||
leaf->resize(size);
|
||||
return leaf;
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateTwoLeafWithSecondLeafSize(uint32_t size) {
|
||||
return CreateInner({
|
||||
CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateLeafWithSize(size)
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFullTwoLevelWithLastLeafSize(uint32_t size) {
|
||||
auto root = CreateFullTwoLevel();
|
||||
for (uint32_t i = 0; i < root->numChildren()-1; ++i) {
|
||||
LoadLeafNode(root->readChild(i).blockId())->resize(nodeStore->layout().maxBytesPerLeaf());
|
||||
}
|
||||
LoadLeafNode(root->readLastChild().blockId())->resize(size);
|
||||
return root;
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateThreeLevelWithOneChildAndLastLeafSize(uint32_t size) {
|
||||
return CreateInner({
|
||||
CreateInner({
|
||||
CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateLeafWithSize(size)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateThreeLevelWithTwoChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateInner({
|
||||
CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateInner({
|
||||
CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateLeafWithSize(size)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateThreeLevelWithThreeChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateInner({
|
||||
CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateInner({
|
||||
CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateLeafWithSize(size)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFullThreeLevelWithLastLeafSize(uint32_t size) {
|
||||
auto root = CreateFullThreeLevel();
|
||||
for (uint32_t i = 0; i < root->numChildren(); ++i) {
|
||||
auto node = LoadInnerNode(root->readChild(i).blockId());
|
||||
for (uint32_t j = 0; j < node->numChildren(); ++j) {
|
||||
LoadLeafNode(node->readChild(j).blockId())->resize(nodeStore->layout().maxBytesPerLeaf());
|
||||
}
|
||||
}
|
||||
LoadLeafNode(LoadInnerNode(root->readLastChild().blockId())->readLastChild().blockId())->resize(size);
|
||||
return root;
|
||||
}
|
||||
|
||||
unique_ref<DataInnerNode> DataTreeTest::CreateFourLevelMinDataWithLastLeafSize(uint32_t size) {
|
||||
return CreateInner({
|
||||
CreateFullThreeLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()),
|
||||
CreateInner({CreateInner({CreateLeafWithSize(size)})})
|
||||
});
|
||||
}
|
||||
|
||||
void DataTreeTest::EXPECT_IS_LEAF_NODE(const BlockId &blockId) {
|
||||
auto node = LoadLeafNode(blockId);
|
||||
EXPECT_NE(nullptr, node.get());
|
||||
}
|
||||
|
||||
void DataTreeTest::EXPECT_IS_INNER_NODE(const BlockId &blockId) {
|
||||
auto node = LoadInnerNode(blockId);
|
||||
EXPECT_NE(nullptr, node.get());
|
||||
}
|
||||
|
||||
void DataTreeTest::EXPECT_IS_TWONODE_CHAIN(const BlockId &blockId) {
|
||||
auto node = LoadInnerNode(blockId);
|
||||
EXPECT_EQ(1u, node->numChildren());
|
||||
EXPECT_IS_LEAF_NODE(node->readChild(0).blockId());
|
||||
}
|
||||
|
||||
void DataTreeTest::EXPECT_IS_FULL_TWOLEVEL_TREE(const BlockId &blockId) {
|
||||
auto node = LoadInnerNode(blockId);
|
||||
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), node->numChildren());
|
||||
for (unsigned int i = 0; i < node->numChildren(); ++i) {
|
||||
EXPECT_IS_LEAF_NODE(node->readChild(i).blockId());
|
||||
}
|
||||
}
|
||||
|
||||
void DataTreeTest::EXPECT_IS_FULL_THREELEVEL_TREE(const BlockId &blockId) {
|
||||
auto root = LoadInnerNode(blockId);
|
||||
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), root->numChildren());
|
||||
for (unsigned int i = 0; i < root->numChildren(); ++i) {
|
||||
auto node = LoadInnerNode(root->readChild(i).blockId());
|
||||
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), node->numChildren());
|
||||
for (unsigned int j = 0; j < node->numChildren(); ++j) {
|
||||
EXPECT_IS_LEAF_NODE(node->readChild(j).blockId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
void DataTreeTest::CHECK_DEPTH(int depth, const BlockId &blockId) {
|
||||
if (depth == 0) {
|
||||
EXPECT_IS_LEAF_NODE(blockId);
|
||||
} else {
|
||||
auto node = LoadInnerNode(blockId);
|
||||
EXPECT_EQ(depth, node->depth());
|
||||
for (uint32_t i = 0; i < node->numChildren(); ++i) {
|
||||
CHECK_DEPTH(depth-1, node->readChild(i).blockId());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREETEST_H_
|
||||
#define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREETEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h"
|
||||
#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h"
|
||||
#include "blobstore/implementations/onblocks/datatreestore/DataTree.h"
|
||||
#include "blobstore/implementations/onblocks/datatreestore/DataTreeStore.h"
|
||||
#include "blockstore/implementations/mock/MockBlockStore.h"
|
||||
|
||||
class DataTreeTest: public ::testing::Test {
|
||||
public:
|
||||
DataTreeTest();
|
||||
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 256;
|
||||
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataLeafNode> CreateLeaf();
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateInner(std::vector<const blobstore::onblocks::datanodestore::DataNode *> children);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateInner(std::initializer_list<const blobstore::onblocks::datanodestore::DataNode *> children);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateInner(std::initializer_list<cpputils::unique_ref<blobstore::onblocks::datanodestore::DataNode>> children);
|
||||
|
||||
cpputils::unique_ref<blobstore::onblocks::datatreestore::DataTree> CreateLeafOnlyTree();
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateTwoLeaf();
|
||||
cpputils::unique_ref<blobstore::onblocks::datatreestore::DataTree> CreateTwoLeafTree();
|
||||
void FillNode(blobstore::onblocks::datanodestore::DataInnerNode *node);
|
||||
void FillNodeTwoLevel(blobstore::onblocks::datanodestore::DataInnerNode *node);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFullTwoLevel();
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFullThreeLevel();
|
||||
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateThreeLevelMinData();
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFourLevelMinData();
|
||||
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> LoadInnerNode(const blockstore::BlockId &blockId);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataLeafNode> LoadLeafNode(const blockstore::BlockId &blockId);
|
||||
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataLeafNode> CreateLeafWithSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateTwoLeafWithSecondLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFullTwoLevelWithLastLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateThreeLevelWithOneChildAndLastLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateThreeLevelWithTwoChildrenAndLastLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateThreeLevelWithThreeChildrenAndLastLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFullThreeLevelWithLastLeafSize(uint32_t size);
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataInnerNode> CreateFourLevelMinDataWithLastLeafSize(uint32_t size);
|
||||
|
||||
cpputils::unique_ref<blockstore::mock::MockBlockStore> _blockStore;
|
||||
blockstore::mock::MockBlockStore *blockStore;
|
||||
cpputils::unique_ref<blobstore::onblocks::datanodestore::DataNodeStore> _nodeStore;
|
||||
blobstore::onblocks::datanodestore::DataNodeStore *nodeStore;
|
||||
blobstore::onblocks::datatreestore::DataTreeStore treeStore;
|
||||
|
||||
void EXPECT_IS_LEAF_NODE(const blockstore::BlockId &blockId);
|
||||
void EXPECT_IS_INNER_NODE(const blockstore::BlockId &blockId);
|
||||
void EXPECT_IS_TWONODE_CHAIN(const blockstore::BlockId &blockId);
|
||||
void EXPECT_IS_FULL_TWOLEVEL_TREE(const blockstore::BlockId &blockId);
|
||||
void EXPECT_IS_FULL_THREELEVEL_TREE(const blockstore::BlockId &blockId);
|
||||
|
||||
void CHECK_DEPTH(int depth, const blockstore::BlockId &blockId);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DataTreeTest);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_LEAFDATAFIXTURE_H_
|
||||
#define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_LEAFDATAFIXTURE_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
// A data fixture containing data for a leaf.
|
||||
// The class can fill this data into a given leaf
|
||||
// and check, whether the data stored in a given leaf is correct.
|
||||
class LeafDataFixture {
|
||||
public:
|
||||
LeafDataFixture(int size, int iv = 0): _data(cpputils::DataFixture::generate(size, iv)) {}
|
||||
|
||||
void FillInto(blobstore::onblocks::datanodestore::DataLeafNode *leaf) const {
|
||||
leaf->resize(_data.size());
|
||||
leaf->write(_data.data(), 0, _data.size());
|
||||
}
|
||||
|
||||
void EXPECT_DATA_CORRECT(const blobstore::onblocks::datanodestore::DataLeafNode &leaf, int onlyCheckNumBytes = -1) const {
|
||||
if (onlyCheckNumBytes == -1) {
|
||||
EXPECT_EQ(_data.size(), leaf.numBytes());
|
||||
EXPECT_EQ(0, std::memcmp(_data.data(), loadData(leaf).data(), _data.size()));
|
||||
} else {
|
||||
EXPECT_LE(onlyCheckNumBytes, static_cast<int>(leaf.numBytes()));
|
||||
EXPECT_EQ(0, std::memcmp(_data.data(), loadData(leaf).data(), onlyCheckNumBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static cpputils::Data loadData(const blobstore::onblocks::datanodestore::DataLeafNode &leaf) {
|
||||
cpputils::Data data(leaf.numBytes());
|
||||
leaf.read(data.data(), 0, leaf.numBytes());
|
||||
return data;
|
||||
}
|
||||
cpputils::Data _data;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
#define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
|
||||
#include <cpp-utils/macros.h>
|
||||
#include <cpp-utils/pointer/cast.h>
|
||||
#include "LeafDataFixture.h"
|
||||
#include <cpp-utils/assert/assert.h>
|
||||
|
||||
//TODO Rename, since we now allow any number of levels
|
||||
// A data fixture containing data for a two-level tree (one inner node with leaf children).
|
||||
// The class can fill this data into the leaf children of a given inner node
|
||||
// and given an inner node can check, whether the data stored is correct.
|
||||
class TwoLevelDataFixture {
|
||||
public:
|
||||
enum class SizePolicy {
|
||||
Random,
|
||||
Full,
|
||||
Unchanged
|
||||
};
|
||||
TwoLevelDataFixture(blobstore::onblocks::datanodestore::DataNodeStore *dataNodeStore, SizePolicy sizePolicy, int iv=0): _dataNodeStore(dataNodeStore), _iv(iv), _sizePolicy(sizePolicy) {}
|
||||
|
||||
void FillInto(blobstore::onblocks::datanodestore::DataNode *node) {
|
||||
// _iv-1 means there is no endLeafIndex - we fill all leaves.
|
||||
ForEachLeaf(node, _iv, _iv-1, [this] (blobstore::onblocks::datanodestore::DataLeafNode *leaf, int leafIndex) {
|
||||
LeafDataFixture(size(leafIndex, leaf), leafIndex).FillInto(leaf);
|
||||
});
|
||||
}
|
||||
|
||||
void EXPECT_DATA_CORRECT(blobstore::onblocks::datanodestore::DataNode *node, int maxCheckedLeaves = 0, int lastLeafMaxCheckedBytes = -1) {
|
||||
ForEachLeaf(node, _iv, _iv+maxCheckedLeaves, [this, maxCheckedLeaves, lastLeafMaxCheckedBytes] (blobstore::onblocks::datanodestore::DataLeafNode *leaf, int leafIndex) {
|
||||
if (leafIndex == _iv+maxCheckedLeaves-1) {
|
||||
// It is the last leaf
|
||||
LeafDataFixture(size(leafIndex, leaf), leafIndex).EXPECT_DATA_CORRECT(*leaf, lastLeafMaxCheckedBytes);
|
||||
} else {
|
||||
LeafDataFixture(size(leafIndex, leaf), leafIndex).EXPECT_DATA_CORRECT(*leaf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
int ForEachLeaf(blobstore::onblocks::datanodestore::DataNode *node, int firstLeafIndex, int endLeafIndex, std::function<void (blobstore::onblocks::datanodestore::DataLeafNode*, int)> action) {
|
||||
if (firstLeafIndex == endLeafIndex) {
|
||||
return firstLeafIndex;
|
||||
}
|
||||
auto leaf = dynamic_cast<blobstore::onblocks::datanodestore::DataLeafNode*>(node);
|
||||
if (leaf != nullptr) {
|
||||
action(leaf, firstLeafIndex);
|
||||
return firstLeafIndex + 1;
|
||||
} else {
|
||||
auto inner = dynamic_cast<blobstore::onblocks::datanodestore::DataInnerNode*>(node);
|
||||
int leafIndex = firstLeafIndex;
|
||||
for (uint32_t i = 0; i < inner->numChildren(); ++i) {
|
||||
auto child = _dataNodeStore->load(inner->readChild(i).blockId()).value();
|
||||
leafIndex = ForEachLeaf(child.get(), leafIndex, endLeafIndex, action);
|
||||
}
|
||||
return leafIndex;
|
||||
}
|
||||
}
|
||||
|
||||
blobstore::onblocks::datanodestore::DataNodeStore *_dataNodeStore;
|
||||
int _iv;
|
||||
SizePolicy _sizePolicy;
|
||||
|
||||
int size(int childIndex, blobstore::onblocks::datanodestore::DataLeafNode *leaf) {
|
||||
switch (_sizePolicy) {
|
||||
case SizePolicy::Full:
|
||||
return _dataNodeStore->layout().maxBytesPerLeaf();
|
||||
case SizePolicy::Random:
|
||||
return mod(static_cast<int>(_dataNodeStore->layout().maxBytesPerLeaf() - childIndex), static_cast<int>(_dataNodeStore->layout().maxBytesPerLeaf()));
|
||||
case SizePolicy::Unchanged:
|
||||
return leaf->numBytes();
|
||||
default:
|
||||
ASSERT(false, "Unknown size policy");
|
||||
}
|
||||
}
|
||||
|
||||
int mod(int value, int mod) {
|
||||
while(value < 0) {
|
||||
value += mod;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -1,15 +0,0 @@
|
||||
#include "BlobStoreTest.h"
|
||||
|
||||
#include <blockstore/implementations/testfake/FakeBlockStore.h>
|
||||
#include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h"
|
||||
#include <cpp-utils/pointer/gcc_4_8_compatibility.h>
|
||||
|
||||
using blobstore::onblocks::BlobStoreOnBlocks;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
constexpr uint32_t BlobStoreTest::BLOCKSIZE_BYTES;
|
||||
|
||||
BlobStoreTest::BlobStoreTest()
|
||||
: blobStore(make_unique_ref<BlobStoreOnBlocks>(make_unique_ref<FakeBlockStore>(), BLOCKSIZE_BYTES)) {
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_TESTUTILS_BLOBSTORETEST_H_
|
||||
#define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_TESTUTILS_BLOBSTORETEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "blobstore/interface/BlobStore.h"
|
||||
|
||||
class BlobStoreTest: public ::testing::Test {
|
||||
public:
|
||||
BlobStoreTest();
|
||||
|
||||
static constexpr uint32_t BLOCKSIZE_BYTES = 4096;
|
||||
|
||||
cpputils::unique_ref<blobstore::BlobStore> blobStore;
|
||||
|
||||
cpputils::unique_ref<blobstore::Blob> loadBlob(const blockstore::BlockId &blockId) {
|
||||
auto loaded = blobStore->load(blockId);
|
||||
EXPECT_TRUE(static_cast<bool>(loaded));
|
||||
return std::move(*loaded);
|
||||
}
|
||||
|
||||
void reset(cpputils::unique_ref<blobstore::Blob> ref) {
|
||||
UNUSED(ref);
|
||||
//ref is moved into here and then destructed
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -1,88 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace blobstore::onblocks::utils;
|
||||
using ::testing::Test;
|
||||
using std::numeric_limits;
|
||||
|
||||
class CeilDivisionTest: public Test {};
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide0_4) {
|
||||
EXPECT_EQ(0, ceilDivision(0, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide1_4) {
|
||||
EXPECT_EQ(1, ceilDivision(1, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide2_4) {
|
||||
EXPECT_EQ(1, ceilDivision(2, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide3_4) {
|
||||
EXPECT_EQ(1, ceilDivision(3, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide4_4) {
|
||||
EXPECT_EQ(1, ceilDivision(4, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide5_4) {
|
||||
EXPECT_EQ(2, ceilDivision(5, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide6_4) {
|
||||
EXPECT_EQ(2, ceilDivision(6, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide7_4) {
|
||||
EXPECT_EQ(2, ceilDivision(7, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide8_4) {
|
||||
EXPECT_EQ(2, ceilDivision(8, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide9_4) {
|
||||
EXPECT_EQ(3, ceilDivision(9, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide0_1) {
|
||||
EXPECT_EQ(0, ceilDivision(0, 1));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide5_1) {
|
||||
EXPECT_EQ(5, ceilDivision(5, 1));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide7_2) {
|
||||
EXPECT_EQ(4, ceilDivision(7, 2));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide8_2) {
|
||||
EXPECT_EQ(4, ceilDivision(8, 2));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide9_2) {
|
||||
EXPECT_EQ(5, ceilDivision(9, 2));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide1_1) {
|
||||
EXPECT_EQ(1, ceilDivision(1, 1));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, Divide5_5) {
|
||||
EXPECT_EQ(1, ceilDivision(5, 5));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, DivideLargeByItself) {
|
||||
EXPECT_EQ(1, ceilDivision(183495303, 183495303));
|
||||
}
|
||||
|
||||
TEST_F(CeilDivisionTest, 64bit) {
|
||||
uint64_t base = UINT64_C(1024)*1024*1024*1024;
|
||||
EXPECT_GT(base, std::numeric_limits<uint32_t>::max());
|
||||
EXPECT_EQ(base/1024, ceilDivision(base, UINT64_C(1024)));
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace blobstore::onblocks::utils;
|
||||
using ::testing::Test;
|
||||
using std::numeric_limits;
|
||||
|
||||
class CeilLogTest: public Test {};
|
||||
|
||||
TEST_F(CeilLogTest, Log3_1) {
|
||||
EXPECT_EQ(0, ceilLog(3, 1));
|
||||
}
|
||||
|
||||
TEST_F(CeilLogTest, Log3_2) {
|
||||
EXPECT_EQ(1, ceilLog(3, 2));
|
||||
}
|
||||
|
||||
TEST_F(CeilLogTest, Log3_3) {
|
||||
EXPECT_EQ(1, ceilLog(3, 3));
|
||||
}
|
||||
|
||||
TEST_F(CeilLogTest, Log3_4) {
|
||||
EXPECT_EQ(2, ceilLog(3, 4));
|
||||
}
|
||||
|
||||
TEST_F(CeilLogTest, 64bit) {
|
||||
uint64_t value = UINT64_C(1024)*1024*1024*1024;
|
||||
EXPECT_GT(value, std::numeric_limits<uint32_t>::max());
|
||||
EXPECT_EQ(4u, ceilLog(UINT64_C(1024), value));
|
||||
}
|
||||
|
||||
|
||||
//TODO ...
|
@ -1,77 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
|
||||
using namespace blobstore::onblocks::utils;
|
||||
using ::testing::Test;
|
||||
|
||||
class IntPowTest: public Test {};
|
||||
|
||||
TEST_F(IntPowTest, ExponentAndBaseAreZero) {
|
||||
EXPECT_EQ(1, intPow(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ExponentIsZero1) {
|
||||
EXPECT_EQ(1, intPow(1, 0));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ExponentIsZero2) {
|
||||
EXPECT_EQ(1, intPow(1000, 0));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsZero1) {
|
||||
EXPECT_EQ(0, intPow(0, 1));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsZero2) {
|
||||
EXPECT_EQ(0, intPow(0, 1000));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ExponentIsOne1) {
|
||||
EXPECT_EQ(0, intPow(0, 1));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ExponentIsOne2) {
|
||||
EXPECT_EQ(2, intPow(2, 1));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ExponentIsOne3) {
|
||||
EXPECT_EQ(1000, intPow(1000, 1));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsTwo1) {
|
||||
EXPECT_EQ(1024, intPow(2, 10));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsTwo2) {
|
||||
EXPECT_EQ(1024*1024, intPow(2, 20));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsTwo3) {
|
||||
EXPECT_EQ(1024*1024*1024, intPow(2, 30));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsTen1) {
|
||||
EXPECT_EQ(100, intPow(10, 2));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, BaseIsTen2) {
|
||||
EXPECT_EQ(1000000, intPow(10, 6));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ArbitraryNumbers1) {
|
||||
EXPECT_EQ(4096, intPow(4, 6));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ArbitraryNumbers2) {
|
||||
EXPECT_EQ(1296, intPow(6, 4));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, ArbitraryNumbers3) {
|
||||
EXPECT_EQ(282475249, intPow(7, 10));
|
||||
}
|
||||
|
||||
TEST_F(IntPowTest, 64bit) {
|
||||
uint64_t value = UINT64_C(1024)*1024*1024*1024;
|
||||
EXPECT_GT(value, std::numeric_limits<uint32_t>::max());
|
||||
EXPECT_EQ(value*value*value, intPow(value, UINT64_C(3)));
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blobstore/implementations/onblocks/utils/Math.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace blobstore::onblocks::utils;
|
||||
using ::testing::Test;
|
||||
using std::numeric_limits;
|
||||
|
||||
class MaxZeroSubtractionTest: public Test {};
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractToZero1) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractToZero2) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(5, 5));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractToZero3) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(184930, 184930));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractToZero4) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits<uint32_t>::max()-1, numeric_limits<uint32_t>::max()-1));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractToZero5) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits<uint32_t>::max(), numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractPositive1) {
|
||||
EXPECT_EQ(1, maxZeroSubtraction(5, 4));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractPositive2) {
|
||||
EXPECT_EQ(181081, maxZeroSubtraction(184930, 3849));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractPositive3) {
|
||||
EXPECT_EQ(numeric_limits<uint32_t>::max()-1, maxZeroSubtraction(numeric_limits<uint32_t>::max(), UINT32_C(1)));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractPositive4) {
|
||||
EXPECT_EQ(5u, maxZeroSubtraction(numeric_limits<uint32_t>::max(), numeric_limits<uint32_t>::max()-5));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractNegative1) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(4, 5));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractNegative2) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(3849, 184930));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractNegative3) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits<uint32_t>::max()-1, numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractNegative4) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits<uint32_t>::max()-5, numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractNegative5) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(UINT32_C(5), numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractFromZero1) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(0, 1));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractFromZero2) {
|
||||
EXPECT_EQ(0, maxZeroSubtraction(0, 184930));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, SubtractFromZero3) {
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(UINT32_C(0), numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, 64bit_valid) {
|
||||
uint64_t value = UINT64_C(1024)*1024*1024*1024;
|
||||
EXPECT_GT(value, std::numeric_limits<uint32_t>::max());
|
||||
EXPECT_EQ(value*1024-value, maxZeroSubtraction(value*1024, value));
|
||||
}
|
||||
|
||||
TEST_F(MaxZeroSubtractionTest, 64bit_zero) {
|
||||
uint64_t value = UINT64_C(1024)*1024*1024*1024;
|
||||
EXPECT_GT(value, std::numeric_limits<uint32_t>::max());
|
||||
EXPECT_EQ(0u, maxZeroSubtraction(value, value*1024));
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
project (blockstore-test)
|
||||
|
||||
set(SOURCES
|
||||
utils/BlockStoreUtilsTest.cpp
|
||||
interface/BlockStoreTest.cpp
|
||||
interface/BlockStore2Test.cpp
|
||||
interface/BlockTest.cpp
|
||||
implementations/testfake/TestFakeBlockStoreTest.cpp
|
||||
implementations/mock/MockBlockStoreTest.cpp
|
||||
implementations/inmemory/InMemoryBlockStoreTest.cpp
|
||||
implementations/parallelaccess/ParallelAccessBlockStoreTest_Generic.cpp
|
||||
implementations/parallelaccess/ParallelAccessBlockStoreTest_Specific.cpp
|
||||
implementations/compressing/CompressingBlockStoreTest.cpp
|
||||
implementations/compressing/compressors/testutils/CompressorTest.cpp
|
||||
implementations/encrypted/EncryptedBlockStoreTest_Generic.cpp
|
||||
implementations/encrypted/EncryptedBlockStoreTest_Specific.cpp
|
||||
implementations/ondisk/OnDiskBlockStoreTest_Generic.cpp
|
||||
implementations/ondisk/OnDiskBlockStoreTest_Specific.cpp
|
||||
implementations/ondisk/OnDiskBlockTest/OnDiskBlockCreateTest.cpp
|
||||
implementations/ondisk/OnDiskBlockTest/OnDiskBlockFlushTest.cpp
|
||||
implementations/ondisk/OnDiskBlockTest/OnDiskBlockLoadTest.cpp
|
||||
implementations/caching/CachingBlockStore2Test_Generic.cpp
|
||||
implementations/caching/CachingBlockStore2Test_Specific.cpp
|
||||
implementations/caching/cache/QueueMapTest_Values.cpp
|
||||
implementations/caching/cache/testutils/MinimalKeyType.cpp
|
||||
implementations/caching/cache/testutils/CopyableMovableValueType.cpp
|
||||
implementations/caching/cache/testutils/MinimalValueType.cpp
|
||||
implementations/caching/cache/testutils/QueueMapTest.cpp
|
||||
implementations/caching/cache/testutils/CacheTest.cpp
|
||||
implementations/caching/cache/QueueMapTest_Size.cpp
|
||||
implementations/caching/cache/CacheTest_MoveConstructor.cpp
|
||||
implementations/caching/cache/CacheTest_PushAndPop.cpp
|
||||
implementations/caching/cache/QueueMapTest_MoveConstructor.cpp
|
||||
implementations/caching/cache/QueueMapTest_MemoryLeak.cpp
|
||||
implementations/caching/cache/CacheTest_RaceCondition.cpp
|
||||
implementations/caching/cache/PeriodicTaskTest.cpp
|
||||
implementations/caching/cache/QueueMapTest_Peek.cpp
|
||||
implementations/integrity/KnownBlockVersionsTest.cpp
|
||||
implementations/integrity/IntegrityBlockStoreTest_Generic.cpp
|
||||
implementations/integrity/IntegrityBlockStoreTest_Specific.cpp
|
||||
implementations/low2highlevel/LowToHighLevelBlockStoreTest.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blockstore)
|
||||
add_test(${PROJECT_NAME} ${PROJECT_NAME})
|
||||
|
||||
target_enable_style_warnings(${PROJECT_NAME})
|
||||
target_activate_cpp14(${PROJECT_NAME})
|
@ -1,37 +0,0 @@
|
||||
#include <blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h>
|
||||
#include "blockstore/implementations/caching/CachingBlockStore2.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "../../testutils/BlockStore2Test.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::BlockStore2;
|
||||
using blockstore::caching::CachingBlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class CachingBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(
|
||||
make_unique_ref<CachingBlockStore2>(make_unique_ref<InMemoryBlockStore2>())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Caching2, BlockStoreTest, CachingBlockStoreTestFixture);
|
||||
|
||||
|
||||
class CachingBlockStore2TestFixture: public BlockStore2TestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore2> createBlockStore() override {
|
||||
return make_unique_ref<CachingBlockStore2>(make_unique_ref<InMemoryBlockStore2>());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Caching, BlockStore2Test, CachingBlockStore2TestFixture);
|
@ -1,54 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/caching/CachingBlockStore2.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::Data;
|
||||
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using namespace blockstore::caching;
|
||||
|
||||
class CachingBlockStore2Test: public Test {
|
||||
public:
|
||||
CachingBlockStore2Test():
|
||||
baseBlockStore(new InMemoryBlockStore2),
|
||||
blockStore(std::move(cpputils::nullcheck(std::unique_ptr<InMemoryBlockStore2>(baseBlockStore)).value())) {
|
||||
}
|
||||
InMemoryBlockStore2 *baseBlockStore;
|
||||
CachingBlockStore2 blockStore;
|
||||
};
|
||||
|
||||
TEST_F(CachingBlockStore2Test, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
||||
TEST_F(CachingBlockStore2Test, PhysicalBlockSize_zerovirtual) {
|
||||
auto blockId = blockStore.create(Data(0));
|
||||
blockStore.flush();
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
||||
|
||||
TEST_F(CachingBlockStore2Test, PhysicalBlockSize_negativeboundaries) {
|
||||
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
|
||||
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
|
||||
auto blockId = blockStore.create(Data(0));
|
||||
blockStore.flush();
|
||||
auto physicalSizeForVirtualSizeZero = baseBlockStore->load(blockId).value().size();
|
||||
if (physicalSizeForVirtualSizeZero > 0) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1));
|
||||
}
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero));
|
||||
EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1));
|
||||
}
|
||||
|
||||
TEST_F(CachingBlockStore2Test, PhysicalBlockSize_positive) {
|
||||
auto blockId = blockStore.create(Data(10*1024u));
|
||||
blockStore.flush();
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
||||
|
||||
// TODO Add test cases that flushing the block store doesn't destroy things (i.e. all test cases from BlockStoreTest, but with flushes inbetween)
|
@ -1,35 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref.h>
|
||||
#include "blockstore/implementations/caching/cache/Cache.h"
|
||||
#include "testutils/MinimalKeyType.h"
|
||||
#include "testutils/CopyableMovableValueType.h"
|
||||
|
||||
using namespace blockstore::caching;
|
||||
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using ::testing::Test;
|
||||
|
||||
//Test that Cache uses a move constructor for Value if possible
|
||||
class CacheTest_MoveConstructor: public Test {
|
||||
public:
|
||||
CacheTest_MoveConstructor(): cache(make_unique_ref<Cache<MinimalKeyType, CopyableMovableValueType, 100>>("test")) {
|
||||
CopyableMovableValueType::numCopyConstructorCalled = 0;
|
||||
}
|
||||
unique_ref<Cache<MinimalKeyType, CopyableMovableValueType, 100>> cache;
|
||||
};
|
||||
|
||||
TEST_F(CacheTest_MoveConstructor, MoveIntoCache) {
|
||||
cache->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
|
||||
CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_MoveConstructor, CopyIntoCache) {
|
||||
CopyableMovableValueType value(2);
|
||||
cache->push(MinimalKeyType::create(0), value);
|
||||
CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
#include "testutils/CacheTest.h"
|
||||
|
||||
#include "blockstore/implementations/caching/cache/Cache.h"
|
||||
#include "testutils/MinimalKeyType.h"
|
||||
#include "testutils/MinimalValueType.h"
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
|
||||
using namespace blockstore::caching;
|
||||
|
||||
class CacheTest_PushAndPop: public CacheTest {};
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_EmptyCache) {
|
||||
EXPECT_EQ(boost::none, pop(10));
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_NonEmptyCache) {
|
||||
push(9, 10);
|
||||
EXPECT_EQ(boost::none, pop(10));
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_FullCache) {
|
||||
//Add a lot of even numbered keys
|
||||
for (int i = 0; i < static_cast<int>(MAX_ENTRIES); ++i) {
|
||||
push(2*i, 2*i);
|
||||
}
|
||||
//Request an odd numbered key
|
||||
EXPECT_EQ(boost::none, pop(9));
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, OneEntry) {
|
||||
push(10, 20);
|
||||
EXPECT_EQ(20, pop(10).value());
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, MultipleEntries) {
|
||||
push(10, 20);
|
||||
push(20, 30);
|
||||
push(30, 40);
|
||||
EXPECT_EQ(30, pop(20).value());
|
||||
EXPECT_EQ(20, pop(10).value());
|
||||
EXPECT_EQ(40, pop(30).value());
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, FullCache) {
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); ++i) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); ++i) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopOrdered) {
|
||||
for(int i = 1; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); ++i) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, FullCache_PushOrdered_PopNonOrdered) {
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); ++i) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = 1; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
}
|
||||
|
||||
int roundDownToEven(int number) {
|
||||
if (number % 2 == 0) {
|
||||
return number;
|
||||
} else {
|
||||
return number - 1;
|
||||
}
|
||||
}
|
||||
|
||||
int roundDownToOdd(int number) {
|
||||
if (number % 2 != 0) {
|
||||
return number;
|
||||
} else {
|
||||
return number - 1;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopNonOrdered) {
|
||||
for(int i = roundDownToEven(MAX_ENTRIES - 1); i >= 0; i -= 2) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = 1; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
for(int i = roundDownToOdd(MAX_ENTRIES-1); i >= 0; i -= 2) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES); i += 2) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, MoreThanFullCache) {
|
||||
for(int i = 0; i < static_cast<int>(MAX_ENTRIES + 2); ++i) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
//Check that the oldest two elements got deleted automatically
|
||||
EXPECT_EQ(boost::none, pop(0));
|
||||
EXPECT_EQ(boost::none, pop(1));
|
||||
//Check the other elements are still there
|
||||
for(int i = 2; i < static_cast<int>(MAX_ENTRIES + 2); ++i) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_PushAndPop, AfterTimeout) {
|
||||
constexpr double TIMEOUT1_SEC = Cache::MAX_LIFETIME_SEC * 3/4;
|
||||
constexpr double TIMEOUT2_SEC = Cache::PURGE_LIFETIME_SEC * 3/4;
|
||||
static_assert(TIMEOUT1_SEC + TIMEOUT2_SEC > Cache::MAX_LIFETIME_SEC, "Ensure that our chosen timeouts push the first entry out of the cache");
|
||||
|
||||
push(10, 20);
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast<int>(1000 * TIMEOUT1_SEC)));
|
||||
push(20, 30);
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast<int>(1000 * TIMEOUT2_SEC)));
|
||||
EXPECT_EQ(boost::none, pop(10));
|
||||
EXPECT_EQ(30, pop(20).value());
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
#include "testutils/CacheTest.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
#include <cpp-utils/lock/ConditionBarrier.h>
|
||||
|
||||
using namespace blockstore::caching;
|
||||
using std::chrono::seconds;
|
||||
using std::string;
|
||||
using cpputils::ConditionBarrier;
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
using std::future;
|
||||
|
||||
// Regression tests for a race condition.
|
||||
// An element could be in the process of being thrown out of the cache and while the destructor is running, another
|
||||
// thread calls pop() for the element and gets none returned. But since the destructor isn't finished yet, the data from
|
||||
// the cache element also isn't completely written back yet and an application loading it runs into a race condition.
|
||||
|
||||
class ObjectWithLongDestructor {
|
||||
public:
|
||||
ObjectWithLongDestructor(ConditionBarrier *onDestructorStarted, std::atomic<bool> *destructorFinished)
|
||||
: _onDestructorStarted(onDestructorStarted), _destructorFinished(destructorFinished) {}
|
||||
~ObjectWithLongDestructor() {
|
||||
_onDestructorStarted->release();
|
||||
std::this_thread::sleep_for(seconds(1));
|
||||
*_destructorFinished = true;
|
||||
}
|
||||
private:
|
||||
ConditionBarrier *_onDestructorStarted;
|
||||
std::atomic<bool> *_destructorFinished;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ObjectWithLongDestructor);
|
||||
};
|
||||
|
||||
class CacheTest_RaceCondition: public ::testing::Test {
|
||||
public:
|
||||
CacheTest_RaceCondition(): cache("test"), destructorStarted(), destructorFinished(false) {}
|
||||
|
||||
static constexpr unsigned int MAX_ENTRIES = 100;
|
||||
|
||||
Cache<int, unique_ptr<ObjectWithLongDestructor>, MAX_ENTRIES> cache;
|
||||
ConditionBarrier destructorStarted;
|
||||
std::atomic<bool> destructorFinished;
|
||||
|
||||
int pushObjectWithLongDestructor() {
|
||||
cache.push(2, make_unique<ObjectWithLongDestructor>(&destructorStarted, &destructorFinished));
|
||||
return 2;
|
||||
}
|
||||
|
||||
int pushDummyObject() {
|
||||
cache.push(3, nullptr);
|
||||
return 3;
|
||||
}
|
||||
|
||||
future<void> causeCacheOverflowInOtherThread() {
|
||||
//Add maximum+1 element in another thread (this causes the cache to flush the first element in another thread)
|
||||
return std::async(std::launch::async, [this] {
|
||||
for(unsigned int i = 0; i < MAX_ENTRIES+1; ++i) {
|
||||
cache.push(MAX_ENTRIES+i, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(int key) {
|
||||
EXPECT_FALSE(destructorFinished);
|
||||
cache.pop(key);
|
||||
EXPECT_TRUE(destructorFinished);
|
||||
}
|
||||
|
||||
void EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(int key) {
|
||||
EXPECT_FALSE(destructorFinished);
|
||||
cache.pop(key);
|
||||
EXPECT_FALSE(destructorFinished);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByAge) {
|
||||
auto id = pushObjectWithLongDestructor();
|
||||
|
||||
destructorStarted.wait();
|
||||
EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByAge) {
|
||||
pushObjectWithLongDestructor();
|
||||
auto id = pushDummyObject();
|
||||
|
||||
destructorStarted.wait();
|
||||
EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByPush) {
|
||||
auto id = pushObjectWithLongDestructor();
|
||||
|
||||
auto future = causeCacheOverflowInOtherThread();
|
||||
destructorStarted.wait();
|
||||
EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByPush) {
|
||||
pushObjectWithLongDestructor();
|
||||
auto id = pushDummyObject();
|
||||
|
||||
auto future = causeCacheOverflowInOtherThread();
|
||||
destructorStarted.wait();
|
||||
EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id);
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "blockstore/implementations/caching/cache/PeriodicTask.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
using ::testing::Test;
|
||||
using std::mutex;
|
||||
using std::unique_lock;
|
||||
using std::condition_variable;
|
||||
|
||||
using namespace blockstore::caching;
|
||||
|
||||
class AtomicCounter {
|
||||
public:
|
||||
AtomicCounter(int count): _mutex(), _cv(), _counter(count) {}
|
||||
|
||||
void decrease() {
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
--_counter;
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
void waitForZero() {
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
_cv.wait(lock, [this] () {return _counter <= 0;});
|
||||
}
|
||||
private:
|
||||
mutex _mutex;
|
||||
condition_variable _cv;
|
||||
int _counter;
|
||||
};
|
||||
|
||||
class PeriodicTaskTest: public Test {
|
||||
};
|
||||
|
||||
TEST_F(PeriodicTaskTest, DoesntDeadlockInDestructorWhenDestructedImmediately) {
|
||||
PeriodicTask task([](){}, 1, "test");
|
||||
}
|
||||
|
||||
TEST_F(PeriodicTaskTest, CallsCallbackAtLeast10Times) {
|
||||
AtomicCounter counter(10);
|
||||
|
||||
PeriodicTask task([&counter](){
|
||||
counter.decrease();
|
||||
}, 0.001, "test");
|
||||
|
||||
counter.waitForZero();
|
||||
}
|
||||
|
||||
TEST_F(PeriodicTaskTest, DoesntCallCallbackAfterDestruction) {
|
||||
std::atomic<int> callCount(0);
|
||||
{
|
||||
PeriodicTask task([&callCount](){
|
||||
callCount += 1;
|
||||
}, 0.001, "test");
|
||||
}
|
||||
int callCountDirectlyAfterDestruction = callCount;
|
||||
boost::this_thread::sleep_for(boost::chrono::seconds(1));
|
||||
EXPECT_EQ(callCountDirectlyAfterDestruction, callCount);
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
#include "testutils/QueueMapTest.h"
|
||||
|
||||
// Tests that QueueMap calls destructors correctly.
|
||||
// This is needed, because QueueMap does its own memory management.
|
||||
class QueueMapTest_MemoryLeak: public QueueMapTest {
|
||||
public:
|
||||
void EXPECT_NUM_INSTANCES(int num) {
|
||||
EXPECT_EQ(num, MinimalKeyType::instances);
|
||||
EXPECT_EQ(num, MinimalValueType::instances);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, Empty) {
|
||||
EXPECT_NUM_INSTANCES(0);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOne) {
|
||||
push(2, 3);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwo) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
EXPECT_NUM_INSTANCES(2);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingOldest) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop();
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingFirst) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(2);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingLast) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(3);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
EXPECT_NUM_INSTANCES(0);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKey) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
EXPECT_NUM_INSTANCES(0);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
push(3, 4);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingOne) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
push(3, 4);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingSame) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
push(2, 3);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingSame) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
push(2, 3);
|
||||
EXPECT_NUM_INSTANCES(1);
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref.h>
|
||||
#include "blockstore/implementations/caching/cache/QueueMap.h"
|
||||
#include "testutils/MinimalKeyType.h"
|
||||
#include "testutils/CopyableMovableValueType.h"
|
||||
|
||||
using namespace blockstore::caching;
|
||||
|
||||
using ::testing::Test;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
//Test that QueueMap uses a move constructor for Value if possible
|
||||
class QueueMapTest_MoveConstructor: public Test {
|
||||
public:
|
||||
QueueMapTest_MoveConstructor(): map(make_unique_ref<QueueMap<MinimalKeyType, CopyableMovableValueType>>()) {
|
||||
CopyableMovableValueType::numCopyConstructorCalled = 0;
|
||||
}
|
||||
unique_ref<QueueMap<MinimalKeyType, CopyableMovableValueType>> map;
|
||||
};
|
||||
|
||||
TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_MoveIntoMap) {
|
||||
map->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
|
||||
CopyableMovableValueType val = map->pop().value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_MoveIntoMap) {
|
||||
map->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
|
||||
CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_CopyIntoMap) {
|
||||
CopyableMovableValueType value(2);
|
||||
map->push(MinimalKeyType::create(0), value);
|
||||
CopyableMovableValueType val = map->pop().value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_CopyIntoMap) {
|
||||
CopyableMovableValueType value(2);
|
||||
map->push(MinimalKeyType::create(0), value);
|
||||
CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value();
|
||||
val.value(); //Access it to avoid the compiler optimizing the assignment away
|
||||
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#include "testutils/QueueMapTest.h"
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
|
||||
class QueueMapPeekTest: public QueueMapTest {};
|
||||
|
||||
TEST_F(QueueMapPeekTest, PoppingFromEmpty) {
|
||||
EXPECT_EQ(boost::none, peek());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapPeekTest, PushingOne) {
|
||||
push(3, 2);
|
||||
EXPECT_EQ(2, peek().value());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapPeekTest, PushingTwo) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
EXPECT_EQ(3, peek().value());
|
||||
EXPECT_EQ(3, peek().value());
|
||||
EXPECT_EQ(3, pop().value());
|
||||
EXPECT_EQ(4, peek().value());
|
||||
EXPECT_EQ(4, peek().value());
|
||||
EXPECT_EQ(4, pop().value());
|
||||
EXPECT_EQ(boost::none, peek());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapPeekTest, AfterPushingTwoAndPoppingFirst) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(2);
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
EXPECT_EQ(4, peek().value());
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
#include "testutils/QueueMapTest.h"
|
||||
|
||||
class QueueMapTest_Size: public QueueMapTest {};
|
||||
|
||||
TEST_F(QueueMapTest_Size, Empty) {
|
||||
EXPECT_EQ(0, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOne) {
|
||||
push(2, 3);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingTwo) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
EXPECT_EQ(2, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingOldest) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop();
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingFirst) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(2);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingLast) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(3);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
EXPECT_EQ(0, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKey) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
EXPECT_EQ(0, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
push(3, 4);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingOne) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
push(3, 4);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingSame) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
push(2, 3);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingSame) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
push(2, 3);
|
||||
EXPECT_EQ(1, size());
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
#include "testutils/QueueMapTest.h"
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
class QueueMapTest_Values: public QueueMapTest {};
|
||||
|
||||
TEST_F(QueueMapTest_Values, PoppingFromEmpty) {
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PoppingFromEmptyPerKey) {
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PoppingNonexistingPerKey) {
|
||||
push(3, 2);
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushingOne) {
|
||||
push(3, 2);
|
||||
EXPECT_EQ(2, pop(3).value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushingTwo) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
EXPECT_EQ(3, pop().value());
|
||||
EXPECT_EQ(4, pop().value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingFirst) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(2);
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
EXPECT_EQ(4, pop(3).value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingLast) {
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
pop(3);
|
||||
EXPECT_EQ(boost::none, pop(3));
|
||||
EXPECT_EQ(3, pop(2).value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKey) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePushingOne) {
|
||||
push(2, 3);
|
||||
pop();
|
||||
push(3, 4);
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
EXPECT_EQ(4, pop(3).value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKeyPushingOne) {
|
||||
push(2, 3);
|
||||
pop(2);
|
||||
push(3, 4);
|
||||
EXPECT_EQ(boost::none, pop(2));
|
||||
EXPECT_EQ(4, pop(3).value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushingSomePoppingMiddlePerKey) {
|
||||
push(1, 2);
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
push(4, 5);
|
||||
push(5, 6);
|
||||
EXPECT_EQ(3, pop(2).value());
|
||||
EXPECT_EQ(5, pop(4).value());
|
||||
EXPECT_EQ(2, pop().value());
|
||||
EXPECT_EQ(4, pop().value());
|
||||
EXPECT_EQ(6, pop().value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushingSomePoppingFirstPerKey) {
|
||||
push(1, 2);
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
push(4, 5);
|
||||
push(5, 6);
|
||||
EXPECT_EQ(2, pop(1).value());
|
||||
EXPECT_EQ(3, pop(2).value());
|
||||
EXPECT_EQ(4, pop().value());
|
||||
EXPECT_EQ(5, pop().value());
|
||||
EXPECT_EQ(6, pop().value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushingSomePoppingLastPerKey) {
|
||||
push(1, 2);
|
||||
push(2, 3);
|
||||
push(3, 4);
|
||||
push(4, 5);
|
||||
push(5, 6);
|
||||
EXPECT_EQ(6, pop(5).value());
|
||||
EXPECT_EQ(5, pop(4).value());
|
||||
EXPECT_EQ(2, pop().value());
|
||||
EXPECT_EQ(3, pop().value());
|
||||
EXPECT_EQ(4, pop().value());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
}
|
||||
|
||||
//This test forces the underlying datastructure (std::map or std::unordered_map) to grow and reallocate memory.
|
||||
//So it tests, that QueueMap still works after reallocating memory.
|
||||
TEST_F(QueueMapTest_Values, ManyValues) {
|
||||
//Push 1 million entries
|
||||
for (int i = 0; i < 1000000; ++i) {
|
||||
push(i, 2*i);
|
||||
}
|
||||
//pop every other one by key
|
||||
for (int i = 0; i < 1000000; i += 2) {
|
||||
EXPECT_EQ(2*i, pop(i).value());
|
||||
}
|
||||
//pop the rest in queue order
|
||||
for (int i = 1; i < 1000000; i += 2) {
|
||||
EXPECT_EQ(2*i, peek().value());
|
||||
EXPECT_EQ(2*i, pop().value());
|
||||
}
|
||||
EXPECT_EQ(0, size());
|
||||
EXPECT_EQ(boost::none, pop());
|
||||
EXPECT_EQ(boost::none, peek());
|
||||
}
|
||||
|
||||
TEST_F(QueueMapTest_Values, PushAlreadyExistingValue) {
|
||||
push(2, 3);
|
||||
EXPECT_ANY_THROW(
|
||||
push(2, 4);
|
||||
);
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#include "CacheTest.h"
|
||||
|
||||
void CacheTest::push(int key, int value) {
|
||||
return _cache.push(MinimalKeyType::create(key), MinimalValueType::create(value));
|
||||
}
|
||||
|
||||
boost::optional<int> CacheTest::pop(int key) {
|
||||
boost::optional<MinimalValueType> entry = _cache.pop(MinimalKeyType::create(key));
|
||||
if (!entry) {
|
||||
return boost::none;
|
||||
}
|
||||
return entry->value();
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/caching/cache/Cache.h"
|
||||
#include "MinimalKeyType.h"
|
||||
#include "MinimalValueType.h"
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
// This class is a parent class for tests on QueueMap.
|
||||
// It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements.
|
||||
// Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values).
|
||||
class CacheTest: public ::testing::Test {
|
||||
public:
|
||||
CacheTest(): _cache("test") {}
|
||||
|
||||
void push(int key, int value);
|
||||
boost::optional<int> pop(int key);
|
||||
|
||||
static constexpr unsigned int MAX_ENTRIES = 100;
|
||||
|
||||
using Cache = blockstore::caching::Cache<MinimalKeyType, MinimalValueType, MAX_ENTRIES>;
|
||||
|
||||
private:
|
||||
Cache _cache;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,3 +0,0 @@
|
||||
#include "CopyableMovableValueType.h"
|
||||
|
||||
int CopyableMovableValueType::numCopyConstructorCalled = 0;
|
@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_
|
||||
|
||||
class CopyableMovableValueType {
|
||||
public:
|
||||
static int numCopyConstructorCalled;
|
||||
CopyableMovableValueType(int value): _value(value) {}
|
||||
CopyableMovableValueType(const CopyableMovableValueType &rhs): CopyableMovableValueType(rhs._value) {
|
||||
++numCopyConstructorCalled;
|
||||
}
|
||||
// NOLINTNEXTLINE(cert-oop54-cpp)
|
||||
CopyableMovableValueType &operator=(const CopyableMovableValueType &rhs) {
|
||||
_value = rhs._value;
|
||||
++numCopyConstructorCalled;
|
||||
return *this;
|
||||
}
|
||||
CopyableMovableValueType(CopyableMovableValueType &&rhs) noexcept: CopyableMovableValueType(rhs._value) {
|
||||
//Don't increase numCopyConstructorCalled
|
||||
}
|
||||
CopyableMovableValueType &operator=(CopyableMovableValueType &&rhs) noexcept {
|
||||
//Don't increase numCopyConstructorCalled
|
||||
_value = rhs._value;
|
||||
return *this;
|
||||
}
|
||||
int value() const {
|
||||
return _value;
|
||||
}
|
||||
private:
|
||||
int _value;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,3 +0,0 @@
|
||||
#include "MinimalKeyType.h"
|
||||
|
||||
std::atomic<int> MinimalKeyType::instances(0);
|
@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
|
||||
// This is a not-default-constructible Key type
|
||||
class MinimalKeyType {
|
||||
public:
|
||||
static std::atomic<int> instances;
|
||||
|
||||
static MinimalKeyType create(int value) {
|
||||
return MinimalKeyType(value);
|
||||
}
|
||||
|
||||
MinimalKeyType(const MinimalKeyType &rhs): MinimalKeyType(rhs.value()) {
|
||||
}
|
||||
|
||||
~MinimalKeyType() {
|
||||
--instances;
|
||||
}
|
||||
|
||||
int value() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
private:
|
||||
MinimalKeyType(int value): _value(value) {
|
||||
++instances;
|
||||
}
|
||||
|
||||
int _value;
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<MinimalKeyType> {
|
||||
size_t operator()(const MinimalKeyType &obj) const {
|
||||
return obj.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline bool operator==(const MinimalKeyType &lhs, const MinimalKeyType &rhs) {
|
||||
return lhs.value() == rhs.value();
|
||||
}
|
||||
|
||||
#endif
|
@ -1,3 +0,0 @@
|
||||
#include "MinimalValueType.h"
|
||||
|
||||
std::atomic<int> MinimalValueType::instances(0);
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_
|
||||
|
||||
#include <cpp-utils/macros.h>
|
||||
#include <cassert>
|
||||
#include <cpp-utils/assert/assert.h>
|
||||
#include <atomic>
|
||||
|
||||
// This is a not-default-constructible non-copyable but moveable Value type
|
||||
class MinimalValueType {
|
||||
public:
|
||||
static std::atomic<int> instances;
|
||||
|
||||
static MinimalValueType create(int value) {
|
||||
return MinimalValueType(value);
|
||||
}
|
||||
|
||||
MinimalValueType(MinimalValueType &&rhs) noexcept: MinimalValueType(rhs.value()) {
|
||||
rhs._isMoved = true;
|
||||
}
|
||||
|
||||
MinimalValueType &operator=(MinimalValueType &&rhs) noexcept {
|
||||
_value = rhs.value();
|
||||
_isMoved = false;
|
||||
rhs._isMoved = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MinimalValueType() {
|
||||
ASSERT(!_isDestructed, "Object was already destructed before");
|
||||
--instances;
|
||||
_isDestructed = true;
|
||||
}
|
||||
|
||||
int value() const {
|
||||
ASSERT(!_isMoved && !_isDestructed, "Object is invalid");
|
||||
return _value;
|
||||
}
|
||||
|
||||
private:
|
||||
MinimalValueType(int value): _value(value), _isMoved(false), _isDestructed(false) {
|
||||
++instances;
|
||||
}
|
||||
|
||||
int _value;
|
||||
bool _isMoved;
|
||||
bool _isDestructed;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MinimalValueType);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,44 +0,0 @@
|
||||
#include "QueueMapTest.h"
|
||||
|
||||
QueueMapTest::QueueMapTest(): _map(cpputils::make_unique_ref<blockstore::caching::QueueMap<MinimalKeyType, MinimalValueType>>()) {
|
||||
MinimalKeyType::instances = 0;
|
||||
MinimalValueType::instances = 0;
|
||||
}
|
||||
|
||||
QueueMapTest::~QueueMapTest() {
|
||||
cpputils::destruct(std::move(_map));
|
||||
EXPECT_EQ(0, MinimalKeyType::instances);
|
||||
EXPECT_EQ(0, MinimalValueType::instances);
|
||||
}
|
||||
|
||||
void QueueMapTest::push(int key, int value) {
|
||||
_map->push(MinimalKeyType::create(key), MinimalValueType::create(value));
|
||||
}
|
||||
|
||||
boost::optional<int> QueueMapTest::pop() {
|
||||
auto elem = _map->pop();
|
||||
if (!elem) {
|
||||
return boost::none;
|
||||
}
|
||||
return elem.value().value();
|
||||
}
|
||||
|
||||
boost::optional<int> QueueMapTest::pop(int key) {
|
||||
auto elem = _map->pop(MinimalKeyType::create(key));
|
||||
if (!elem) {
|
||||
return boost::none;
|
||||
}
|
||||
return elem.value().value();
|
||||
}
|
||||
|
||||
boost::optional<int> QueueMapTest::peek() {
|
||||
auto elem = _map->peek();
|
||||
if (!elem) {
|
||||
return boost::none;
|
||||
}
|
||||
return elem.value().value();
|
||||
}
|
||||
|
||||
int QueueMapTest::size() {
|
||||
return _map->size();
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref.h>
|
||||
#include "blockstore/implementations/caching/cache/QueueMap.h"
|
||||
#include "MinimalKeyType.h"
|
||||
#include "MinimalValueType.h"
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
// This class is a parent class for tests on QueueMap.
|
||||
// It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements.
|
||||
// Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values).
|
||||
class QueueMapTest: public ::testing::Test {
|
||||
public:
|
||||
QueueMapTest();
|
||||
~QueueMapTest();
|
||||
|
||||
void push(int key, int value);
|
||||
boost::optional<int> pop();
|
||||
boost::optional<int> pop(int key);
|
||||
boost::optional<int> peek();
|
||||
int size();
|
||||
|
||||
private:
|
||||
cpputils::unique_ref<blockstore::caching::QueueMap<MinimalKeyType, MinimalValueType>> _map;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,27 +0,0 @@
|
||||
#include "blockstore/implementations/compressing/CompressingBlockStore.h"
|
||||
#include "blockstore/implementations/compressing/compressors/Gzip.h"
|
||||
#include "blockstore/implementations/compressing/compressors/RunLengthEncoding.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::compressing::CompressingBlockStore;
|
||||
using blockstore::compressing::Gzip;
|
||||
using blockstore::compressing::RunLengthEncoding;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
template<class Compressor>
|
||||
class CompressingBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<CompressingBlockStore<Compressor>>(make_unique_ref<FakeBlockStore>());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Compressing_Gzip, BlockStoreTest, CompressingBlockStoreTestFixture<Gzip>);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Compressing_RunLengthEncoding, BlockStoreTest, CompressingBlockStoreTestFixture<RunLengthEncoding>);
|
@ -1,92 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/compressing/compressors/Gzip.h"
|
||||
#include "blockstore/implementations/compressing/compressors/RunLengthEncoding.h"
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using namespace blockstore::compressing;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
|
||||
template<class Compressor>
|
||||
class CompressorTest: public ::testing::Test {
|
||||
public:
|
||||
void EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(const Data &data) {
|
||||
Data compressed = Compressor::Compress(data);
|
||||
Data decompressed = Compressor::Decompress(compressed.data(), compressed.size());
|
||||
EXPECT_EQ(data, decompressed);
|
||||
}
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(CompressorTest);
|
||||
|
||||
TYPED_TEST_P(CompressorTest, Empty) {
|
||||
Data empty(0);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(empty);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, ArbitraryData) {
|
||||
Data fixture = DataFixture::generate(10240);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(fixture);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, Zeroes) {
|
||||
Data zeroes(10240);
|
||||
zeroes.FillWithZeroes();
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(zeroes);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, Runs) {
|
||||
Data data(4096);
|
||||
std::memset(data.dataOffset(0), 0xF2, 1024);
|
||||
std::memset(data.dataOffset(1024), 0x00, 1024);
|
||||
std::memset(data.dataOffset(2048), 0x01, 2048);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, RunsAndArbitrary) {
|
||||
Data data(4096);
|
||||
std::memset(data.dataOffset(0), 0xF2, 1024);
|
||||
std::memcpy(data.dataOffset(1024), DataFixture::generate(1024).data(), 1024);
|
||||
std::memset(data.dataOffset(2048), 0x01, 2048);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, LargeData) {
|
||||
// this is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding)
|
||||
Data fixture = DataFixture::generate(200000);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(fixture);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, LargeRuns) {
|
||||
// each run is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding)
|
||||
constexpr size_t RUN_SIZE = 200000;
|
||||
Data data(3*RUN_SIZE);
|
||||
std::memset(data.dataOffset(0), 0xF2, RUN_SIZE);
|
||||
std::memset(data.dataOffset(RUN_SIZE), 0x00, RUN_SIZE);
|
||||
std::memset(data.dataOffset(2*RUN_SIZE), 0x01, RUN_SIZE);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CompressorTest, LargeRunsAndArbitrary) {
|
||||
// each run is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding)
|
||||
constexpr size_t RUN_SIZE = 200000;
|
||||
Data data(3*RUN_SIZE);
|
||||
std::memset(data.dataOffset(0), 0xF2, RUN_SIZE);
|
||||
std::memcpy(data.dataOffset(RUN_SIZE), DataFixture::generate(RUN_SIZE).data(), RUN_SIZE);
|
||||
std::memset(data.dataOffset(2*RUN_SIZE), 0x01, RUN_SIZE);
|
||||
this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(CompressorTest,
|
||||
Empty,
|
||||
ArbitraryData,
|
||||
Zeroes,
|
||||
Runs,
|
||||
RunsAndArbitrary,
|
||||
LargeData,
|
||||
LargeRuns,
|
||||
LargeRunsAndArbitrary
|
||||
);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Gzip, CompressorTest, Gzip);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(RunLengthEncoding, CompressorTest, RunLengthEncoding);
|
@ -1,65 +0,0 @@
|
||||
#include <cpp-utils/crypto/symmetric/ciphers.h>
|
||||
#include <cpp-utils/crypto/symmetric/Cipher.h>
|
||||
#include <blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h>
|
||||
#include "blockstore/implementations/encrypted/EncryptedBlockStore2.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "../../testutils/BlockStore2Test.h"
|
||||
//TODO Move FakeAuthenticatedCipher out of test folder to normal folder. Dependencies should not point into tests of other modules.
|
||||
#include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::BlockStore2;
|
||||
using blockstore::encrypted::EncryptedBlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
using cpputils::AES256_GCM;
|
||||
using cpputils::AES256_CFB;
|
||||
using cpputils::FakeAuthenticatedCipher;
|
||||
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
template<class Cipher>
|
||||
class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(
|
||||
make_unique_ref<EncryptedBlockStore2<Cipher>>(make_unique_ref<InMemoryBlockStore2>(), createKeyFixture())
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
|
||||
return Cipher::EncryptionKey::FromString(
|
||||
DataFixture::generate(Cipher::KEYSIZE, seed).ToString()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture<FakeAuthenticatedCipher>);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_GCM>);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_CFB, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_CFB>);
|
||||
|
||||
template<class Cipher>
|
||||
class EncryptedBlockStore2TestFixture: public BlockStore2TestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore2> createBlockStore() override {
|
||||
return make_unique_ref<EncryptedBlockStore2<Cipher>>(make_unique_ref<InMemoryBlockStore2>(), createKeyFixture());
|
||||
}
|
||||
|
||||
private:
|
||||
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
|
||||
return Cipher::EncryptionKey::FromString(
|
||||
DataFixture::generate(Cipher::KEYSIZE, seed).ToString()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_FakeCipher, BlockStore2Test, EncryptedBlockStore2TestFixture<FakeAuthenticatedCipher>);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_GCM, BlockStore2Test, EncryptedBlockStore2TestFixture<AES256_GCM>);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_CFB, BlockStore2Test, EncryptedBlockStore2TestFixture<AES256_CFB>);
|
@ -1,132 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h"
|
||||
#include "blockstore/implementations/encrypted/EncryptedBlockStore2.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "blockstore/utils/BlockStoreUtils.h"
|
||||
#include "../../testutils/gtest_printers.h"
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::Data;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::FakeAuthenticatedCipher;
|
||||
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using namespace blockstore::encrypted;
|
||||
|
||||
class EncryptedBlockStoreTest: public Test {
|
||||
public:
|
||||
static constexpr unsigned int BLOCKSIZE = 1024;
|
||||
EncryptedBlockStoreTest():
|
||||
baseBlockStore(new InMemoryBlockStore2),
|
||||
blockStore(make_unique_ref<EncryptedBlockStore2<FakeAuthenticatedCipher>>(std::move(cpputils::nullcheck(std::unique_ptr<InMemoryBlockStore2>(baseBlockStore)).value()), FakeAuthenticatedCipher::Key1())),
|
||||
data(DataFixture::generate(BLOCKSIZE)) {
|
||||
}
|
||||
InMemoryBlockStore2 *baseBlockStore;
|
||||
unique_ref<EncryptedBlockStore2<FakeAuthenticatedCipher>> blockStore;
|
||||
Data data;
|
||||
|
||||
blockstore::BlockId CreateBlockDirectlyWithFixtureAndReturnKey() {
|
||||
return CreateBlockReturnKey(data);
|
||||
}
|
||||
|
||||
blockstore::BlockId CreateBlockReturnKey(const Data &initData) {
|
||||
return blockStore->create(initData.copy());
|
||||
}
|
||||
|
||||
blockstore::BlockId CreateBlockWriteFixtureToItAndReturnKey() {
|
||||
auto blockId = blockStore->create(Data(data.size()));
|
||||
blockStore->store(blockId, data);
|
||||
return blockId;
|
||||
}
|
||||
|
||||
void ModifyBaseBlock(const blockstore::BlockId &blockId) {
|
||||
auto block = baseBlockStore->load(blockId).value();
|
||||
CryptoPP::byte* middle_byte = static_cast<CryptoPP::byte*>(block.data()) + 10;
|
||||
*middle_byte = *middle_byte + 1;
|
||||
baseBlockStore->store(blockId, block);
|
||||
}
|
||||
|
||||
blockstore::BlockId CopyBaseBlock(const blockstore::BlockId &blockId) {
|
||||
auto source = baseBlockStore->load(blockId).value();
|
||||
return baseBlockStore->create(source);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(EncryptedBlockStoreTest);
|
||||
};
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteOnCreate) {
|
||||
auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey();
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_NE(boost::none, loaded);
|
||||
EXPECT_EQ(data.size(), loaded->size());
|
||||
EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size()));
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteSeparately) {
|
||||
auto blockId = CreateBlockWriteFixtureToItAndReturnKey();
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_NE(boost::none, loaded);
|
||||
EXPECT_EQ(data.size(), loaded->size());
|
||||
EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size()));
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteOnCreate) {
|
||||
auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey();
|
||||
blockStore->_setKey(FakeAuthenticatedCipher::Key2());
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_EQ(boost::none, loaded);
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteSeparately) {
|
||||
auto blockId = CreateBlockWriteFixtureToItAndReturnKey();
|
||||
blockStore->_setKey(FakeAuthenticatedCipher::Key2());
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_EQ(boost::none, loaded);
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteOnCreate) {
|
||||
auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey();
|
||||
ModifyBaseBlock(blockId);
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_EQ(boost::none, loaded);
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteSeparately) {
|
||||
auto blockId = CreateBlockWriteFixtureToItAndReturnKey();
|
||||
ModifyBaseBlock(blockId);
|
||||
auto loaded = blockStore->load(blockId);
|
||||
EXPECT_EQ(boost::none, loaded);
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_zerovirtual) {
|
||||
auto blockId = CreateBlockReturnKey(Data(0));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_negativeboundaries) {
|
||||
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
|
||||
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
|
||||
auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value().size();
|
||||
if (physicalSizeForVirtualSizeZero > 0) {
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1));
|
||||
}
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero));
|
||||
EXPECT_EQ(1u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1));
|
||||
}
|
||||
|
||||
TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_positive) {
|
||||
auto blockId = CreateBlockReturnKey(Data(10*1024));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(10*1024u, blockStore->blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "../../testutils/BlockStore2Test.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
#include <blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::BlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
class InMemoryBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(
|
||||
make_unique_ref<InMemoryBlockStore2>()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(InMemory, BlockStoreTest, InMemoryBlockStoreTestFixture);
|
||||
|
||||
class InMemoryBlockStore2TestFixture: public BlockStore2TestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore2> createBlockStore() override {
|
||||
return make_unique_ref<InMemoryBlockStore2>();
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(InMemory, BlockStore2Test, InMemoryBlockStore2TestFixture);
|
@ -1,62 +0,0 @@
|
||||
#include "blockstore/implementations/integrity/IntegrityBlockStore2.h"
|
||||
#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "../../testutils/BlockStore2Test.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::BlockStore2;
|
||||
using blockstore::integrity::IntegrityBlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::TempFile;
|
||||
|
||||
template<bool AllowIntegrityViolations, bool MissingBlockIsIntegrityViolation>
|
||||
class IntegrityBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
IntegrityBlockStoreTestFixture() :stateFile(false) {}
|
||||
|
||||
TempFile stateFile;
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(
|
||||
make_unique_ref<IntegrityBlockStore2>(make_unique_ref<InMemoryBlockStore2>(), stateFile.path(), 0x12345678, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, [] {})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
using IntegrityBlockStoreTestFixture_multiclient = IntegrityBlockStoreTestFixture<false, false>;
|
||||
using IntegrityBlockStoreTestFixture_singleclient = IntegrityBlockStoreTestFixture<false, true>;
|
||||
using IntegrityBlockStoreTestFixture_multiclient_allowIntegrityViolations = IntegrityBlockStoreTestFixture<true, false>;
|
||||
using IntegrityBlockStoreTestFixture_singleclient_allowIntegrityViolations = IntegrityBlockStoreTestFixture<true, true>;
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient, BlockStoreTest, IntegrityBlockStoreTestFixture_singleclient);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient_allowIntegrityViolations, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient_allowIntegrityViolations);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient_allowIntegrityViolations, BlockStoreTest, IntegrityBlockStoreTestFixture_singleclient_allowIntegrityViolations);
|
||||
|
||||
template<bool AllowIntegrityViolations, bool MissingBlockIsIntegrityViolation>
|
||||
class IntegrityBlockStore2TestFixture: public BlockStore2TestFixture {
|
||||
public:
|
||||
IntegrityBlockStore2TestFixture() :stateFile(false) {}
|
||||
|
||||
TempFile stateFile;
|
||||
unique_ref<BlockStore2> createBlockStore() override {
|
||||
return make_unique_ref<IntegrityBlockStore2>(make_unique_ref<InMemoryBlockStore2>(), stateFile.path(), 0x12345678, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, [] {});
|
||||
}
|
||||
};
|
||||
|
||||
using IntegrityBlockStore2TestFixture_multiclient = IntegrityBlockStore2TestFixture<false, false>;
|
||||
using IntegrityBlockStore2TestFixture_singleclient = IntegrityBlockStore2TestFixture<false, true>;
|
||||
using IntegrityBlockStore2TestFixture_multiclient_allowIntegrityViolations = IntegrityBlockStore2TestFixture<true, false>;
|
||||
using IntegrityBlockStore2TestFixture_singleclient_allowIntegrityViolations = IntegrityBlockStore2TestFixture<true, true>;
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient_allowIntegrityViolations);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient_allowIntegrityViolations);
|
@ -1,388 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/integrity/IntegrityBlockStore2.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "blockstore/utils/BlockStoreUtils.h"
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
#include "../../testutils/gtest_printers.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::Data;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::TempFile;
|
||||
using cpputils::serialize;
|
||||
using cpputils::deserialize;
|
||||
using boost::none;
|
||||
using std::unique_ptr;
|
||||
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using namespace blockstore::integrity;
|
||||
|
||||
namespace {
|
||||
class FakeCallback final {
|
||||
public:
|
||||
FakeCallback(): wasCalled_(false) {}
|
||||
|
||||
bool wasCalled() const {
|
||||
return wasCalled_;
|
||||
}
|
||||
|
||||
std::function<void ()> callback() {
|
||||
return [this] () {
|
||||
wasCalled_ = true;
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
bool wasCalled_;
|
||||
};
|
||||
}
|
||||
|
||||
template<bool AllowIntegrityViolations, bool MissingBlockIsIntegrityViolation>
|
||||
class IntegrityBlockStoreTest: public Test {
|
||||
public:
|
||||
static constexpr unsigned int BLOCKSIZE = 1024;
|
||||
IntegrityBlockStoreTest():
|
||||
stateFile(false),
|
||||
onIntegrityViolation(),
|
||||
baseBlockStore(new InMemoryBlockStore2),
|
||||
blockStore(make_unique_ref<IntegrityBlockStore2>(std::move(cpputils::nullcheck(std::unique_ptr<InMemoryBlockStore2>(baseBlockStore)).value()), stateFile.path(), myClientId, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, onIntegrityViolation.callback())),
|
||||
data(DataFixture::generate(BLOCKSIZE)) {
|
||||
}
|
||||
static constexpr uint32_t myClientId = 0x12345678;
|
||||
TempFile stateFile;
|
||||
FakeCallback onIntegrityViolation;
|
||||
InMemoryBlockStore2 *baseBlockStore;
|
||||
unique_ref<IntegrityBlockStore2> blockStore;
|
||||
Data data;
|
||||
|
||||
blockstore::BlockId CreateBlockReturnKey() {
|
||||
return CreateBlockReturnKey(data);
|
||||
}
|
||||
|
||||
blockstore::BlockId CreateBlockReturnKey(const Data &initData) {
|
||||
return blockStore->create(initData.copy());
|
||||
}
|
||||
|
||||
Data loadBaseBlock(const blockstore::BlockId &blockId) {
|
||||
return baseBlockStore->load(blockId).value();
|
||||
}
|
||||
|
||||
Data loadBlock(const blockstore::BlockId &blockId) {
|
||||
return blockStore->load(blockId).value();
|
||||
}
|
||||
|
||||
void modifyBlock(const blockstore::BlockId &blockId) {
|
||||
auto block = blockStore->load(blockId).value();
|
||||
CryptoPP::byte* first_byte = static_cast<CryptoPP::byte*>(block.data());
|
||||
*first_byte = *first_byte + 1;
|
||||
blockStore->store(blockId, block);
|
||||
}
|
||||
|
||||
void rollbackBaseBlock(const blockstore::BlockId &blockId, const Data &data) {
|
||||
baseBlockStore->store(blockId, data);
|
||||
}
|
||||
|
||||
void decreaseVersionNumber(const blockstore::BlockId &blockId) {
|
||||
auto baseBlock = baseBlockStore->load(blockId).value();
|
||||
void* versionPtr = static_cast<uint8_t*>(baseBlock.data()) + IntegrityBlockStore2::VERSION_HEADER_OFFSET;
|
||||
uint64_t version = deserialize<uint64_t>(versionPtr);
|
||||
ASSERT(version > 1, "Can't decrease the lowest allowed version number");
|
||||
serialize<uint64_t>(versionPtr, version-1);
|
||||
baseBlockStore->store(blockId, baseBlock);
|
||||
}
|
||||
|
||||
void increaseVersionNumber(const blockstore::BlockId &blockId) {
|
||||
auto baseBlock = baseBlockStore->load(blockId).value();
|
||||
void* versionPtr = static_cast<uint8_t*>(baseBlock.data()) + IntegrityBlockStore2::VERSION_HEADER_OFFSET;
|
||||
uint64_t version = deserialize<uint64_t>(versionPtr);
|
||||
serialize<uint64_t>(versionPtr, version+1);
|
||||
baseBlockStore->store(blockId, baseBlock);
|
||||
}
|
||||
|
||||
void changeClientId(const blockstore::BlockId &blockId) {
|
||||
auto baseBlock = baseBlockStore->load(blockId).value();
|
||||
void* clientIdPtr = static_cast<uint8_t*>(baseBlock.data()) + IntegrityBlockStore2::CLIENTID_HEADER_OFFSET;
|
||||
uint64_t clientId = deserialize<uint64_t>(clientIdPtr);
|
||||
serialize<uint64_t>(clientIdPtr, clientId+1);
|
||||
baseBlockStore->store(blockId, baseBlock);
|
||||
}
|
||||
|
||||
void deleteBlock(const blockstore::BlockId &blockId) {
|
||||
blockStore->remove(blockId);
|
||||
}
|
||||
|
||||
void insertBaseBlock(const blockstore::BlockId &blockId, Data data) {
|
||||
EXPECT_TRUE(baseBlockStore->tryCreate(blockId, data));
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(IntegrityBlockStoreTest);
|
||||
};
|
||||
|
||||
using IntegrityBlockStoreTest_Default = IntegrityBlockStoreTest<false, false>;
|
||||
using IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation = IntegrityBlockStoreTest<false, true>;
|
||||
using IntegrityBlockStoreTest_AllowIntegrityViolations = IntegrityBlockStoreTest<true, false>;
|
||||
using IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation = IntegrityBlockStoreTest<true, true>;
|
||||
|
||||
template<bool AllowIntegrityViolations, bool MissingBlockIsIntegrityViolation>
|
||||
constexpr uint32_t IntegrityBlockStoreTest<AllowIntegrityViolations, MissingBlockIsIntegrityViolation>::myClientId;
|
||||
|
||||
// Test that a decreasing version number is not allowed
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_1) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
modifyBlock(blockId);
|
||||
rollbackBaseBlock(blockId, oldBaseBlock);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that a decreasing version number is allowed if allowIntegrityViolations is set.
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsDecreasingVersionNumberForSameClient_1) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
modifyBlock(blockId);
|
||||
rollbackBaseBlock(blockId, oldBaseBlock);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_2) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
// Decrease the version number again
|
||||
decreaseVersionNumber(blockId);
|
||||
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsDecreasingVersionNumberForSameClient_2) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
// Decrease the version number again
|
||||
decreaseVersionNumber(blockId);
|
||||
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that a different client doesn't need to have a higher version number (i.e. version numbers are per client).
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(blockId);
|
||||
decreaseVersionNumber(blockId);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(blockId);
|
||||
decreaseVersionNumber(blockId);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that it doesn't allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowSameVersionNumberForOldClient) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(blockId);
|
||||
loadBlock(blockId); // make the block store know about this other client's modification
|
||||
// Rollback to old client
|
||||
rollbackBaseBlock(blockId, oldBaseBlock);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that it does allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client, but integrity violations are allowed
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsSameVersionNumberForOldClient) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(blockId);
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(blockId);
|
||||
loadBlock(blockId); // make the block store know about this other client's modification
|
||||
// Rollback to old client
|
||||
rollbackBaseBlock(blockId, oldBaseBlock);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that deleted blocks cannot be re-introduced
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowReintroducingDeletedBlocks) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
deleteBlock(blockId);
|
||||
insertBaseBlock(blockId, std::move(oldBaseBlock));
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Test that deleted blocks can be re-introduced if integrity violations are allowed
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsReintroducingDeletedBlocks) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
deleteBlock(blockId);
|
||||
insertBaseBlock(blockId, std::move(oldBaseBlock));
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// This can happen if a client synchronization is delayed. Another client might have won the conflict and pushed a new version for the deleted block.
|
||||
TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
deleteBlock(blockId);
|
||||
insertBaseBlock(blockId, std::move(oldBaseBlock));
|
||||
increaseVersionNumber(blockId);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(blockId);
|
||||
deleteBlock(blockId);
|
||||
insertBaseBlock(blockId, std::move(oldBaseBlock));
|
||||
increaseVersionNumber(blockId);
|
||||
EXPECT_NE(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them.
|
||||
TEST_F(IntegrityBlockStoreTest_Default, DeletionPrevention_AllowsDeletingBlocksWhenDeactivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, DeletionPrevention_AllowsDeletingBlocksWhenDeactivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a single-client scenario, missing blocks are integrity errors.
|
||||
TEST_F(IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation, DeletionPrevention_DoesntAllowDeletingBlocksWhenActivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a single-client scenario, missing blocks don't throw if integrity violations are allowed.
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation, DeletionPrevention_AllowsDeletingBlocksWhenActivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them.
|
||||
TEST_F(IntegrityBlockStoreTest_Default, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
int count = 0;
|
||||
blockStore->forEachBlock([&count] (const blockstore::BlockId &) {
|
||||
++count;
|
||||
});
|
||||
EXPECT_EQ(0, count);
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
int count = 0;
|
||||
blockStore->forEachBlock([&count] (const blockstore::BlockId &) {
|
||||
++count;
|
||||
});
|
||||
EXPECT_EQ(0, count);
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a single-client scenario, missing blocks are integrity errors.
|
||||
TEST_F(IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation, DeletionPrevention_InForEachBlock_DoesntAllowDeletingBlocksWhenActivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
blockStore->forEachBlock([] (const blockstore::BlockId &) {});
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// Check that in a single-client scenario, missing blocks don't throw if integrity violations are allowed.
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenActivated) {
|
||||
auto blockId = blockStore->create(Data(0));
|
||||
baseBlockStore->remove(blockId);
|
||||
blockStore->forEachBlock([] (const blockstore::BlockId &) {});
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, LoadingWithDifferentBlockIdFails) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
blockstore::BlockId key2 = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
baseBlockStore->store(key2, baseBlockStore->load(blockId).value());
|
||||
EXPECT_EQ(boost::none, blockStore->load(key2));
|
||||
EXPECT_TRUE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, LoadingWithDifferentBlockIdDoesntFail) {
|
||||
auto blockId = CreateBlockReturnKey();
|
||||
blockstore::BlockId key2 = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
baseBlockStore->store(key2, baseBlockStore->load(blockId).value());
|
||||
EXPECT_NE(boost::none, blockStore->load(key2));
|
||||
EXPECT_FALSE(onIntegrityViolation.wasCalled());
|
||||
}
|
||||
|
||||
// TODO Test more integrity cases:
|
||||
// - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client)
|
||||
// - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id
|
||||
// - Think about more...
|
||||
// TODO Test that disabling integrity checks allows all these cases
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_zerovirtual) {
|
||||
auto blockId = CreateBlockReturnKey(Data(0));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_negativeboundaries) {
|
||||
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
|
||||
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
|
||||
auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value().size();
|
||||
if (physicalSizeForVirtualSizeZero > 0) {
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1));
|
||||
}
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero));
|
||||
EXPECT_EQ(1u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1));
|
||||
}
|
||||
|
||||
TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_positive) {
|
||||
auto blockId = CreateBlockReturnKey(Data(10*1024));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(10*1024u, blockStore->blockSizeFromPhysicalBlockSize(base.size()));
|
||||
}
|
@ -1,354 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <blockstore/implementations/integrity/KnownBlockVersions.h>
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
|
||||
using blockstore::integrity::KnownBlockVersions;
|
||||
using blockstore::BlockId;
|
||||
using cpputils::TempFile;
|
||||
using std::unordered_set;
|
||||
|
||||
class KnownBlockVersionsTest : public ::testing::Test {
|
||||
public:
|
||||
KnownBlockVersionsTest() :stateFile(false), testobj(stateFile.path(), myClientId) {}
|
||||
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
blockstore::BlockId blockId2 = blockstore::BlockId::FromString("C772972491BB4932A1389EE14BC7090A");
|
||||
static constexpr uint32_t myClientId = 0x12345678;
|
||||
static constexpr uint32_t clientId = 0x23456789;
|
||||
static constexpr uint32_t clientId2 = 0x34567890;
|
||||
|
||||
TempFile stateFile;
|
||||
KnownBlockVersions testobj;
|
||||
|
||||
void setVersion(KnownBlockVersions *testobj, uint32_t clientId, const blockstore::BlockId &blockId, uint64_t version) {
|
||||
if (!testobj->checkAndUpdateVersion(clientId, blockId, version)) {
|
||||
throw std::runtime_error("Couldn't increase version");
|
||||
}
|
||||
}
|
||||
|
||||
void EXPECT_VERSION_IS(uint64_t version, KnownBlockVersions *testobj, blockstore::BlockId &blockId, uint32_t clientId) {
|
||||
EXPECT_FALSE(testobj->checkAndUpdateVersion(clientId, blockId, version-1));
|
||||
EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, blockId, version+1));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, setandget) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, setandget_isPerClientId) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId, 3);
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, setandget_isPerBlock) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId, blockId2, 3);
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(3u, testobj.getBlockVersion(clientId, blockId2));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, setandget_allowsIncreasing) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId, blockId, 6);
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, setandget_doesntAllowDecreasing) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_ANY_THROW(
|
||||
setVersion(&testobj, clientId, blockId, 4);
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, myClientId_isConsistent) {
|
||||
EXPECT_EQ(testobj.myClientId(), testobj.myClientId());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, incrementVersion_newentry) {
|
||||
auto version = testobj.incrementVersion(blockId);
|
||||
EXPECT_EQ(1u, version);
|
||||
EXPECT_EQ(1u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry) {
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 5);
|
||||
auto version = testobj.incrementVersion(blockId);
|
||||
EXPECT_EQ(6u, version);
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_newentry) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 5));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientSameVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 5));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientLowerVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 4));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientNewerVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6));
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientSameVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 5));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientLowerVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 3));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientHigherVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 3));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6));
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_oldClientIsSelf) {
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 3));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_oldClientIsSelf) {
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 5);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_oldClientIsSelf) {
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 4);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 6));
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_newClientIsSelf) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 7);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 3));
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_newClientIsSelf) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 7);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client
|
||||
EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_newClientIsSelf) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, testobj.myClientId(), blockId, 7);
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6));
|
||||
EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId));
|
||||
EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentKeys) {
|
||||
// Setup
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId2, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 150));
|
||||
|
||||
// Checks
|
||||
EXPECT_VERSION_IS(150, &testobj, blockId, clientId);
|
||||
EXPECT_VERSION_IS(100, &testobj, blockId2, clientId);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentClientIds) {
|
||||
// Setup
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 150));
|
||||
|
||||
EXPECT_VERSION_IS(150, &testobj, blockId, clientId);
|
||||
EXPECT_VERSION_IS(100, &testobj, blockId, clientId2);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_allowsRollbackToSameClientWithSameVersionNumber) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_doesntAllowRollbackToOldClientWithSameVersionNumber) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 10));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) {
|
||||
TempFile stateFile(false);
|
||||
{
|
||||
KnownBlockVersions _1(stateFile.path(), myClientId);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(KnownBlockVersions(stateFile.path(), myClientId).checkAndUpdateVersion(clientId, blockId, 1));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) {
|
||||
TempFile stateFile(false);
|
||||
EXPECT_TRUE(KnownBlockVersions(stateFile.path(), myClientId).checkAndUpdateVersion(clientId, blockId, 100));
|
||||
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_EQ(100u, obj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_threeentries) {
|
||||
TempFile stateFile(false);
|
||||
{
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(obj.myClientId(), blockId, 100));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(obj.myClientId(), blockId2, 50));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 150));
|
||||
}
|
||||
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_EQ(100u, obj.getBlockVersion(obj.myClientId(), blockId));
|
||||
EXPECT_EQ(50u, obj.getBlockVersion(obj.myClientId(), blockId2));
|
||||
EXPECT_EQ(150u, obj.getBlockVersion(clientId, blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_lastUpdateClientIdIsStored) {
|
||||
{
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, blockId, 10));
|
||||
}
|
||||
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_FALSE(obj.checkAndUpdateVersion(clientId, blockId, 100));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, blockId, 10));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 101));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, markAsDeleted_doesntAllowReIntroducing_sameClientId) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
testobj.markBlockAsDeleted(blockId);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, markAsDeleted_doesntAllowReIntroducing_oldClientId) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId, 5);
|
||||
testobj.markBlockAsDeleted(blockId);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, markAsDeleted_checkAndUpdateDoesntDestroyState) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId, 5);
|
||||
testobj.markBlockAsDeleted(blockId);
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5));
|
||||
|
||||
// Check block is still deleted
|
||||
EXPECT_FALSE(testobj.blockShouldExist(blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, blockShouldExist_unknownBlock) {
|
||||
EXPECT_FALSE(testobj.blockShouldExist(blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, blockShouldExist_knownBlock) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_TRUE(testobj.blockShouldExist(blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, blockShouldExist_deletedBlock) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
testobj.markBlockAsDeleted(blockId);
|
||||
EXPECT_FALSE(testobj.blockShouldExist(blockId));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, path) {
|
||||
KnownBlockVersions obj(stateFile.path(), myClientId);
|
||||
EXPECT_EQ(stateFile.path(), obj.path());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_empty) {
|
||||
EXPECT_EQ(unordered_set<BlockId>({}), testobj.existingBlocks());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_oneentry) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
EXPECT_EQ(unordered_set<BlockId>({blockId}), testobj.existingBlocks());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId2, 5);
|
||||
EXPECT_EQ(unordered_set<BlockId>({blockId, blockId2}), testobj.existingBlocks());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries_sameKey) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId, 5);
|
||||
EXPECT_EQ(unordered_set<BlockId>({blockId}), testobj.existingBlocks());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntry) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId2, 5);
|
||||
testobj.markBlockAsDeleted(blockId2);
|
||||
EXPECT_EQ(unordered_set<BlockId>({blockId}), testobj.existingBlocks());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntries) {
|
||||
setVersion(&testobj, clientId, blockId, 5);
|
||||
setVersion(&testobj, clientId2, blockId2, 5);
|
||||
testobj.markBlockAsDeleted(blockId);
|
||||
testobj.markBlockAsDeleted(blockId2);
|
||||
EXPECT_EQ(unordered_set<BlockId>({}), testobj.existingBlocks());
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
using blockstore::inmemory::InMemoryBlockStore2;
|
||||
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class LowToHighLevelBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
LowToHighLevelBlockStoreTestFixture() {}
|
||||
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(make_unique_ref<InMemoryBlockStore2>());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(LowToHighLevel, BlockStoreTest, LowToHighLevelBlockStoreTestFixture);
|
@ -1,20 +0,0 @@
|
||||
#include "blockstore/implementations/mock/MockBlock.h"
|
||||
#include "blockstore/implementations/mock/MockBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::mock::MockBlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
class MockBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<MockBlockStore>(make_unique_ref<FakeBlockStore>());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mock, BlockStoreTest, MockBlockStoreTestFixture);
|
@ -1,45 +0,0 @@
|
||||
#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h"
|
||||
#include "blockstore/implementations/ondisk/OnDiskBlockStore2.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include "../../testutils/BlockStore2Test.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/tempfile/TempDir.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::ondisk::OnDiskBlockStore2;
|
||||
using blockstore::BlockStore2;
|
||||
using blockstore::lowtohighlevel::LowToHighLevelBlockStore;
|
||||
|
||||
using cpputils::TempDir;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
class OnDiskBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
OnDiskBlockStoreTestFixture(): tempdir() {}
|
||||
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<LowToHighLevelBlockStore>(
|
||||
make_unique_ref<OnDiskBlockStore2>(tempdir.path())
|
||||
);
|
||||
}
|
||||
private:
|
||||
TempDir tempdir;
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(OnDisk, BlockStoreTest, OnDiskBlockStoreTestFixture);
|
||||
|
||||
class OnDiskBlockStore2TestFixture: public BlockStore2TestFixture {
|
||||
public:
|
||||
OnDiskBlockStore2TestFixture(): tempdir() {}
|
||||
|
||||
unique_ref<BlockStore2> createBlockStore() override {
|
||||
return make_unique_ref<OnDiskBlockStore2>(tempdir.path());
|
||||
}
|
||||
private:
|
||||
TempDir tempdir;
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(OnDisk, BlockStore2Test, OnDiskBlockStore2TestFixture);
|
@ -1,67 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/ondisk/OnDiskBlockStore2.h"
|
||||
#include <cpp-utils/tempfile/TempDir.h>
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::TempDir;
|
||||
using cpputils::Data;
|
||||
using std::ifstream;
|
||||
using blockstore::BlockId;
|
||||
|
||||
using namespace blockstore::ondisk;
|
||||
|
||||
class OnDiskBlockStoreTest: public Test {
|
||||
public:
|
||||
OnDiskBlockStoreTest():
|
||||
baseDir(),
|
||||
blockStore(baseDir.path()) {
|
||||
}
|
||||
TempDir baseDir;
|
||||
OnDiskBlockStore2 blockStore;
|
||||
|
||||
blockstore::BlockId CreateBlockReturnKey(const Data &initData) {
|
||||
return blockStore.create(initData.copy());
|
||||
}
|
||||
|
||||
uint64_t getPhysicalBlockSize(const BlockId &blockId) {
|
||||
ifstream stream((baseDir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3)).c_str());
|
||||
stream.seekg(0, stream.end);
|
||||
return stream.tellg();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_zerovirtual) {
|
||||
auto blockId = CreateBlockReturnKey(Data(0));
|
||||
auto baseSize = getPhysicalBlockSize(blockId);
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(baseSize));
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_negativeboundaries) {
|
||||
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
|
||||
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
|
||||
auto physicalSizeForVirtualSizeZero = getPhysicalBlockSize(CreateBlockReturnKey(Data(0)));
|
||||
if (physicalSizeForVirtualSizeZero > 0) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1));
|
||||
}
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero));
|
||||
EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1));
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_positive) {
|
||||
auto blockId = CreateBlockReturnKey(Data(10*1024));
|
||||
auto baseSize = getPhysicalBlockSize(blockId);
|
||||
EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(baseSize));
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocksWithSameKeyPrefix) {
|
||||
const BlockId key1 = BlockId::FromString("4CE72ECDD20877A12ADBF4E3927C0A13");
|
||||
const BlockId key2 = BlockId::FromString("4CE72ECDD20877A12ADBF4E3927C0A14");
|
||||
EXPECT_TRUE(blockStore.tryCreate(key1, cpputils::Data(0)));
|
||||
EXPECT_TRUE(blockStore.tryCreate(key2, cpputils::Data(0)));
|
||||
EXPECT_EQ(2u, blockStore.numBlocks());
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
#include <cpp-utils/tempfile/TempDir.h>
|
||||
|
||||
// TODO This should be ported to BlockStore2
|
||||
|
||||
/*
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using cpputils::Data;
|
||||
using cpputils::TempFile;
|
||||
using cpputils::TempDir;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
using namespace blockstore;
|
||||
using namespace blockstore::ondisk;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
class OnDiskBlockCreateTest: public Test {
|
||||
public:
|
||||
OnDiskBlockCreateTest()
|
||||
// Don't create the temp file yet (therefore pass false to the TempFile constructor)
|
||||
: dir(),
|
||||
key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")),
|
||||
file(dir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3), false) {
|
||||
}
|
||||
TempDir dir;
|
||||
BlockId key;
|
||||
TempFile file;
|
||||
};
|
||||
|
||||
TEST_F(OnDiskBlockCreateTest, CreatingBlockCreatesFile) {
|
||||
EXPECT_FALSE(bf::exists(file.path()));
|
||||
|
||||
auto block = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0));
|
||||
|
||||
EXPECT_TRUE(bf::exists(file.path()));
|
||||
EXPECT_TRUE(bf::is_regular_file(file.path()));
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockCreateTest, CreatingExistingBlockReturnsNull) {
|
||||
auto block1 = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0));
|
||||
auto block2 = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0));
|
||||
EXPECT_TRUE((bool)block1);
|
||||
EXPECT_FALSE((bool)block2);
|
||||
}
|
||||
|
||||
class OnDiskBlockCreateSizeTest: public OnDiskBlockCreateTest, public WithParamInterface<size_t> {
|
||||
public:
|
||||
unique_ref<OnDiskBlock> block;
|
||||
Data ZEROES;
|
||||
|
||||
OnDiskBlockCreateSizeTest():
|
||||
block(OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(GetParam()).FillWithZeroes()).value()),
|
||||
ZEROES(block->size())
|
||||
{
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(OnDiskBlockCreateSizeTest, OnDiskBlockCreateSizeTest, Values(0, 1, 5, 1024, 10*1024*1024));
|
||||
|
||||
TEST_P(OnDiskBlockCreateSizeTest, OnDiskSizeIsCorrect) {
|
||||
Data fileContent = Data::LoadFromFile(file.path()).value();
|
||||
EXPECT_EQ(GetParam() + OnDiskBlock::formatVersionHeaderSize(), fileContent.size());
|
||||
}
|
||||
|
||||
TEST_P(OnDiskBlockCreateSizeTest, OnDiskBlockIsZeroedOut) {
|
||||
Data fileContent = Data::LoadFromFile(file.path()).value();
|
||||
Data fileContentWithoutHeader(fileContent.size() - OnDiskBlock::formatVersionHeaderSize());
|
||||
std::memcpy(fileContentWithoutHeader.data(), fileContent.dataOffset(OnDiskBlock::formatVersionHeaderSize()), fileContentWithoutHeader.size());
|
||||
EXPECT_EQ(ZEROES, fileContentWithoutHeader);
|
||||
}
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface.
|
||||
// Here, we create it using OnDiskBlock::CreateOnDisk()
|
||||
TEST_P(OnDiskBlockCreateSizeTest, InMemorySizeIsCorrect) {
|
||||
EXPECT_EQ(GetParam(), block->size());
|
||||
}
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface.
|
||||
// Here, we create it using OnDiskBlock::CreateOnDisk()
|
||||
TEST_P(OnDiskBlockCreateSizeTest, InMemoryBlockIsZeroedOut) {
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), block->data(), block->size()));
|
||||
}
|
||||
*/
|
@ -1,119 +0,0 @@
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
#include <cpp-utils/tempfile/TempDir.h>
|
||||
|
||||
// TODO This should be ported to BlockStore2
|
||||
/*
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::TempFile;
|
||||
using cpputils::TempDir;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
using namespace blockstore;
|
||||
using namespace blockstore::ondisk;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
class OnDiskBlockFlushTest: public Test, public WithParamInterface<size_t> {
|
||||
public:
|
||||
OnDiskBlockFlushTest()
|
||||
// Don't create the temp file yet (therefore pass false to the TempFile constructor)
|
||||
: dir(),
|
||||
key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")),
|
||||
file(dir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3), false),
|
||||
randomData(DataFixture::generate(GetParam())) {
|
||||
}
|
||||
TempDir dir;
|
||||
BlockId key;
|
||||
TempFile file;
|
||||
|
||||
Data randomData;
|
||||
|
||||
unique_ref<OnDiskBlock> CreateBlockAndLoadItFromDisk() {
|
||||
{
|
||||
OnDiskBlock::CreateOnDisk(dir.path(), blockId, randomData.copy()).value();
|
||||
}
|
||||
return OnDiskBlock::LoadFromDisk(dir.path(), blockId).value();
|
||||
}
|
||||
|
||||
unique_ref<OnDiskBlock> CreateBlock() {
|
||||
return OnDiskBlock::CreateOnDisk(dir.path(), blockId, randomData.copy()).value();
|
||||
}
|
||||
|
||||
void WriteDataToBlock(const unique_ref<OnDiskBlock> &block) {
|
||||
block->write(randomData.data(), 0, randomData.size());
|
||||
}
|
||||
|
||||
void EXPECT_BLOCK_DATA_CORRECT(const unique_ref<OnDiskBlock> &block) {
|
||||
EXPECT_EQ(randomData.size(), block->size());
|
||||
EXPECT_EQ(0, std::memcmp(randomData.data(), block->data(), randomData.size()));
|
||||
}
|
||||
|
||||
void EXPECT_STORED_FILE_DATA_CORRECT() {
|
||||
Data fileContent = Data::LoadFromFile(file.path()).value();
|
||||
Data fileContentWithoutHeader(fileContent.size() - OnDiskBlock::formatVersionHeaderSize());
|
||||
std::memcpy(fileContentWithoutHeader.data(), fileContent.dataOffset(OnDiskBlock::formatVersionHeaderSize()), fileContentWithoutHeader.size());
|
||||
EXPECT_EQ(randomData, fileContentWithoutHeader);
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(OnDiskBlockFlushTest, OnDiskBlockFlushTest, Values((size_t)0, (size_t)1, (size_t)1024, (size_t)4096, (size_t)10*1024*1024));
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface.
|
||||
// Here, we create it using OnDiskBlock::CreateOnDisk()
|
||||
TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushingDoesntChangeBlock) {
|
||||
auto block = CreateBlock();
|
||||
WriteDataToBlock(block);
|
||||
|
||||
EXPECT_BLOCK_DATA_CORRECT(block);
|
||||
}
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface.
|
||||
// Here, we create it using OnDiskBlock::CreateOnDisk() / OnDiskBlock::LoadFromDisk()
|
||||
TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushingDoesntChangeBlock) {
|
||||
auto block = CreateBlockAndLoadItFromDisk();
|
||||
WriteDataToBlock(block);
|
||||
|
||||
EXPECT_BLOCK_DATA_CORRECT(block);
|
||||
}
|
||||
|
||||
TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushingWritesCorrectData) {
|
||||
auto block = CreateBlock();
|
||||
WriteDataToBlock(block);
|
||||
|
||||
EXPECT_STORED_FILE_DATA_CORRECT();
|
||||
}
|
||||
|
||||
TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushingWritesCorrectData) {
|
||||
auto block = CreateBlockAndLoadItFromDisk();
|
||||
WriteDataToBlock(block);
|
||||
|
||||
EXPECT_STORED_FILE_DATA_CORRECT();
|
||||
}
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there it can only checks block content by loading it again.
|
||||
// Here, we check the content on disk.
|
||||
TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushesWhenDestructed) {
|
||||
{
|
||||
auto block = CreateBlock();
|
||||
WriteDataToBlock(block);
|
||||
}
|
||||
EXPECT_STORED_FILE_DATA_CORRECT();
|
||||
}
|
||||
|
||||
// This test is also tested by OnDiskBlockStoreTest, but there it can only checks block content by loading it again.
|
||||
// Here, we check the content on disk.
|
||||
TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushesWhenDestructed) {
|
||||
{
|
||||
auto block = CreateBlockAndLoadItFromDisk();
|
||||
WriteDataToBlock(block);
|
||||
}
|
||||
EXPECT_STORED_FILE_DATA_CORRECT();
|
||||
}
|
||||
*/
|
@ -1,82 +0,0 @@
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include "blockstore/utils/FileDoesntExistException.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cpp-utils/data/Data.h>
|
||||
#include <cpp-utils/tempfile/TempFile.h>
|
||||
#include <cpp-utils/tempfile/TempDir.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
#include <fstream>
|
||||
|
||||
// TODO This should be ported to BlockStore2
|
||||
/*
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
|
||||
using std::ofstream;
|
||||
using std::ios;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::TempFile;
|
||||
using cpputils::TempDir;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
using namespace blockstore;
|
||||
using namespace blockstore::ondisk;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
class OnDiskBlockLoadTest: public Test, public WithParamInterface<size_t> {
|
||||
public:
|
||||
OnDiskBlockLoadTest():
|
||||
dir(),
|
||||
key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")),
|
||||
file(dir.path() / blockId.ToString(), false) {
|
||||
}
|
||||
TempDir dir;
|
||||
BlockId key;
|
||||
TempFile file;
|
||||
|
||||
void CreateBlockWithSize(size_t size) {
|
||||
Data data(size);
|
||||
OnDiskBlock::CreateOnDisk(dir.path(), blockId, std::move(data));
|
||||
}
|
||||
|
||||
void StoreData(Data data) {
|
||||
OnDiskBlock::CreateOnDisk(dir.path(), blockId, std::move(data));
|
||||
}
|
||||
|
||||
unique_ref<OnDiskBlock> LoadBlock() {
|
||||
return OnDiskBlock::LoadFromDisk(dir.path(), blockId).value();
|
||||
}
|
||||
|
||||
void EXPECT_BLOCK_DATA_EQ(const Data &expected, const OnDiskBlock &actual) {
|
||||
EXPECT_EQ(expected.size(), actual.size());
|
||||
EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), expected.size()));
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(OnDiskBlockLoadTest, OnDiskBlockLoadTest, Values(0, 1, 5, 1024, 10*1024*1024));
|
||||
|
||||
TEST_P(OnDiskBlockLoadTest, LoadsCorrectSize) {
|
||||
CreateBlockWithSize(GetParam());
|
||||
|
||||
auto block = LoadBlock();
|
||||
|
||||
EXPECT_EQ(GetParam(), block->size());
|
||||
}
|
||||
|
||||
TEST_P(OnDiskBlockLoadTest, LoadedDataIsCorrect) {
|
||||
Data randomData = DataFixture::generate(GetParam());
|
||||
StoreData(randomData.copy());
|
||||
|
||||
auto block = LoadBlock();
|
||||
|
||||
EXPECT_BLOCK_DATA_EQ(randomData, *block);
|
||||
}
|
||||
|
||||
TEST_F(OnDiskBlockLoadTest, LoadNotExistingBlock) {
|
||||
BlockId key2 = BlockId::FromString("272EE5517627CFA147A971A8E6E747E0");
|
||||
EXPECT_EQ(boost::none, OnDiskBlock::LoadFromDisk(dir.path(), key2));
|
||||
}
|
||||
*/
|
@ -1,23 +0,0 @@
|
||||
#include "blockstore/implementations/parallelaccess/ParallelAccessBlockStore.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::parallelaccess::ParallelAccessBlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
|
||||
using cpputils::make_unique_ref;
|
||||
using cpputils::unique_ref;
|
||||
|
||||
class ParallelAccessBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<ParallelAccessBlockStore>(make_unique_ref<FakeBlockStore>());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(ParallelAccess, BlockStoreTest, ParallelAccessBlockStoreTestFixture);
|
||||
|
||||
//TODO Add specific tests ensuring that loading the same block twice doesn't load it twice from the underlying blockstore
|
@ -1,52 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "blockstore/implementations/parallelaccess/ParallelAccessBlockStore.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::Data;
|
||||
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
|
||||
using namespace blockstore::parallelaccess;
|
||||
|
||||
class ParallelAccessBlockStoreTest: public Test {
|
||||
public:
|
||||
ParallelAccessBlockStoreTest():
|
||||
baseBlockStore(new FakeBlockStore),
|
||||
blockStore(std::move(cpputils::nullcheck(std::unique_ptr<FakeBlockStore>(baseBlockStore)).value())) {
|
||||
}
|
||||
FakeBlockStore *baseBlockStore;
|
||||
ParallelAccessBlockStore blockStore;
|
||||
|
||||
blockstore::BlockId CreateBlockReturnKey(const Data &initData) {
|
||||
return blockStore.create(initData)->blockId();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
||||
TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_zerovirtual) {
|
||||
auto blockId = CreateBlockReturnKey(Data(0));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(base->size()));
|
||||
}
|
||||
|
||||
TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_negativeboundaries) {
|
||||
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
|
||||
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
|
||||
auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value()->size();
|
||||
if (physicalSizeForVirtualSizeZero > 0) {
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1));
|
||||
}
|
||||
EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero));
|
||||
EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1));
|
||||
}
|
||||
|
||||
TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_positive) {
|
||||
auto blockId = CreateBlockReturnKey(Data(10*1024));
|
||||
auto base = baseBlockStore->load(blockId).value();
|
||||
EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(base->size()));
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#include "blockstore/implementations/testfake/FakeBlock.h"
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include "../../testutils/BlockStoreTest.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using blockstore::BlockStore;
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
class FakeBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||
public:
|
||||
unique_ref<BlockStore> createBlockStore() override {
|
||||
return make_unique_ref<FakeBlockStore>();
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(TestFake, BlockStoreTest, FakeBlockStoreTestFixture);
|
@ -1,145 +0,0 @@
|
||||
#include "blockstore/interface/BlockStore2.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::Return;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Eq;
|
||||
using ::testing::ByRef;
|
||||
|
||||
using std::string;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using boost::optional;
|
||||
|
||||
namespace boost {
|
||||
inline void PrintTo(const optional<cpputils::Data> &, ::std::ostream *os) {
|
||||
*os << "optional<Data>";
|
||||
}
|
||||
}
|
||||
|
||||
using namespace blockstore;
|
||||
|
||||
class BlockStore2Mock: public BlockStore2 {
|
||||
public:
|
||||
MOCK_METHOD(BlockId, createBlockId, (), (const, override));
|
||||
MOCK_METHOD(bool, tryCreate, (const BlockId &blockId, const cpputils::Data &data), (override));
|
||||
MOCK_METHOD(void, store, (const BlockId &, const Data &data), (override));
|
||||
MOCK_METHOD(optional<Data>, load, (const BlockId &), (const, override));
|
||||
MOCK_METHOD(bool, remove, (const BlockId &), (override));
|
||||
MOCK_METHOD(uint64_t, numBlocks, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, estimateNumFreeBytes, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, blockSizeFromPhysicalBlockSize, (uint64_t), (const, override));
|
||||
MOCK_METHOD(void, forEachBlock, (std::function<void (const blockstore::BlockId &)>), (const, override));
|
||||
};
|
||||
|
||||
class BlockStore2Test: public Test {
|
||||
public:
|
||||
BlockStore2Test() :blockStoreMock(), blockStore(blockStoreMock),
|
||||
blockId1(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")),
|
||||
blockId2(BlockId::FromString("AC772971491BB4932A389EE14BC7090A")),
|
||||
blockId3(BlockId::FromString("1BB4932A38AC77C7090A2971499EE14B")) {}
|
||||
|
||||
BlockStore2Mock blockStoreMock;
|
||||
BlockStore2 &blockStore;
|
||||
const BlockId blockId1;
|
||||
const BlockId blockId2;
|
||||
const BlockId blockId3;
|
||||
|
||||
Data createDataWithSize(size_t size) {
|
||||
Data fixture(DataFixture::generate(size));
|
||||
Data data(size);
|
||||
std::memcpy(data.data(), fixture.data(), size);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BlockStore2Test, DataIsPassedThrough0) {
|
||||
Data data = createDataWithSize(0);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(Return(true));
|
||||
EXPECT_EQ(blockId1, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, DataIsPassedThrough1) {
|
||||
Data data = createDataWithSize(1);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(Return(true));
|
||||
EXPECT_EQ(blockId1, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, DataIsPassedThrough1024) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(Return(true));
|
||||
EXPECT_EQ(blockId1, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, BlockIdIsCorrect) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(blockId1, testing::_)).WillOnce(Return(true));
|
||||
EXPECT_EQ(blockId1, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, TwoBlocksGetDifferentIds) {
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, testing::_))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return true;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return true;
|
||||
}));
|
||||
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_EQ(blockId1, blockStore.create(data));
|
||||
EXPECT_EQ(blockId2, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, WillTryADifferentIdIfKeyAlreadyExists) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data))))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return false;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return true;
|
||||
}));
|
||||
|
||||
EXPECT_EQ(blockId2, blockStore.create(data));
|
||||
}
|
||||
|
||||
TEST_F(BlockStore2Test, WillTryADifferentIdIfIdAlreadyExistsTwoTimes) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2))
|
||||
.WillOnce(Return(blockId3));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data))))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return false;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return false;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, const Data &) {
|
||||
EXPECT_EQ(blockId3, blockId);
|
||||
return true;
|
||||
}));
|
||||
|
||||
EXPECT_EQ(blockId3, blockStore.create(data));
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
#include "blockstore/interface/BlockStore.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::Return;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Eq;
|
||||
using ::testing::ByRef;
|
||||
using ::testing::Action;
|
||||
|
||||
using std::string;
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
using boost::optional;
|
||||
|
||||
using namespace blockstore;
|
||||
|
||||
class BlockStoreMock: public BlockStore {
|
||||
public:
|
||||
MOCK_METHOD(BlockId, createBlockId, (), (override));
|
||||
MOCK_METHOD(optional<unique_ref<Block>>, tryCreate, (const BlockId &, Data data), (override));
|
||||
MOCK_METHOD(unique_ref<Block>, overwrite, (const BlockId &, Data data), (override));
|
||||
MOCK_METHOD(optional<unique_ref<Block>>, load, (const BlockId &), (override));
|
||||
MOCK_METHOD(void, remove, (unique_ref<Block>), (override));
|
||||
MOCK_METHOD(void, remove, (const BlockId &), (override));
|
||||
MOCK_METHOD(uint64_t, numBlocks, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, estimateNumFreeBytes, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, blockSizeFromPhysicalBlockSize, (uint64_t), (const, override));
|
||||
MOCK_METHOD(void, forEachBlock, (std::function<void (const blockstore::BlockId &)>), (const, override));
|
||||
};
|
||||
|
||||
class BlockMock: public Block {
|
||||
public:
|
||||
BlockMock(): Block(BlockId::Random()) {}
|
||||
MOCK_METHOD(const void*, data, (), (const, override));
|
||||
MOCK_METHOD(void, write, (const void*, uint64_t, uint64_t), (override));
|
||||
MOCK_METHOD(void, flush, (), (override));
|
||||
MOCK_METHOD(size_t, size, (), (const, override));
|
||||
MOCK_METHOD(void, resize, (size_t), (override));
|
||||
};
|
||||
|
||||
class BlockStoreTest: public Test {
|
||||
public:
|
||||
BlockStoreTest() :blockStoreMock(), blockStore(blockStoreMock),
|
||||
blockId1(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")),
|
||||
blockId2(BlockId::FromString("AC772971491BB4932A389EE14BC7090A")),
|
||||
blockId3(BlockId::FromString("1BB4932A38AC77C7090A2971499EE14B")) {}
|
||||
|
||||
BlockStoreMock blockStoreMock;
|
||||
BlockStore &blockStore;
|
||||
const BlockId blockId1;
|
||||
const BlockId blockId2;
|
||||
const BlockId blockId3;
|
||||
|
||||
Data createDataWithSize(size_t size) {
|
||||
Data fixture(DataFixture::generate(size));
|
||||
Data data(size);
|
||||
std::memcpy(data.data(), fixture.data(), size);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const Action<optional<unique_ref<Block>>(const BlockId &, cpputils::Data)> ReturnNewBlockMock = Invoke(
|
||||
[] (const BlockId&, cpputils::Data) {
|
||||
return optional<unique_ref<Block>>(unique_ref<Block>(make_unique_ref<BlockMock>()));
|
||||
});
|
||||
|
||||
TEST_F(BlockStoreTest, DataIsPassedThrough0) {
|
||||
Data data = createDataWithSize(0);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock);
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, DataIsPassedThrough1) {
|
||||
Data data = createDataWithSize(1);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock);
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, DataIsPassedThrough1024) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock);
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, BlockIdIsCorrect) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(blockId1, testing::_)).WillOnce(ReturnNewBlockMock);
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, TwoBlocksGetDifferentIds) {
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, testing::_))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return optional<unique_ref<Block>>(unique_ref<Block>(make_unique_ref<BlockMock>()));
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return optional<unique_ref<Block>>(unique_ref<Block>(make_unique_ref<BlockMock>()));
|
||||
}));
|
||||
|
||||
Data data = createDataWithSize(1024);
|
||||
blockStore.create(data);
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, WillTryADifferentIdIfKeyAlreadyExists) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data))))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data ) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return boost::none;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data ) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return optional<unique_ref<Block>>(unique_ref<Block>(make_unique_ref<BlockMock>()));
|
||||
}));
|
||||
|
||||
blockStore.create(data);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreTest, WillTryADifferentIdIfIdAlreadyExistsTwoTimes) {
|
||||
Data data = createDataWithSize(1024);
|
||||
EXPECT_CALL(blockStoreMock, createBlockId())
|
||||
.WillOnce(Return(blockId1))
|
||||
.WillOnce(Return(blockId2))
|
||||
.WillOnce(Return(blockId3));
|
||||
EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data))))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data) {
|
||||
EXPECT_EQ(blockId1, blockId);
|
||||
return boost::none;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data) {
|
||||
EXPECT_EQ(blockId2, blockId);
|
||||
return boost::none;
|
||||
}))
|
||||
.WillOnce(Invoke([this](const BlockId &blockId, Data) {
|
||||
EXPECT_EQ(blockId3, blockId);
|
||||
return optional<unique_ref<Block>>(unique_ref<Block>(make_unique_ref<BlockMock>()));
|
||||
}));
|
||||
|
||||
blockStore.create(data);
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
/*
|
||||
* Tests that the header can be included without needing additional header includes as dependencies.
|
||||
*/
|
||||
#include "blockstore/interface/Block.h"
|
@ -1,479 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
#include "blockstore/interface/BlockStore2.h"
|
||||
|
||||
namespace boost {
|
||||
inline void PrintTo(const optional<cpputils::Data> &, ::std::ostream *os) {
|
||||
*os << "optional<Data>";
|
||||
}
|
||||
}
|
||||
|
||||
class BlockStore2TestFixture {
|
||||
public:
|
||||
virtual ~BlockStore2TestFixture() {}
|
||||
virtual cpputils::unique_ref<blockstore::BlockStore2> createBlockStore() = 0;
|
||||
};
|
||||
|
||||
template<class ConcreteBlockStoreTestFixture>
|
||||
class BlockStore2Test: public ::testing::Test {
|
||||
public:
|
||||
BlockStore2Test() :fixture(), blockStore(this->fixture.createBlockStore()) {}
|
||||
|
||||
BOOST_STATIC_ASSERT_MSG(
|
||||
(std::is_base_of<BlockStore2TestFixture, ConcreteBlockStoreTestFixture>::value),
|
||||
"Given test fixture for instantiating the (type parameterized) BlockStoreTest must inherit from BlockStoreTestFixture"
|
||||
);
|
||||
|
||||
ConcreteBlockStoreTestFixture fixture;
|
||||
cpputils::unique_ref<blockstore::BlockStore2> blockStore;
|
||||
|
||||
template<class Entry>
|
||||
void EXPECT_UNORDERED_EQ(const std::vector<Entry> &expected, std::vector<Entry> actual) {
|
||||
EXPECT_EQ(expected.size(), actual.size());
|
||||
for (const Entry &expectedEntry : expected) {
|
||||
removeOne(&actual, expectedEntry);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Entry>
|
||||
void removeOne(std::vector<Entry> *entries, const Entry &toRemove) {
|
||||
auto found = std::find(entries->begin(), entries->end(), toRemove);
|
||||
if (found != entries->end()) {
|
||||
entries->erase(found);
|
||||
return;
|
||||
}
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(BlockStore2Test);
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_FALSE(this->blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_NE(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
this->blockStore->store(blockId, cpputils::Data(1024));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
this->blockStore->store(blockId, cpputils::Data(1024));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
this->blockStore->store(blockId, cpputils::Data(1024));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys) {
|
||||
blockstore::BlockId blockId1 = this->blockStore->create(cpputils::Data(1024));
|
||||
blockstore::BlockId blockId2 = this->blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_NE(blockId1, blockId2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_NE(boost::none, this->blockStore->load(blockId));
|
||||
EXPECT_TRUE(this->blockStore->remove(blockId));
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
EXPECT_NE(boost::none, this->blockStore->load(blockId));
|
||||
EXPECT_TRUE(this->blockStore->remove(blockId));
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_TRUE(this->blockStore->remove(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) {
|
||||
blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024));
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
EXPECT_EQ(true, this->blockStore->remove(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto result = this->blockStore->remove(blockId);
|
||||
EXPECT_EQ(false, result);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingNonexistingBlock_thenFails) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973");
|
||||
blockstore::BlockId differentKey = blockstore::BlockId::FromString("290AC2C7097274A389EE14B91B72B493");
|
||||
ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
EXPECT_EQ(false, this->blockStore->remove(differentKey));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
|
||||
auto blockId = this->blockStore->create(cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
auto blockId = this->blockStore->create(cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
auto blockId = this->blockStore->create(data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(loaded, data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
auto blockId = this->blockStore->create(data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(loaded, data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973");
|
||||
ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973");
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973");
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
ASSERT_TRUE(this->blockStore->tryCreate(blockId, data.copy()));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(loaded, data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973");
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
ASSERT_TRUE(this->blockStore->tryCreate(blockId, data.copy()));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(loaded, data);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
this->blockStore->store(blockId, cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
this->blockStore->store(blockId, cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
this->blockStore->store(blockId, data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(data, loaded);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
this->blockStore->store(blockId, data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(data, loaded);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) {
|
||||
auto blockId = this->blockStore->create(cpputils::Data(512));
|
||||
this->blockStore->store(blockId, cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
auto blockId = this->blockStore->create(cpputils::Data(512));
|
||||
this->blockStore->store(blockId, cpputils::Data(0));
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0u, loaded.size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
auto blockId = this->blockStore->create(cpputils::Data(512));
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
this->blockStore->store(blockId, data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(data, loaded);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
auto blockId = this->blockStore->create(cpputils::Data(512));
|
||||
cpputils::Data data = cpputils::DataFixture::generate(1024);
|
||||
this->blockStore->store(blockId, data.copy());
|
||||
auto loaded = this->blockStore->load(blockId).value();
|
||||
EXPECT_EQ(data, loaded);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) {
|
||||
this->blockStore->create(cpputils::Data(512));
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_EQ(boost::none, this->blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectOnEmptyBlockstore) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterCreatingOneBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterRemovingTheLastBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockStore->create(cpputils::Data(1));
|
||||
EXPECT_TRUE(blockStore->remove(blockId));
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterCreatingTwoBlocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
blockStore->create(cpputils::Data(0));
|
||||
EXPECT_EQ(2u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterRemovingABlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockStore->create(cpputils::Data(1));
|
||||
blockStore->create(cpputils::Data(1));
|
||||
EXPECT_TRUE(blockStore->remove(blockId));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterStoringANewBlock) {
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->store(blockId, cpputils::Data(1));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterStoringAnExistingBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockStore->create(cpputils::Data(1));
|
||||
blockStore->store(blockId, cpputils::Data(1));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_zeroblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_oneblock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({blockId}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_twoblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId1 = blockStore->create(cpputils::Data(1));
|
||||
auto blockId2 = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({blockId1, blockId2}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_threeblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId1 = blockStore->create(cpputils::Data(1));
|
||||
auto blockId2 = blockStore->create(cpputils::Data(1));
|
||||
auto blockId3 = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({blockId1, blockId2, blockId3}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_doesntListRemovedBlocks_oneblock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId1 = blockStore->create(cpputils::Data(1));
|
||||
EXPECT_TRUE(blockStore->remove(blockId1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, ForEachBlock_doesntListRemovedBlocks_twoblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId1 = blockStore->create(cpputils::Data(1));
|
||||
auto blockId2 = blockStore->create(cpputils::Data(1));
|
||||
EXPECT_TRUE(blockStore->remove(blockId1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({blockId2}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndSameSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndDifferentSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(4096)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
}
|
||||
/*
|
||||
TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024)));
|
||||
EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndBothNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(0)));
|
||||
}*/
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(BlockStore2Test,
|
||||
givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails,
|
||||
givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds,
|
||||
givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds,
|
||||
givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds,
|
||||
givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails,
|
||||
givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails,
|
||||
givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds,
|
||||
givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds,
|
||||
givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds,
|
||||
givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys,
|
||||
givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore,
|
||||
givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore,
|
||||
givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds,
|
||||
givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds,
|
||||
givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails,
|
||||
givenNonEmptyBlockStore_whenRemovingNonexistingBlock_thenFails,
|
||||
givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads,
|
||||
givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails,
|
||||
givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails,
|
||||
NumBlocksIsCorrectOnEmptyBlockstore,
|
||||
NumBlocksIsCorrectAfterCreatingOneBlock,
|
||||
NumBlocksIsCorrectAfterRemovingTheLastBlock,
|
||||
NumBlocksIsCorrectAfterCreatingTwoBlocks,
|
||||
NumBlocksIsCorrectAfterRemovingABlock,
|
||||
NumBlocksIsCorrectAfterStoringANewBlock,
|
||||
NumBlocksIsCorrectAfterStoringAnExistingBlock,
|
||||
ForEachBlock_zeroblocks,
|
||||
ForEachBlock_oneblock,
|
||||
ForEachBlock_twoblocks,
|
||||
ForEachBlock_threeblocks,
|
||||
ForEachBlock_doesntListRemovedBlocks_oneblock,
|
||||
ForEachBlock_doesntListRemovedBlocks_twoblocks,
|
||||
TryCreateTwoBlocksWithSameBlockIdAndSameSize,
|
||||
TryCreateTwoBlocksWithSameBlockIdAndDifferentSize,
|
||||
TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize
|
||||
//TODO Just disabled because gtest doesn't allow more template parameters. Fix and reenable!
|
||||
// see https://github.com/google/googletest/issues/1267
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize,
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndBothNullSize
|
||||
);
|
||||
|
||||
|
||||
#endif
|
@ -1,406 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORETEST_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORETEST_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||
|
||||
#include "blockstore/interface/BlockStore.h"
|
||||
|
||||
class MockForEachBlockCallback final {
|
||||
public:
|
||||
std::function<void (const blockstore::BlockId &)> callback() {
|
||||
return [this] (const blockstore::BlockId &blockId) {
|
||||
called_with.push_back(blockId);
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<blockstore::BlockId> called_with;
|
||||
};
|
||||
|
||||
class BlockStoreTestFixture {
|
||||
public:
|
||||
virtual ~BlockStoreTestFixture() {}
|
||||
virtual cpputils::unique_ref<blockstore::BlockStore> createBlockStore() = 0;
|
||||
};
|
||||
|
||||
template<class ConcreteBlockStoreTestFixture>
|
||||
class BlockStoreTest: public ::testing::Test {
|
||||
public:
|
||||
BlockStoreTest() :fixture() {}
|
||||
|
||||
BOOST_STATIC_ASSERT_MSG(
|
||||
(std::is_base_of<BlockStoreTestFixture, ConcreteBlockStoreTestFixture>::value),
|
||||
"Given test fixture for instantiating the (type parameterized) BlockStoreTest must inherit from BlockStoreTestFixture"
|
||||
);
|
||||
|
||||
ConcreteBlockStoreTestFixture fixture;
|
||||
|
||||
void TestBlockIsUsable(cpputils::unique_ref<blockstore::Block> block, blockstore::BlockStore *blockStore) {
|
||||
// Write full block space and check it was correctly written
|
||||
cpputils::Data fixture = cpputils::DataFixture::generate(block->size());
|
||||
block->write(fixture.data(), 0, fixture.size());
|
||||
EXPECT_EQ(0, std::memcmp(fixture.data(), block->data(), fixture.size()));
|
||||
|
||||
// Store and reload block and check data is still correct
|
||||
auto blockId = block->blockId();
|
||||
cpputils::destruct(std::move(block));
|
||||
block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(fixture.data(), block->data(), fixture.size()));
|
||||
}
|
||||
|
||||
template<class Entry>
|
||||
void EXPECT_UNORDERED_EQ(const std::vector<Entry> &expected, std::vector<Entry> actual) {
|
||||
EXPECT_EQ(expected.size(), actual.size());
|
||||
for (const Entry &expectedEntry : expected) {
|
||||
removeOne(&actual, expectedEntry);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Entry>
|
||||
void removeOne(std::vector<Entry> *entries, const Entry &toRemove) {
|
||||
auto found = std::find(entries->begin(), entries->end(), toRemove);
|
||||
if (found != entries->end()) {
|
||||
entries->erase(found);
|
||||
return;
|
||||
}
|
||||
EXPECT_TRUE(false);
|
||||
}
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(BlockStoreTest);
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, TwoCreatedBlocksHaveDifferentBlockIds) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1024));
|
||||
auto block2 = blockStore->create(cpputils::Data(1024));
|
||||
EXPECT_NE(block1->blockId(), block2->blockId());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, BlockIsNotLoadableAfterDeleting_DeleteByBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId = blockStore->create(cpputils::Data(1024))->blockId();
|
||||
auto block = blockStore->load(blockId);
|
||||
EXPECT_NE(boost::none, block);
|
||||
blockStore->remove(std::move(*block));
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, BlockIsNotLoadableAfterDeleting_DeleteByBlockId) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId = blockStore->create(cpputils::Data(1024))->blockId();
|
||||
blockStore->remove(blockId);
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectOnEmptyBlockstore) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(1));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock_AfterClosingBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(1));
|
||||
blockStore->remove(std::move(block));
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlockId) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId = blockStore->create(cpputils::Data(1))->blockId();
|
||||
blockStore->remove(blockId);
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
auto block2 = blockStore->create(cpputils::Data(0));
|
||||
EXPECT_EQ(2u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingFirstBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
auto block2 = blockStore->create(cpputils::Data(0));
|
||||
EXPECT_EQ(2u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingSecondBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
blockStore->create(cpputils::Data(0));
|
||||
EXPECT_EQ(2u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingBothBlocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
blockStore->create(cpputils::Data(0));
|
||||
EXPECT_EQ(2u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(1));
|
||||
blockStore->create(cpputils::Data(1));
|
||||
blockStore->remove(std::move(block));
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlockId) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto blockId = blockStore->create(cpputils::Data(1))->blockId();
|
||||
blockStore->create(cpputils::Data(1));
|
||||
blockStore->remove(blockId);
|
||||
EXPECT_EQ(1u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, CanRemoveModifiedBlock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(5));
|
||||
block->write("data", 0, 4);
|
||||
blockStore->remove(std::move(block));
|
||||
EXPECT_EQ(0u, blockStore->numBlocks());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_zeroblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_oneblock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({block->blockId()}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_twoblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
auto block2 = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({block1->blockId(), block2->blockId()}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_threeblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
auto block2 = blockStore->create(cpputils::Data(1));
|
||||
auto block3 = blockStore->create(cpputils::Data(1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({block1->blockId(), block2->blockId(), block3->blockId()}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_doesntListRemovedBlocks_oneblock) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
blockStore->remove(std::move(block1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, ForEachBlock_doesntListRemovedBlocks_twoblocks) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block1 = blockStore->create(cpputils::Data(1));
|
||||
auto block2 = blockStore->create(cpputils::Data(1));
|
||||
blockStore->remove(std::move(block1));
|
||||
MockForEachBlockCallback mockForEachBlockCallback;
|
||||
blockStore->forEachBlock(mockForEachBlockCallback.callback());
|
||||
this->EXPECT_UNORDERED_EQ({block2->blockId()}, mockForEachBlockCallback.called_with);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Larger_FromZero) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(0));
|
||||
block->resize(10);
|
||||
EXPECT_EQ(10u, block->size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Larger_FromZero_BlockIsStillUsable) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(0));
|
||||
block->resize(10);
|
||||
this->TestBlockIsUsable(std::move(block), blockStore.get());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Larger) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(20);
|
||||
EXPECT_EQ(20u, block->size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Larger_BlockIsStillUsable) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(20);
|
||||
this->TestBlockIsUsable(std::move(block), blockStore.get());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Smaller) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(5);
|
||||
EXPECT_EQ(5u, block->size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Smaller_BlockIsStillUsable) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(5);
|
||||
this->TestBlockIsUsable(std::move(block), blockStore.get());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Smaller_ToZero) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(0);
|
||||
EXPECT_EQ(0u, block->size());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, Resize_Smaller_ToZero_BlockIsStillUsable) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
auto block = blockStore->create(cpputils::Data(10));
|
||||
block->resize(0);
|
||||
this->TestBlockIsUsable(std::move(block), blockStore.get());
|
||||
}
|
||||
/*
|
||||
TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndSameSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto block = blockStore->tryCreate(blockId, cpputils::Data(1024));
|
||||
(*block)->flush(); //TODO Ideally, flush shouldn't be necessary here.
|
||||
auto block2 = blockStore->tryCreate(blockId, cpputils::Data(1024));
|
||||
EXPECT_NE(boost::none, block);
|
||||
EXPECT_EQ(boost::none, block2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndDifferentSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto block = blockStore->tryCreate(blockId, cpputils::Data(1024));
|
||||
(*block)->flush(); //TODO Ideally, flush shouldn't be necessary here.
|
||||
auto block2 = blockStore->tryCreate(blockId, cpputils::Data(4096));
|
||||
EXPECT_NE(boost::none, block);
|
||||
EXPECT_EQ(boost::none, block2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto block = blockStore->tryCreate(blockId, cpputils::Data(0));
|
||||
(*block)->flush(); //TODO Ideally, flush shouldn't be necessary here.
|
||||
auto block2 = blockStore->tryCreate(blockId, cpputils::Data(1024));
|
||||
EXPECT_NE(boost::none, block);
|
||||
EXPECT_EQ(boost::none, block2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto block = blockStore->tryCreate(blockId, cpputils::Data(1024));
|
||||
(*block)->flush(); //TODO Ideally, flush shouldn't be necessary here.
|
||||
auto block2 = blockStore->tryCreate(blockId, cpputils::Data(0));
|
||||
EXPECT_NE(boost::none, block);
|
||||
EXPECT_EQ(boost::none, block2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndBothNullSize) {
|
||||
auto blockStore = this->fixture.createBlockStore();
|
||||
blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
auto block = blockStore->tryCreate(blockId, cpputils::Data(0));
|
||||
(*block)->flush(); //TODO Ideally, flush shouldn't be necessary here.
|
||||
auto block2 = blockStore->tryCreate(blockId, cpputils::Data(0));
|
||||
EXPECT_NE(boost::none, block);
|
||||
EXPECT_EQ(boost::none, block2);
|
||||
}*/
|
||||
|
||||
#include "BlockStoreTest_Size.h"
|
||||
#include "BlockStoreTest_Data.h"
|
||||
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(BlockStoreTest,
|
||||
CreatedBlockHasCorrectSize,
|
||||
LoadingUnchangedBlockHasCorrectSize,
|
||||
CreatedBlockData,
|
||||
LoadingUnchangedBlockData,
|
||||
LoadedBlockIsCorrect,
|
||||
// LoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing,
|
||||
AfterCreate_FlushingDoesntChangeBlock,
|
||||
AfterLoad_FlushingDoesntChangeBlock,
|
||||
AfterCreate_FlushesWhenDestructed,
|
||||
AfterLoad_FlushesWhenDestructed,
|
||||
LoadNonExistingBlock,
|
||||
TwoCreatedBlocksHaveDifferentBlockIds,
|
||||
BlockIsNotLoadableAfterDeleting_DeleteByBlock,
|
||||
BlockIsNotLoadableAfterDeleting_DeleteByBlockId,
|
||||
NumBlocksIsCorrectOnEmptyBlockstore,
|
||||
NumBlocksIsCorrectAfterAddingOneBlock,
|
||||
NumBlocksIsCorrectAfterAddingOneBlock_AfterClosingBlock,
|
||||
NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlock,
|
||||
NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlockId,
|
||||
NumBlocksIsCorrectAfterAddingTwoBlocks,
|
||||
NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingFirstBlock,
|
||||
NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingSecondBlock,
|
||||
NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingBothBlocks,
|
||||
NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlock,
|
||||
NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlockId,
|
||||
WriteAndReadImmediately,
|
||||
WriteAndReadAfterLoading,
|
||||
WriteTwiceAndRead,
|
||||
OverwriteSameSizeAndReadImmediately,
|
||||
OverwriteSameSizeAndReadAfterLoading,
|
||||
OverwriteSmallerSizeAndReadImmediately,
|
||||
OverwriteSmallerSizeAndReadAfterLoading,
|
||||
OverwriteLargerSizeAndReadAfterLoading,
|
||||
OverwriteLargerSizeAndReadImmediately,
|
||||
OverwriteNonexistingAndReadAfterLoading,
|
||||
OverwriteNonexistingAndReadImmediately,
|
||||
CanRemoveModifiedBlock,
|
||||
ForEachBlock_zeroblocks,
|
||||
ForEachBlock_oneblock,
|
||||
ForEachBlock_twoblocks,
|
||||
ForEachBlock_threeblocks,
|
||||
ForEachBlock_doesntListRemovedBlocks_oneblock,
|
||||
ForEachBlock_doesntListRemovedBlocks_twoblocks,
|
||||
Resize_Larger_FromZero,
|
||||
Resize_Larger_FromZero_BlockIsStillUsable,
|
||||
Resize_Larger,
|
||||
Resize_Larger_BlockIsStillUsable,
|
||||
Resize_Smaller,
|
||||
Resize_Smaller_BlockIsStillUsable,
|
||||
Resize_Smaller_ToZero,
|
||||
Resize_Smaller_ToZero_BlockIsStillUsable
|
||||
//TODO Just disabled because gtest doesn't allow more template parameters. Fix and reenable!
|
||||
// see https://github.com/google/googletest/issues/1267
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndSameSize,
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndDifferentSize,
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize,
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize,
|
||||
//TryCreateTwoBlocksWithSameBlockIdAndBothNullSize,
|
||||
);
|
||||
|
||||
|
||||
#endif
|
@ -1,175 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_DATA_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_DATA_H_
|
||||
|
||||
// This file is meant to be included by BlockStoreTest.h only
|
||||
|
||||
struct DataRange {
|
||||
uint64_t blocksize;
|
||||
uint64_t offset;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
class BlockStoreDataParametrizedTest {
|
||||
public:
|
||||
BlockStoreDataParametrizedTest(cpputils::unique_ref<blockstore::BlockStore> blockStore_, const DataRange &testData_)
|
||||
: blockStore(std::move(blockStore_)),
|
||||
testData(testData_),
|
||||
foregroundData(cpputils::DataFixture::generate(testData.count, 0)),
|
||||
backgroundData(cpputils::DataFixture::generate(testData.blocksize, 1)) {
|
||||
}
|
||||
|
||||
void TestWriteAndReadImmediately() {
|
||||
auto block = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes());
|
||||
block->write(foregroundData.data(), testData.offset, testData.count);
|
||||
|
||||
EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*block, testData.offset, testData.count);
|
||||
}
|
||||
|
||||
void TestWriteAndReadAfterLoading() {
|
||||
blockstore::BlockId blockId = CreateBlockWriteToItAndReturnKey(foregroundData);
|
||||
|
||||
auto loaded_block = blockStore->load(blockId).value();
|
||||
EXPECT_DATA_READS_AS(foregroundData, *loaded_block, testData.offset, testData.count);
|
||||
EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded_block, testData.offset, testData.count);
|
||||
}
|
||||
|
||||
void TestWriteTwiceAndRead() {
|
||||
auto block = blockStore->create(cpputils::Data(testData.blocksize));
|
||||
block->write(backgroundData.data(), 0, testData.blocksize);
|
||||
block->write(foregroundData.data(), testData.offset, testData.count);
|
||||
EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count);
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(backgroundData, *block, testData.offset, testData.count);
|
||||
}
|
||||
|
||||
void TestOverwriteSameSizeAndReadImmediately() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId();
|
||||
auto block = blockStore->overwrite(blockId, backgroundData.copy());
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
void TestOverwriteSameSizeAndReadAfterLoading() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId();
|
||||
blockStore->overwrite(blockId, backgroundData.copy());
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
void TestOverwriteSmallerSizeAndReadImmediately() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId();
|
||||
auto block = blockStore->overwrite(blockId, foregroundData.copy());
|
||||
EXPECT_EQ(testData.count, block->size());
|
||||
EXPECT_DATA_READS_AS(foregroundData, *block, 0, testData.count);
|
||||
}
|
||||
|
||||
void TestOverwriteSmallerSizeAndReadAfterLoading() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId();
|
||||
blockStore->overwrite(blockId, foregroundData.copy());
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(testData.count, block->size());
|
||||
EXPECT_DATA_READS_AS(foregroundData, *block, 0, testData.count);
|
||||
}
|
||||
|
||||
void TestOverwriteLargerSizeAndReadImmediately() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.count))->blockId();
|
||||
auto block = blockStore->overwrite(blockId, backgroundData.copy());
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
void TestOverwriteLargerSizeAndReadAfterLoading() {
|
||||
auto blockId = blockStore->create(cpputils::Data(testData.count))->blockId();
|
||||
blockStore->overwrite(blockId, backgroundData.copy());
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
void TestOverwriteNonexistingAndReadImmediately() {
|
||||
auto blockId = blockStore->createBlockId();
|
||||
auto block = blockStore->overwrite(blockId, backgroundData.copy());
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
void TestOverwriteNonexistingAndReadAfterLoading() {
|
||||
auto blockId = blockStore->createBlockId();
|
||||
blockStore->overwrite(blockId, backgroundData.copy());
|
||||
auto block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(testData.blocksize, block->size());
|
||||
EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize);
|
||||
}
|
||||
|
||||
private:
|
||||
cpputils::unique_ref<blockstore::BlockStore> blockStore;
|
||||
DataRange testData;
|
||||
cpputils::Data foregroundData;
|
||||
cpputils::Data backgroundData;
|
||||
|
||||
blockstore::BlockId CreateBlockWriteToItAndReturnKey(const cpputils::Data &to_write) {
|
||||
auto newblock = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes());
|
||||
|
||||
newblock->write(to_write.data(), testData.offset, testData.count);
|
||||
return newblock->blockId();
|
||||
}
|
||||
|
||||
void EXPECT_DATA_READS_AS(const cpputils::Data &expected, const blockstore::Block &block, uint64_t offset, uint64_t count) {
|
||||
cpputils::Data read(count);
|
||||
std::memcpy(read.data(), static_cast<const uint8_t*>(block.data()) + offset, count);
|
||||
EXPECT_EQ(expected, read);
|
||||
}
|
||||
|
||||
void EXPECT_DATA_READS_AS_OUTSIDE_OF(const cpputils::Data &expected, const blockstore::Block &block, uint64_t start, uint64_t count) {
|
||||
cpputils::Data begin(start);
|
||||
cpputils::Data end(testData.blocksize - count - start);
|
||||
|
||||
std::memcpy(begin.data(), expected.data(), start);
|
||||
std::memcpy(end.data(), expected.dataOffset(start+count), end.size());
|
||||
|
||||
EXPECT_DATA_READS_AS(begin, block, 0, start);
|
||||
EXPECT_DATA_READS_AS(end, block, start + count, end.size());
|
||||
}
|
||||
|
||||
void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const blockstore::Block &block, uint64_t start, uint64_t count) {
|
||||
cpputils::Data ZEROES(testData.blocksize);
|
||||
ZEROES.FillWithZeroes();
|
||||
EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, block, start, count);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<DataRange> DATA_RANGES() {
|
||||
return {
|
||||
DataRange{1024, 0, 1024}, // full size leaf, access beginning to end
|
||||
DataRange{1024, 100, 1024 - 200}, // full size leaf, access middle to middle
|
||||
DataRange{1024, 0, 1024 - 100}, // full size leaf, access beginning to middle
|
||||
DataRange{1024, 100, 1024 - 100}, // full size leaf, access middle to end
|
||||
DataRange{1024 - 100, 0, 1024 - 100}, // non-full size leaf, access beginning to end
|
||||
DataRange{1024 - 100, 100, 1024 - 300}, // non-full size leaf, access middle to middle
|
||||
DataRange{1024 - 100, 0, 1024 - 200}, // non-full size leaf, access beginning to middle
|
||||
DataRange{1024 - 100, 100, 1024 - 200} // non-full size leaf, access middle to end
|
||||
};
|
||||
};
|
||||
#define TYPED_TEST_P_FOR_ALL_DATA_RANGES(TestName) \
|
||||
TYPED_TEST_P(BlockStoreTest, TestName) { \
|
||||
for (auto dataRange: DATA_RANGES()) { \
|
||||
BlockStoreDataParametrizedTest(this->fixture.createBlockStore(), dataRange) \
|
||||
.Test##TestName(); \
|
||||
} \
|
||||
}
|
||||
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteAndReadImmediately);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteAndReadAfterLoading);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteTwiceAndRead);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSameSizeAndReadImmediately);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSameSizeAndReadAfterLoading);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSmallerSizeAndReadImmediately);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSmallerSizeAndReadAfterLoading);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteLargerSizeAndReadImmediately);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteLargerSizeAndReadAfterLoading);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteNonexistingAndReadImmediately);
|
||||
TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteNonexistingAndReadAfterLoading);
|
||||
|
||||
#endif
|
@ -1,164 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_SIZE_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_SIZE_H_
|
||||
|
||||
// This file is meant to be included by BlockStoreTest.h only
|
||||
|
||||
#include <cpp-utils/data/Data.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
class BlockStoreSizeParameterizedTest {
|
||||
public:
|
||||
BlockStoreSizeParameterizedTest(cpputils::unique_ref<blockstore::BlockStore> blockStore_, size_t size_): blockStore(std::move(blockStore_)), size(size_) {}
|
||||
|
||||
void TestCreatedBlockHasCorrectSize() {
|
||||
auto block = CreateBlock();
|
||||
EXPECT_EQ(size, block->size());
|
||||
}
|
||||
|
||||
void TestLoadingUnchangedBlockHasCorrectSize() {
|
||||
blockstore::BlockId blockId = CreateBlock()->blockId();
|
||||
auto loaded_block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(size, loaded_block->size());
|
||||
}
|
||||
|
||||
void TestCreatedBlockData() {
|
||||
cpputils::Data data = cpputils::DataFixture::generate(size);
|
||||
auto block = blockStore->create(data);
|
||||
EXPECT_EQ(0, std::memcmp(data.data(), block->data(), size));
|
||||
}
|
||||
|
||||
void TestLoadingUnchangedBlockData() {
|
||||
cpputils::Data data = cpputils::DataFixture::generate(size);
|
||||
blockstore::BlockId blockId = blockStore->create(data)->blockId();
|
||||
auto loaded_block = blockStore->load(blockId).value();
|
||||
EXPECT_EQ(0, std::memcmp(data.data(), loaded_block->data(), size));
|
||||
}
|
||||
|
||||
void TestLoadedBlockIsCorrect() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
auto loaded_block = StoreDataToBlockAndLoadIt(randomData);
|
||||
EXPECT_EQ(size, loaded_block->size());
|
||||
EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size));
|
||||
}
|
||||
|
||||
void TestLoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
auto loaded_block = StoreDataToBlockAndLoadItDirectlyAfterFlushing(randomData);
|
||||
EXPECT_EQ(size, loaded_block->size());
|
||||
EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size));
|
||||
}
|
||||
|
||||
void TestAfterCreate_FlushingDoesntChangeBlock() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
auto block = CreateBlock();
|
||||
WriteDataToBlock(block.get(), randomData);
|
||||
block->flush();
|
||||
|
||||
EXPECT_BLOCK_DATA_CORRECT(*block, randomData);
|
||||
}
|
||||
|
||||
void TestAfterLoad_FlushingDoesntChangeBlock() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
auto block = CreateBlockAndLoadIt();
|
||||
WriteDataToBlock(block.get(), randomData);
|
||||
block->flush();
|
||||
|
||||
EXPECT_BLOCK_DATA_CORRECT(*block, randomData);
|
||||
}
|
||||
|
||||
void TestAfterCreate_FlushesWhenDestructed() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
blockstore::BlockId blockId = blockstore::BlockId::Null();
|
||||
{
|
||||
auto block = blockStore->create(cpputils::Data(size));
|
||||
blockId = block->blockId();
|
||||
WriteDataToBlock(block.get(), randomData);
|
||||
}
|
||||
auto loaded_block = blockStore->load(blockId).value();
|
||||
EXPECT_BLOCK_DATA_CORRECT(*loaded_block, randomData);
|
||||
}
|
||||
|
||||
void TestAfterLoad_FlushesWhenDestructed() {
|
||||
cpputils::Data randomData = cpputils::DataFixture::generate(size);
|
||||
blockstore::BlockId blockId = blockstore::BlockId::Null();
|
||||
{
|
||||
blockId = CreateBlock()->blockId();
|
||||
auto block = blockStore->load(blockId).value();
|
||||
WriteDataToBlock(block.get(), randomData);
|
||||
}
|
||||
auto loaded_block = blockStore->load(blockId).value();
|
||||
EXPECT_BLOCK_DATA_CORRECT(*loaded_block, randomData);
|
||||
}
|
||||
|
||||
void TestLoadNonExistingBlock() {
|
||||
EXPECT_EQ(boost::none, blockStore->load(blockId));
|
||||
}
|
||||
|
||||
private:
|
||||
const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972");
|
||||
cpputils::unique_ref<blockstore::BlockStore> blockStore;
|
||||
size_t size;
|
||||
|
||||
cpputils::Data ZEROES(size_t size) {
|
||||
cpputils::Data ZEROES(size);
|
||||
ZEROES.FillWithZeroes();
|
||||
return ZEROES;
|
||||
}
|
||||
|
||||
cpputils::unique_ref<blockstore::Block> StoreDataToBlockAndLoadIt(const cpputils::Data &data) {
|
||||
blockstore::BlockId blockId = StoreDataToBlockAndGetKey(data);
|
||||
return blockStore->load(blockId).value();
|
||||
}
|
||||
|
||||
blockstore::BlockId StoreDataToBlockAndGetKey(const cpputils::Data &data) {
|
||||
return blockStore->create(data)->blockId();
|
||||
}
|
||||
|
||||
cpputils::unique_ref<blockstore::Block> StoreDataToBlockAndLoadItDirectlyAfterFlushing(const cpputils::Data &data) {
|
||||
auto block = blockStore->create(data);
|
||||
block->flush();
|
||||
return blockStore->load(block->blockId()).value();
|
||||
}
|
||||
|
||||
cpputils::unique_ref<blockstore::Block> CreateBlockAndLoadIt() {
|
||||
blockstore::BlockId blockId = CreateBlock()->blockId();
|
||||
return blockStore->load(blockId).value();
|
||||
}
|
||||
|
||||
cpputils::unique_ref<blockstore::Block> CreateBlock() {
|
||||
return blockStore->create(cpputils::Data(size));
|
||||
}
|
||||
|
||||
void WriteDataToBlock(blockstore::Block *block, const cpputils::Data &randomData) {
|
||||
block->write(randomData.data(), 0, randomData.size());
|
||||
}
|
||||
|
||||
void EXPECT_BLOCK_DATA_CORRECT(const blockstore::Block &block, const cpputils::Data &randomData) {
|
||||
EXPECT_EQ(randomData.size(), block.size());
|
||||
EXPECT_EQ(0, std::memcmp(randomData.data(), block.data(), randomData.size()));
|
||||
}
|
||||
};
|
||||
|
||||
constexpr std::array<size_t, 5> SIZES = {{0, 1, 1024, 4096, 10*1024*1024}};
|
||||
#define TYPED_TEST_P_FOR_ALL_SIZES(TestName) \
|
||||
TYPED_TEST_P(BlockStoreTest, TestName) { \
|
||||
for (auto size: SIZES) { \
|
||||
BlockStoreSizeParameterizedTest(this->fixture.createBlockStore(), size) \
|
||||
.Test##TestName(); \
|
||||
} \
|
||||
} \
|
||||
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(CreatedBlockHasCorrectSize);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(LoadingUnchangedBlockHasCorrectSize);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(CreatedBlockData);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(LoadingUnchangedBlockData);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(LoadedBlockIsCorrect);
|
||||
//TYPED_TEST_P_FOR_ALL_SIZES(LoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(AfterCreate_FlushingDoesntChangeBlock);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(AfterLoad_FlushingDoesntChangeBlock);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(AfterCreate_FlushesWhenDestructed);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(AfterLoad_FlushesWhenDestructed);
|
||||
TYPED_TEST_P_FOR_ALL_SIZES(LoadNonExistingBlock);
|
||||
|
||||
#endif
|
@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_GTESTPRINTERS_H_
|
||||
#define MESSMER_BLOCKSTORE_TEST_TESTUTILS_GTESTPRINTERS_H_
|
||||
|
||||
namespace cpputils {
|
||||
|
||||
inline void PrintTo(const Data& /*data*/, ::std::ostream* os) {
|
||||
*os << "cpputils::Data";
|
||||
}
|
||||
|
||||
inline void PrintTo(const boost::optional<Data>& data, ::std::ostream* os) {
|
||||
if (data == boost::none) {
|
||||
*os << "none";
|
||||
} else {
|
||||
PrintTo(*data, os);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,111 +0,0 @@
|
||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include "blockstore/utils/BlockStoreUtils.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
using cpputils::Data;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::unique_ref;
|
||||
using cpputils::make_unique_ref;
|
||||
|
||||
using namespace blockstore;
|
||||
using namespace blockstore::utils;
|
||||
|
||||
using blockstore::testfake::FakeBlockStore;
|
||||
|
||||
class BlockStoreUtilsTest: public Test {
|
||||
public:
|
||||
unsigned int SIZE = 1024 * 1024;
|
||||
BlockStoreUtilsTest():
|
||||
ZEROES(SIZE),
|
||||
dataFixture(DataFixture::generate(SIZE)),
|
||||
blockStore(make_unique_ref<FakeBlockStore>()) {
|
||||
ZEROES.FillWithZeroes();
|
||||
}
|
||||
|
||||
Data ZEROES;
|
||||
Data dataFixture;
|
||||
unique_ref<BlockStore> blockStore;
|
||||
};
|
||||
|
||||
TEST_F(BlockStoreUtilsTest, FillWithZeroes) {
|
||||
auto block = blockStore->create(Data(SIZE));
|
||||
block->write(dataFixture.data(), 0, SIZE);
|
||||
EXPECT_NE(0, std::memcmp(ZEROES.data(), block->data(), SIZE));
|
||||
fillWithZeroes(block.get());
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), block->data(), SIZE));
|
||||
}
|
||||
|
||||
class BlockStoreUtilsTest_CopyToNewBlock: public BlockStoreUtilsTest {};
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyEmptyBlock) {
|
||||
auto block = blockStore->create(Data(0));
|
||||
auto block2 = copyToNewBlock(blockStore.get(), *block);
|
||||
|
||||
EXPECT_EQ(0u, block2->size());
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyZeroBlock) {
|
||||
auto block = blockStore->create(ZEROES);
|
||||
auto block2 = copyToNewBlock(blockStore.get(), *block);
|
||||
|
||||
EXPECT_EQ(SIZE, block2->size());
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), block2->data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyDataBlock) {
|
||||
auto block = blockStore->create(Data(SIZE));
|
||||
block->write(dataFixture.data(), 0, SIZE);
|
||||
auto block2 = copyToNewBlock(blockStore.get(), *block);
|
||||
|
||||
EXPECT_EQ(SIZE, block2->size());
|
||||
EXPECT_EQ(0, std::memcmp(dataFixture.data(), block2->data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToNewBlock, OriginalBlockUnchanged) {
|
||||
auto block = blockStore->create(Data(SIZE));
|
||||
block->write(dataFixture.data(), 0, SIZE);
|
||||
auto block2 = copyToNewBlock(blockStore.get(), *block);
|
||||
|
||||
EXPECT_EQ(SIZE, block->size());
|
||||
EXPECT_EQ(0, std::memcmp(dataFixture.data(), block->data(), SIZE));
|
||||
}
|
||||
|
||||
class BlockStoreUtilsTest_CopyToExistingBlock: public BlockStoreUtilsTest {};
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyEmptyBlock) {
|
||||
auto block = blockStore->create(Data(0));
|
||||
auto block2 = blockStore->create(Data(0));
|
||||
copyTo(block2.get(), *block);
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyZeroBlock) {
|
||||
auto block = blockStore->create(ZEROES);
|
||||
auto block2 = blockStore->create(Data(SIZE));
|
||||
block2->write(dataFixture.data(), 0, SIZE);
|
||||
copyTo(block2.get(), *block);
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(ZEROES.data(), block2->data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyDataBlock) {
|
||||
auto block = blockStore->create(Data(SIZE));
|
||||
block->write(dataFixture.data(), 0, SIZE);
|
||||
auto block2 = blockStore->create(Data(SIZE));
|
||||
copyTo(block2.get(), *block);
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(dataFixture.data(), block2->data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, OriginalBlockUnchanged) {
|
||||
auto block = blockStore->create(Data(SIZE));
|
||||
block->write(dataFixture.data(), 0, SIZE);
|
||||
auto block2 = blockStore->create(Data(SIZE));
|
||||
copyTo(block2.get(), *block);
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(dataFixture.data(), block->data(), SIZE));
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
project (cpp-utils-test)
|
||||
|
||||
set(SOURCES
|
||||
crypto/symmetric/CipherTest.cpp
|
||||
crypto/kdf/SCryptTest.cpp
|
||||
crypto/kdf/SCryptParametersTest.cpp
|
||||
crypto/hash/HashTest.cpp
|
||||
MacrosIncludeTest.cpp
|
||||
pointer/unique_ref_test.cpp
|
||||
pointer/cast_include_test.cpp
|
||||
pointer/cast_test.cpp
|
||||
pointer/unique_ref_boost_optional_gtest_workaround_include_test.cpp
|
||||
pointer/optional_ownership_ptr_include_test.cpp
|
||||
pointer/optional_ownership_ptr_test.cpp
|
||||
pointer/unique_ref_include_test.cpp
|
||||
process/daemonize_include_test.cpp
|
||||
process/subprocess_include_test.cpp
|
||||
process/SubprocessTest.cpp
|
||||
process/SignalCatcherTest.cpp
|
||||
process/SignalHandlerTest.cpp
|
||||
tempfile/TempFileTest.cpp
|
||||
tempfile/TempFileIncludeTest.cpp
|
||||
tempfile/TempDirIncludeTest.cpp
|
||||
tempfile/TempDirTest.cpp
|
||||
network/CurlHttpClientTest.cpp
|
||||
network/FakeHttpClientTest.cpp
|
||||
io/DontEchoStdinToStdoutRAIITest.cpp
|
||||
io/ConsoleIncludeTest.cpp
|
||||
io/ConsoleTest_AskYesNo.cpp
|
||||
io/ConsoleTest_Print.cpp
|
||||
io/ConsoleTest_Ask.cpp
|
||||
io/ConsoleTest_AskPassword.cpp
|
||||
io/ProgressBarTest.cpp
|
||||
random/RandomIncludeTest.cpp
|
||||
lock/LockPoolIncludeTest.cpp
|
||||
lock/ConditionBarrierIncludeTest.cpp
|
||||
lock/MutexPoolLockIncludeTest.cpp
|
||||
data/FixedSizeDataTest.cpp
|
||||
data/DataFixtureIncludeTest.cpp
|
||||
data/DataFixtureTest.cpp
|
||||
data/DataTest.cpp
|
||||
data/FixedSizeDataIncludeTest.cpp
|
||||
data/SerializationHelperTest.cpp
|
||||
data/DataIncludeTest.cpp
|
||||
logging/LoggingLevelTest.cpp
|
||||
logging/LoggerTest.cpp
|
||||
logging/LoggingTest.cpp
|
||||
logging/LoggerIncludeTest.cpp
|
||||
logging/LoggingIncludeTest.cpp
|
||||
assert/assert_release_test.cpp
|
||||
assert/backtrace_test.cpp
|
||||
assert/assert_debug_test.cpp
|
||||
system/GetTotalMemoryTest.cpp
|
||||
system/TimeTest.cpp
|
||||
system/PathTest.cpp
|
||||
system/FiletimeTest.cpp
|
||||
system/MemoryTest.cpp
|
||||
system/HomedirTest.cpp
|
||||
system/EnvTest.cpp
|
||||
thread/debugging_test.cpp
|
||||
thread/LeftRightTest.cpp
|
||||
value_type/ValueTypeTest.cpp
|
||||
either_test.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME}_exit_status process/exit_status.cpp)
|
||||
target_activate_cpp14(${PROJECT_NAME}_exit_status)
|
||||
|
||||
add_executable(${PROJECT_NAME}_exit_signal assert/exit_signal.cpp)
|
||||
target_activate_cpp14(${PROJECT_NAME}_exit_signal)
|
||||
target_link_libraries(${PROJECT_NAME}_exit_signal cpp-utils)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} my-gtest-main googletest cpp-utils)
|
||||
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_exit_status ${PROJECT_NAME}_exit_signal)
|
||||
add_test(${PROJECT_NAME} ${PROJECT_NAME})
|
||||
|
||||
target_enable_style_warnings(${PROJECT_NAME})
|
||||
target_activate_cpp14(${PROJECT_NAME})
|
@ -1,3 +0,0 @@
|
||||
#include "cpp-utils/macros.h"
|
||||
|
||||
// Test that macros.h can be included without needing additional dependencies
|
@ -1,58 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define REAL_NDEBUG_
|
||||
#endif
|
||||
|
||||
//Include the ASSERT macro for a debug build
|
||||
#undef NDEBUG
|
||||
#include "cpp-utils/assert/assert.h"
|
||||
|
||||
|
||||
TEST(AssertTest_DebugBuild, DoesntDieIfTrue) {
|
||||
ASSERT(true, "bla");
|
||||
}
|
||||
|
||||
TEST(AssertTest_DebugBuild, DiesIfFalse) {
|
||||
EXPECT_DEATH(
|
||||
ASSERT(false, "bla"),
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
TEST(AssertTest_DebugBuild, whenDisablingAbort_thenThrowsIfFalse) {
|
||||
cpputils::_assert::DisableAbortOnFailedAssertionRAII _disableAbort;
|
||||
EXPECT_THROW(
|
||||
ASSERT(false, "bla"),
|
||||
cpputils::AssertFailed
|
||||
);
|
||||
}
|
||||
|
||||
TEST(AssertTest_DebugBuild, AssertMessage) {
|
||||
#if defined(_MSC_VER)
|
||||
constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:\d+: my message)";
|
||||
#else
|
||||
constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:[0-9]+: my message)";
|
||||
#endif
|
||||
EXPECT_DEATH(
|
||||
ASSERT(2==5, "my message"),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#if !(defined(_MSC_VER) && defined(REAL_NDEBUG_))
|
||||
TEST(AssertTest_DebugBuild, AssertMessageContainsBacktrace) {
|
||||
EXPECT_DEATH(
|
||||
ASSERT(2==5, "my message"),
|
||||
"cpputils::"
|
||||
);
|
||||
}
|
||||
#else
|
||||
TEST(AssertTest_DebugBuild, AssertMessageContainsBacktrace) {
|
||||
EXPECT_DEATH(
|
||||
ASSERT(2==5, "my message"),
|
||||
"#1"
|
||||
);
|
||||
}
|
||||
#endif
|
@ -1,64 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <regex>
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define REAL_NDEBUG_
|
||||
#endif
|
||||
|
||||
//Include the ASSERT macro for a release build
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG 1
|
||||
#endif
|
||||
#include "cpp-utils/assert/assert.h"
|
||||
|
||||
using testing::HasSubstr;
|
||||
|
||||
TEST(AssertTest_ReleaseBuild, DoesntThrowIfTrue) {
|
||||
ASSERT(true, "bla");
|
||||
}
|
||||
|
||||
TEST(AssertTest_ReleaseBuild, ThrowsIfFalse) {
|
||||
EXPECT_THROW(
|
||||
ASSERT(false, "bla"),
|
||||
cpputils::AssertFailed
|
||||
);
|
||||
}
|
||||
|
||||
TEST(AssertTest_ReleaseBuild, AssertMessage) {
|
||||
try {
|
||||
ASSERT(2==5, "my message");
|
||||
FAIL();
|
||||
} catch (const cpputils::AssertFailed &e) {
|
||||
std::string msg = e.what();
|
||||
// For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string?
|
||||
/*EXPECT_THAT(e.what(), MatchesRegex(
|
||||
R"(Assertion \[2==5\] failed in .*assert_release_test.cpp:27: my message)"
|
||||
));*/
|
||||
EXPECT_TRUE(std::regex_search(e.what(), std::regex(R"(Assertion \[2==5\] failed in .*assert_release_test.cpp:30: my message)")));
|
||||
}
|
||||
}
|
||||
|
||||
#if !(defined(_MSC_VER) && defined(REAL_NDEBUG_))
|
||||
TEST(AssertTest_ReleaseBuild, AssertMessageContainsBacktrace) {
|
||||
try {
|
||||
ASSERT(2==5, "my message");
|
||||
FAIL();
|
||||
} catch (const cpputils::AssertFailed &e) {
|
||||
EXPECT_THAT(e.what(), HasSubstr(
|
||||
"cpputils::"
|
||||
));
|
||||
}
|
||||
}
|
||||
#else
|
||||
TEST(AssertTest_ReleaseBuild, AssertMessageContainsBacktrace) {
|
||||
try {
|
||||
ASSERT(2==5, "my message");
|
||||
FAIL();
|
||||
} catch (const cpputils::AssertFailed &e) {
|
||||
EXPECT_THAT(e.what(), HasSubstr(
|
||||
"#1"
|
||||
));
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,230 +0,0 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <csignal>
|
||||
#include "cpp-utils/assert/backtrace.h"
|
||||
#include "cpp-utils/process/subprocess.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "my-gtest-main.h"
|
||||
|
||||
using std::string;
|
||||
using testing::HasSubstr;
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string call_process_exiting_with(const std::string &kind, const std::string &signal = "")
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
auto executable = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_signal.exe";
|
||||
#else
|
||||
auto executable = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_signal";
|
||||
#endif
|
||||
if (!bf::exists(executable))
|
||||
{
|
||||
throw std::runtime_error(executable.string() + " not found.");
|
||||
}
|
||||
auto result = cpputils::Subprocess::call(executable, {kind, signal}, "");
|
||||
return result.output_stderr;
|
||||
}
|
||||
}
|
||||
|
||||
#if !(defined(_MSC_VER) && defined(NDEBUG))
|
||||
|
||||
TEST(BacktraceTest, ContainsTopLevelLine)
|
||||
{
|
||||
string backtrace = cpputils::backtrace();
|
||||
EXPECT_THAT(backtrace, HasSubstr("BacktraceTest"));
|
||||
EXPECT_THAT(backtrace, HasSubstr("ContainsTopLevelLine"));
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string call_process_exiting_with_nullptr_violation()
|
||||
{
|
||||
return call_process_exiting_with("nullptr");
|
||||
}
|
||||
std::string call_process_exiting_with_exception(const std::string &message)
|
||||
{
|
||||
return call_process_exiting_with("exception", message);
|
||||
}
|
||||
}
|
||||
#if defined(_MSC_VER)
|
||||
#include <Windows.h>
|
||||
namespace
|
||||
{
|
||||
std::string call_process_exiting_with_sigsegv()
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(EXCEPTION_ACCESS_VIOLATION));
|
||||
}
|
||||
std::string call_process_exiting_with_sigill()
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(EXCEPTION_ILLEGAL_INSTRUCTION));
|
||||
}
|
||||
std::string call_process_exiting_with_code(DWORD code)
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(code));
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace
|
||||
{
|
||||
std::string call_process_exiting_with_sigsegv()
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(SIGSEGV));
|
||||
}
|
||||
std::string call_process_exiting_with_sigabrt()
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(SIGABRT));
|
||||
}
|
||||
std::string call_process_exiting_with_sigill()
|
||||
{
|
||||
return call_process_exiting_with("signal", std::to_string(SIGILL));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(BacktraceTest, DoesntCrashOnCaughtException)
|
||||
{
|
||||
// This is needed to make sure we don't use some kind of vectored exception handler on Windows
|
||||
// that ignores the call stack and always jumps on when an exception happens.
|
||||
cpputils::showBacktraceOnCrash();
|
||||
try
|
||||
{
|
||||
throw std::logic_error("exception");
|
||||
}
|
||||
catch (const std::logic_error &e)
|
||||
{
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
|
||||
#if !(defined(_MSC_VER) && defined(NDEBUG))
|
||||
TEST(BacktraceTest, ContainsBacktrace)
|
||||
{
|
||||
string backtrace = cpputils::backtrace();
|
||||
#if defined(_MSC_VER)
|
||||
EXPECT_THAT(backtrace, HasSubstr("testing::Test::Run"));
|
||||
#else
|
||||
EXPECT_THAT(backtrace, HasSubstr("BacktraceTest_ContainsBacktrace_Test::TestBody"));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnNullptrAccess)
|
||||
{
|
||||
auto output = call_process_exiting_with_nullptr_violation();
|
||||
#if defined(_MSC_VER)
|
||||
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
|
||||
#else
|
||||
EXPECT_THAT(output, HasSubstr("cpputils::backtrace"));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigSegv)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigsegv();
|
||||
#if defined(_MSC_VER)
|
||||
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
|
||||
#else
|
||||
EXPECT_THAT(output, HasSubstr("cpputils::backtrace"));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnUnhandledException)
|
||||
{
|
||||
auto output = call_process_exiting_with_exception("my_exception_message");
|
||||
#if defined(_MSC_VER)
|
||||
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
|
||||
#else
|
||||
EXPECT_THAT(output, HasSubstr("cpputils::backtrace"));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigIll)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigill();
|
||||
#if defined(_MSC_VER)
|
||||
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
|
||||
#else
|
||||
EXPECT_THAT(output, HasSubstr("cpputils::backtrace"));
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
TEST(BacktraceTest, ContainsBacktrace)
|
||||
{
|
||||
string backtrace = cpputils::backtrace();
|
||||
EXPECT_THAT(backtrace, HasSubstr("#0"));
|
||||
}
|
||||
TEST(BacktraceTest, ShowBacktraceOnNullptrAccess)
|
||||
{
|
||||
auto output = call_process_exiting_with_nullptr_violation();
|
||||
EXPECT_THAT(output, HasSubstr("#1"));
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigSegv)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigsegv();
|
||||
EXPECT_THAT(output, HasSubstr("#1"));
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnUnhandledException)
|
||||
{
|
||||
auto output = call_process_exiting_with_exception("my_exception_message");
|
||||
EXPECT_THAT(output, HasSubstr("#1"));
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigIll)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigill();
|
||||
EXPECT_THAT(output, HasSubstr("#1"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigAbrt)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigabrt();
|
||||
EXPECT_THAT(output, HasSubstr("cpputils::backtrace"));
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigabrt();
|
||||
EXPECT_THAT(output, HasSubstr("SIGABRT"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
constexpr const char *sigsegv_message = "SIGSEGV";
|
||||
constexpr const char *sigill_message = "SIGILL";
|
||||
#else
|
||||
constexpr const char *sigsegv_message = "EXCEPTION_ACCESS_VIOLATION";
|
||||
constexpr const char *sigill_message = "EXCEPTION_ILLEGAL_INSTRUCTION";
|
||||
#endif
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigSegv_ShowsCorrectSignalName)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigsegv();
|
||||
EXPECT_THAT(output, HasSubstr(sigsegv_message));
|
||||
}
|
||||
|
||||
TEST(BacktraceTest, ShowBacktraceOnSigIll_ShowsCorrectSignalName)
|
||||
{
|
||||
auto output = call_process_exiting_with_sigill();
|
||||
EXPECT_THAT(output, HasSubstr(sigill_message));
|
||||
}
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
TEST(BacktraceTest, ShowBacktraceOnUnhandledException_ShowsCorrectExceptionMessage)
|
||||
{
|
||||
auto output = call_process_exiting_with_exception("my_exception_message");
|
||||
EXPECT_THAT(output, HasSubstr("my_exception_message"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
TEST(BacktraceTest, UnknownCode_ShowsCorrectSignalName)
|
||||
{
|
||||
auto output = call_process_exiting_with_code(0x1234567);
|
||||
EXPECT_THAT(output, HasSubstr("UNKNOWN_CODE(0x1234567)"));
|
||||
}
|
||||
#endif
|
@ -1,36 +0,0 @@
|
||||
#include <cpp-utils/assert/backtrace.h>
|
||||
#include <csignal>
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
void handle_exit_signal(char **argv) {
|
||||
const std::string kind = argv[1];
|
||||
if (kind == "exception") {
|
||||
throw std::logic_error(argv[2]);
|
||||
} else if (kind == "nullptr") {
|
||||
int* ptr = nullptr;
|
||||
*ptr = 5; // NOLINT
|
||||
} else if (kind == "signal") {
|
||||
#if defined(_MSC_VER)
|
||||
DWORD code = std::atoll(argv[2]);
|
||||
::RaiseException(code, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||
#else
|
||||
int code = static_cast<int>(std::strtol(argv[2], nullptr, 10));
|
||||
::raise(code);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int /*argc*/, char* argv[]) {
|
||||
cpputils::showBacktraceOnCrash();
|
||||
#if defined(_MSC_VER)
|
||||
// don't show windows error box
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
||||
#endif
|
||||
handle_exit_signal(argv);
|
||||
return 0;
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/crypto/hash/Hash.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
|
||||
using namespace cpputils::hash;
|
||||
using cpputils::DataFixture;
|
||||
using cpputils::Data;
|
||||
|
||||
TEST(HashTest, generateSalt_isIndeterministic) {
|
||||
EXPECT_NE(generateSalt(), generateSalt());
|
||||
}
|
||||
|
||||
TEST(HashTest, hash_setsSaltCorrectly) {
|
||||
Salt salt = generateSalt();
|
||||
Data data = DataFixture::generate(1024);
|
||||
EXPECT_EQ(salt, hash(data, salt).salt);
|
||||
}
|
||||
|
||||
TEST(HashTest, hash_isDeterministicWithSameDataSameSalt) {
|
||||
Salt salt = generateSalt();
|
||||
Data data = DataFixture::generate(1024);
|
||||
EXPECT_EQ(hash(data, salt).digest, hash(data, salt).digest);
|
||||
}
|
||||
|
||||
TEST(HashTest, hash_isIndeterministicWithSameDataDifferentSalt) {
|
||||
Salt salt1 = generateSalt();
|
||||
Salt salt2 = generateSalt();
|
||||
Data data = DataFixture::generate(1024);
|
||||
EXPECT_NE(hash(data, salt1).digest, hash(data, salt2).digest);
|
||||
}
|
||||
|
||||
TEST(HashTest, hash_isIndeterministicWithDifferentDataSameSalt) {
|
||||
Salt salt = generateSalt();
|
||||
Data data1 = DataFixture::generate(1024, 1);
|
||||
Data data2 = DataFixture::generate(1024, 2);
|
||||
EXPECT_NE(hash(data1, salt).digest, hash(data2, salt).digest);
|
||||
}
|
||||
|
||||
TEST(HashTest, hash_isIndeterministicWithDifferentDataDifferentSalt) {
|
||||
Salt salt1 = generateSalt();
|
||||
Salt salt2 = generateSalt();
|
||||
Data data1 = DataFixture::generate(1024, 1);
|
||||
Data data2 = DataFixture::generate(1024, 2);
|
||||
EXPECT_NE(hash(data1, salt1).digest, hash(data2, salt2).digest);
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/crypto/kdf/SCryptParameters.h>
|
||||
#include <cpp-utils/data/DataFixture.h>
|
||||
#include <sstream>
|
||||
|
||||
using namespace cpputils;
|
||||
|
||||
class SCryptParametersTest : public ::testing::Test {
|
||||
public:
|
||||
SCryptParameters SaveAndLoad(const SCryptParameters &source) {
|
||||
Data serialized = source.serialize();
|
||||
return SCryptParameters::deserialize(serialized);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SCryptParametersTest, Salt) {
|
||||
SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0);
|
||||
EXPECT_EQ(DataFixture::generate(32), cfg.salt());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, Salt_Move) {
|
||||
SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0);
|
||||
SCryptParameters moved = std::move(cfg);
|
||||
EXPECT_EQ(DataFixture::generate(32), moved.salt());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, Salt_SaveAndLoad) {
|
||||
SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0);
|
||||
SCryptParameters loaded = SaveAndLoad(cfg);
|
||||
EXPECT_EQ(DataFixture::generate(32), loaded.salt());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, N) {
|
||||
SCryptParameters cfg(Data(0), 1024, 0, 0);
|
||||
EXPECT_EQ(1024u, cfg.n());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, N_Move) {
|
||||
SCryptParameters cfg(Data(0), 1024, 0, 0);
|
||||
SCryptParameters moved = std::move(cfg);
|
||||
EXPECT_EQ(1024u, moved.n());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, N_SaveAndLoad) {
|
||||
SCryptParameters cfg(Data(0), 1024, 0, 0);
|
||||
SCryptParameters loaded = SaveAndLoad(cfg);
|
||||
EXPECT_EQ(1024u, loaded.n());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, r) {
|
||||
SCryptParameters cfg(Data(0), 0, 8, 0);
|
||||
EXPECT_EQ(8u, cfg.r());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, r_Move) {
|
||||
SCryptParameters cfg(Data(0), 0, 8, 0);
|
||||
SCryptParameters moved = std::move(cfg);
|
||||
EXPECT_EQ(8u, moved.r());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, r_SaveAndLoad) {
|
||||
SCryptParameters cfg(Data(0), 0, 8, 0);
|
||||
SCryptParameters loaded = SaveAndLoad(cfg);
|
||||
EXPECT_EQ(8u, loaded.r());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, p) {
|
||||
SCryptParameters cfg(Data(0), 0, 0, 16);
|
||||
EXPECT_EQ(16u, cfg.p());
|
||||
}
|
||||
|
||||
TEST_F(SCryptParametersTest, p_Move) {
|
||||
SCryptParameters cfg(Data(0), 0, 0, 16);
|
||||
SCryptParameters moved = std::move(cfg);
|
||||
EXPECT_EQ(16u, moved.p());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(SCryptParametersTest, p_SaveAndLoad) {
|
||||
SCryptParameters cfg(Data(0), 0, 0, 16);
|
||||
SCryptParameters loaded = SaveAndLoad(cfg);
|
||||
EXPECT_EQ(16u, loaded.p());
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "cpp-utils/crypto/kdf/Scrypt.h"
|
||||
|
||||
using namespace cpputils;
|
||||
using std::string;
|
||||
|
||||
class SCryptTest : public ::testing::Test {
|
||||
public:
|
||||
bool keyEquals(const EncryptionKey& lhs, const EncryptionKey& rhs) {
|
||||
ASSERT(lhs.binaryLength() == rhs.binaryLength(), "Keys must have equal size to be comparable");
|
||||
return 0 == std::memcmp(lhs.data(), rhs.data(), lhs.binaryLength());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SCryptTest, GeneratedKeyIsReproductible_448) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(56, "mypassword");
|
||||
auto rederivedKey = scrypt.deriveExistingKey(56, "mypassword", derivedKey.kdfParameters);
|
||||
EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey));
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, GeneratedKeyIsReproductible_256) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(32, "mypassword");
|
||||
auto rederivedKey = scrypt.deriveExistingKey(32, "mypassword", derivedKey.kdfParameters);
|
||||
EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey));
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, GeneratedKeyIsReproductible_128) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(16, "mypassword");
|
||||
auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword", derivedKey.kdfParameters);
|
||||
EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey));
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, GeneratedKeyIsReproductible_DefaultSettings) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(16, "mypassword");
|
||||
auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword", derivedKey.kdfParameters);
|
||||
EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey));
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, DifferentPasswordResultsInDifferentKey) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(16, "mypassword");
|
||||
auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword2", derivedKey.kdfParameters);
|
||||
EXPECT_FALSE(keyEquals(derivedKey.key, rederivedKey));
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, UsesCorrectSettings) {
|
||||
SCrypt scrypt(SCrypt::TestSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(16, "mypassword");
|
||||
auto parameters = SCryptParameters::deserialize(derivedKey.kdfParameters);
|
||||
EXPECT_EQ(SCrypt::TestSettings.SALT_LEN, parameters.salt().size());
|
||||
EXPECT_EQ(SCrypt::TestSettings.N, parameters.n());
|
||||
EXPECT_EQ(SCrypt::TestSettings.r, parameters.r());
|
||||
EXPECT_EQ(SCrypt::TestSettings.p, parameters.p());
|
||||
}
|
||||
|
||||
TEST_F(SCryptTest, UsesCorrectDefaultSettings) {
|
||||
SCrypt scrypt(SCrypt::DefaultSettings);
|
||||
auto derivedKey = scrypt.deriveNewKey(16, "mypassword");
|
||||
auto parameters = SCryptParameters::deserialize(derivedKey.kdfParameters);
|
||||
EXPECT_EQ(SCrypt::DefaultSettings.SALT_LEN, parameters.salt().size());
|
||||
EXPECT_EQ(SCrypt::DefaultSettings.N, parameters.n());
|
||||
EXPECT_EQ(SCrypt::DefaultSettings.r, parameters.r());
|
||||
EXPECT_EQ(SCrypt::DefaultSettings.p, parameters.p());
|
||||
}
|
@ -1,298 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "cpp-utils/crypto/symmetric/Cipher.h"
|
||||
#include "cpp-utils/crypto/symmetric/ciphers.h"
|
||||
#include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h"
|
||||
|
||||
#include "cpp-utils/data/DataFixture.h"
|
||||
#include "cpp-utils/data/Data.h"
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
|
||||
using namespace cpputils;
|
||||
using std::string;
|
||||
|
||||
template<class Cipher>
|
||||
class CipherTest: public ::testing::Test {
|
||||
public:
|
||||
BOOST_CONCEPT_ASSERT((CipherConcept<Cipher>));
|
||||
typename Cipher::EncryptionKey encKey = createKeyFixture();
|
||||
|
||||
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
|
||||
Data data = DataFixture::generate(Cipher::KEYSIZE, seed);
|
||||
return Cipher::EncryptionKey::FromString(data.ToString());
|
||||
}
|
||||
|
||||
void CheckEncryptThenDecryptIsIdentity(const Data &plaintext) {
|
||||
Data ciphertext = Encrypt(plaintext);
|
||||
Data decrypted = Decrypt(ciphertext);
|
||||
EXPECT_EQ(plaintext, decrypted);
|
||||
}
|
||||
|
||||
void CheckEncryptIsIndeterministic(const Data &plaintext) {
|
||||
Data ciphertext = Encrypt(plaintext);
|
||||
Data ciphertext2 = Encrypt(plaintext);
|
||||
EXPECT_NE(ciphertext, ciphertext2);
|
||||
}
|
||||
|
||||
void CheckEncryptedSize(const Data &plaintext) {
|
||||
Data ciphertext = Encrypt(plaintext);
|
||||
EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size());
|
||||
}
|
||||
|
||||
void ExpectDoesntDecrypt(const Data &ciphertext) {
|
||||
auto decrypted = Cipher::decrypt(static_cast<const CryptoPP::byte*>(ciphertext.data()), ciphertext.size(), this->encKey);
|
||||
EXPECT_FALSE(decrypted);
|
||||
}
|
||||
|
||||
Data Encrypt(const Data &plaintext) {
|
||||
return Cipher::encrypt(static_cast<const CryptoPP::byte*>(plaintext.data()), plaintext.size(), this->encKey);
|
||||
}
|
||||
|
||||
Data Decrypt(const Data &ciphertext) {
|
||||
return Cipher::decrypt(static_cast<const CryptoPP::byte*>(ciphertext.data()), ciphertext.size(), this->encKey).value();
|
||||
}
|
||||
|
||||
static Data CreateZeroes(unsigned int size) {
|
||||
return Data(size).FillWithZeroes();
|
||||
}
|
||||
|
||||
static Data CreateData(unsigned int size, unsigned int seed = 0) {
|
||||
return DataFixture::generate(size, seed);
|
||||
}
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(CipherTest);
|
||||
|
||||
constexpr std::array<unsigned int, 7> SIZES = {{0, 1, 100, 1024, 5000, 1048576, 20971520}};
|
||||
|
||||
TYPED_TEST_P(CipherTest, Size) {
|
||||
for (auto size: SIZES) {
|
||||
EXPECT_EQ(size, TypeParam::ciphertextSize(TypeParam::plaintextSize(size)));
|
||||
EXPECT_EQ(size, TypeParam::plaintextSize(TypeParam::ciphertextSize(size)));
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Zeroes) {
|
||||
for (auto size: SIZES) {
|
||||
Data plaintext = this->CreateZeroes(size);
|
||||
this->CheckEncryptThenDecryptIsIdentity(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Data) {
|
||||
for (auto size: SIZES) {
|
||||
Data plaintext = this->CreateData(size);
|
||||
this->CheckEncryptThenDecryptIsIdentity(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Zeroes) {
|
||||
for (auto size: SIZES) {
|
||||
Data plaintext = this->CreateZeroes(size);
|
||||
this->CheckEncryptIsIndeterministic(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Data) {
|
||||
for (auto size: SIZES) {
|
||||
Data plaintext = this->CreateData(size);
|
||||
this->CheckEncryptIsIndeterministic(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, EncryptedSize) {
|
||||
for (auto size: SIZES) {
|
||||
Data plaintext = this->CreateData(size);
|
||||
this->CheckEncryptedSize(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsTooSmall) {
|
||||
Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_0) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(0);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_1) {
|
||||
static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense.");
|
||||
Data tooSmallCiphertext(1);
|
||||
this->ExpectDoesntDecrypt(tooSmallCiphertext);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(CipherTest,
|
||||
Size,
|
||||
EncryptThenDecrypt_Zeroes,
|
||||
EncryptThenDecrypt_Data,
|
||||
EncryptIsIndeterministic_Zeroes,
|
||||
EncryptIsIndeterministic_Data,
|
||||
EncryptedSize,
|
||||
TryDecryptDataThatIsTooSmall,
|
||||
TryDecryptDataThatIsMuchTooSmall_0,
|
||||
TryDecryptDataThatIsMuchTooSmall_1
|
||||
);
|
||||
|
||||
template<class Cipher>
|
||||
class AuthenticatedCipherTest: public CipherTest<Cipher> {
|
||||
public:
|
||||
Data zeroes1 = CipherTest<Cipher>::CreateZeroes(1);
|
||||
Data plaintext1 = CipherTest<Cipher>::CreateData(1);
|
||||
Data zeroes2 = CipherTest<Cipher>::CreateZeroes(100 * 1024);
|
||||
Data plaintext2 = CipherTest<Cipher>::CreateData(100 * 1024);
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(AuthenticatedCipherTest);
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1) {
|
||||
Data ciphertext = this->Encrypt(this->zeroes1);
|
||||
void* firstByte = ciphertext.data();
|
||||
serialize<CryptoPP::byte>(firstByte, deserialize<CryptoPP::byte>(firstByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data_Size1) {
|
||||
Data ciphertext = this->Encrypt(this->plaintext1);
|
||||
void* firstByte = ciphertext.data();
|
||||
serialize<CryptoPP::byte>(firstByte, deserialize<CryptoPP::byte>(firstByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes) {
|
||||
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||
void* firstByte = ciphertext.data();
|
||||
serialize<CryptoPP::byte>(firstByte, deserialize<CryptoPP::byte>(firstByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data) {
|
||||
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||
void* firstByte = ciphertext.data();
|
||||
serialize<CryptoPP::byte>(firstByte, deserialize<CryptoPP::byte>(firstByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Zeroes) {
|
||||
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||
void* lastByte = ciphertext.dataOffset(ciphertext.size() - 1);
|
||||
serialize<CryptoPP::byte>(lastByte, deserialize<CryptoPP::byte>(lastByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Data) {
|
||||
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||
void* lastByte = ciphertext.dataOffset(ciphertext.size() - 1);
|
||||
serialize<CryptoPP::byte>(lastByte, deserialize<CryptoPP::byte>(lastByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Zeroes) {
|
||||
Data ciphertext = this->Encrypt(this->zeroes2);
|
||||
void* middleByte = ciphertext.dataOffset(ciphertext.size()/2);
|
||||
serialize<CryptoPP::byte>(middleByte, deserialize<CryptoPP::byte>(middleByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Data) {
|
||||
Data ciphertext = this->Encrypt(this->plaintext2);
|
||||
void* middleByte = ciphertext.dataOffset(ciphertext.size()/2);
|
||||
serialize<CryptoPP::byte>(middleByte, deserialize<CryptoPP::byte>(middleByte) + 1);
|
||||
this->ExpectDoesntDecrypt(ciphertext);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptZeroesData) {
|
||||
this->ExpectDoesntDecrypt(this->zeroes2);
|
||||
}
|
||||
|
||||
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) {
|
||||
this->ExpectDoesntDecrypt(this->plaintext2);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_SUITE_P(AuthenticatedCipherTest,
|
||||
ModifyFirstByte_Zeroes_Size1,
|
||||
ModifyFirstByte_Zeroes,
|
||||
ModifyFirstByte_Data_Size1,
|
||||
ModifyFirstByte_Data,
|
||||
ModifyLastByte_Zeroes,
|
||||
ModifyLastByte_Data,
|
||||
ModifyMiddleByte_Zeroes,
|
||||
ModifyMiddleByte_Data,
|
||||
TryDecryptZeroesData,
|
||||
TryDecryptRandomData
|
||||
);
|
||||
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Fake, CipherTest, FakeAuthenticatedCipher);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, CipherTest, XChaCha20Poly1305);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, AuthenticatedCipherTest, XChaCha20Poly1305);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, CipherTest, AES256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES128_CFB, CipherTest, AES128_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES128_GCM, CipherTest, AES128_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(AES128_GCM, AuthenticatedCipherTest, AES128_GCM);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_CFB, CipherTest, Twofish256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_GCM, CipherTest, Twofish256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_GCM, AuthenticatedCipherTest, Twofish256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_CFB, CipherTest, Twofish128_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_GCM, CipherTest, Twofish128_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_GCM, AuthenticatedCipherTest, Twofish128_GCM);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_CFB, CipherTest, Serpent256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_GCM, CipherTest, Serpent256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_GCM, AuthenticatedCipherTest, Serpent256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_CFB, CipherTest, Serpent128_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_GCM, CipherTest, Serpent128_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_GCM, AuthenticatedCipherTest, Serpent128_GCM);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_CFB, CipherTest, Cast256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_GCM, CipherTest, Cast256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_GCM, AuthenticatedCipherTest, Cast256_GCM);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_CFB, CipherTest, Mars448_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_GCM, CipherTest, Mars448_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_GCM, AuthenticatedCipherTest, Mars448_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_CFB, CipherTest, Mars256_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_GCM, CipherTest, Mars256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_GCM, AuthenticatedCipherTest, Mars256_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_CFB, CipherTest, Mars128_CFB); //CFB mode is not authenticated
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_GCM, CipherTest, Mars128_GCM);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_GCM, AuthenticatedCipherTest, Mars128_GCM);
|
||||
|
||||
|
||||
// Test cipher names
|
||||
TEST(CipherNameTest, TestCipherNames) {
|
||||
EXPECT_EQ("xchacha20-poly1305", string(XChaCha20Poly1305::NAME));
|
||||
|
||||
EXPECT_EQ("aes-256-gcm", string(AES256_GCM::NAME));
|
||||
EXPECT_EQ("aes-256-cfb", string(AES256_CFB::NAME));
|
||||
EXPECT_EQ("aes-128-gcm", string(AES128_GCM::NAME));
|
||||
EXPECT_EQ("aes-128-cfb", string(AES128_CFB::NAME));
|
||||
|
||||
EXPECT_EQ("twofish-256-gcm", string(Twofish256_GCM::NAME));
|
||||
EXPECT_EQ("twofish-256-cfb", string(Twofish256_CFB::NAME));
|
||||
EXPECT_EQ("twofish-128-gcm", string(Twofish128_GCM::NAME));
|
||||
EXPECT_EQ("twofish-128-cfb", string(Twofish128_CFB::NAME));
|
||||
|
||||
EXPECT_EQ("serpent-256-gcm", string(Serpent256_GCM::NAME));
|
||||
EXPECT_EQ("serpent-256-cfb", string(Serpent256_CFB::NAME));
|
||||
EXPECT_EQ("serpent-128-gcm", string(Serpent128_GCM::NAME));
|
||||
EXPECT_EQ("serpent-128-cfb", string(Serpent128_CFB::NAME));
|
||||
|
||||
EXPECT_EQ("cast-256-gcm", string(Cast256_GCM::NAME));
|
||||
EXPECT_EQ("cast-256-cfb", string(Cast256_CFB::NAME));
|
||||
|
||||
|
||||
EXPECT_EQ("mars-448-gcm", string(Mars448_GCM::NAME));
|
||||
EXPECT_EQ("mars-448-cfb", string(Mars448_CFB::NAME));
|
||||
EXPECT_EQ("mars-256-gcm", string(Mars256_GCM::NAME));
|
||||
EXPECT_EQ("mars-256-cfb", string(Mars256_CFB::NAME));
|
||||
EXPECT_EQ("mars-128-gcm", string(Mars128_GCM::NAME));
|
||||
EXPECT_EQ("mars-128-cfb", string(Mars128_CFB::NAME));
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
#include "cpp-utils/data/DataFixture.h"
|
||||
|
||||
// Test the header can be included without needing additional dependencies
|
@ -1,73 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cpp-utils/data/Data.h"
|
||||
#include "cpp-utils/data/DataFixture.h"
|
||||
|
||||
using ::testing::Test;
|
||||
|
||||
|
||||
using namespace cpputils;
|
||||
|
||||
class DataFixtureTest: public Test {
|
||||
};
|
||||
|
||||
TEST_F(DataFixtureTest, CreateEmptyFixture) {
|
||||
Data data = DataFixture::generate(0);
|
||||
EXPECT_EQ(0u, data.size());
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, CreateOneByteFixture) {
|
||||
Data data = DataFixture::generate(1);
|
||||
EXPECT_EQ(1u, data.size());
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, CreateLargerFixture) {
|
||||
Data data = DataFixture::generate(20 * 1024 * 1024);
|
||||
EXPECT_EQ(20u * 1024u * 1024u, data.size());
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_DefaultSeed) {
|
||||
Data data1 = DataFixture::generate(1024 * 1024);
|
||||
Data data2 = DataFixture::generate(1024 * 1024);
|
||||
EXPECT_EQ(data1, data2);
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_SeedIs5) {
|
||||
Data data1 = DataFixture::generate(1024 * 1024, 5);
|
||||
Data data2 = DataFixture::generate(1024 * 1024, 5);
|
||||
EXPECT_EQ(data1, data2);
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, DifferentSeedIsDifferentFixture) {
|
||||
Data data1 = DataFixture::generate(1024 * 1024, 0);
|
||||
Data data2 = DataFixture::generate(1024 * 1024, 1);
|
||||
EXPECT_NE(data1, data2);
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_DefaultSeed_1) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1);
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 1));
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_DefaultSeed_2) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(501); //Intentionally not 64bit-aligned, because the generate() function generates 64bit values for performance
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 501));
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_SeedIs5_1) {
|
||||
Data data1 = DataFixture::generate(1024, 5);
|
||||
Data data2 = DataFixture::generate(1, 5);
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 1));
|
||||
}
|
||||
|
||||
TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_SeedIs5_2) {
|
||||
Data data1 = DataFixture::generate(1024, 5);
|
||||
Data data2 = DataFixture::generate(501, 5); //Intentionally not 64bit-aligned, because the generate() function generates 64bit values for performance
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 501));
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
#include "cpp-utils/data/Data.h"
|
||||
|
||||
// Test the header can be included without needing additional dependencies
|
@ -1,297 +0,0 @@
|
||||
#include "cpp-utils/data/DataFixture.h"
|
||||
#include "cpp-utils/data/Data.h"
|
||||
#include "cpp-utils/data/SerializationHelper.h"
|
||||
#include <gmock/gmock.h>
|
||||
#include "cpp-utils/tempfile/TempFile.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
using ::testing::Return;
|
||||
|
||||
using cpputils::TempFile;
|
||||
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::string;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
using namespace cpputils;
|
||||
|
||||
class DataTest: public Test {
|
||||
public:
|
||||
bool DataIsZeroes(const Data &data) {
|
||||
for (size_t i = 0; i != data.size(); ++ i) {
|
||||
if (deserialize<uint8_t>(data.dataOffset(i)) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class DataTestWithSizeParam: public DataTest, public WithParamInterface<size_t> {
|
||||
public:
|
||||
Data randomData;
|
||||
|
||||
DataTestWithSizeParam(): randomData(DataFixture::generate(GetParam())) {}
|
||||
|
||||
static void StoreData(const Data &data, const bf::path &filepath) {
|
||||
ofstream file(filepath.string().c_str(), std::ios::binary | std::ios::trunc);
|
||||
file.write(static_cast<const char*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
static void EXPECT_STORED_FILE_DATA_CORRECT(const Data &data, const bf::path &filepath) {
|
||||
EXPECT_EQ(data.size(), bf::file_size(filepath));
|
||||
|
||||
ifstream file(filepath.string().c_str(), std::ios::binary);
|
||||
char *read_data = new char[data.size()];
|
||||
file.read(read_data, data.size());
|
||||
|
||||
EXPECT_EQ(0, std::memcmp(data.data(), read_data, data.size()));
|
||||
delete[] read_data;
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(DataTestWithSizeParam, DataTestWithSizeParam, Values(0, 1, 2, 1024, 4096, 10*1024*1024));
|
||||
|
||||
TEST_P(DataTestWithSizeParam, ZeroInitializedDataIsDifferentToRandomData) {
|
||||
if (GetParam() != 0) {
|
||||
Data data(GetParam());
|
||||
data.FillWithZeroes();
|
||||
EXPECT_NE(randomData, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Working on a large data area without a crash is a good indicator that we
|
||||
// are actually working on memory that was validly allocated for us.
|
||||
TEST_P(DataTestWithSizeParam, WriteAndCheck) {
|
||||
Data data = randomData.copy();
|
||||
EXPECT_EQ(randomData, data);
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, Size) {
|
||||
Data data(GetParam());
|
||||
EXPECT_EQ(GetParam(), data.size());
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, CheckStoredFile) {
|
||||
TempFile file;
|
||||
randomData.StoreToFile(file.path());
|
||||
|
||||
EXPECT_STORED_FILE_DATA_CORRECT(randomData, file.path());
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, CheckLoadedData) {
|
||||
TempFile file;
|
||||
StoreData(randomData, file.path());
|
||||
|
||||
Data data = Data::LoadFromFile(file.path()).value();
|
||||
|
||||
EXPECT_EQ(randomData, data);
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, StoreDoesntChangeData) {
|
||||
Data data = randomData.copy();
|
||||
|
||||
TempFile file;
|
||||
data.StoreToFile(file.path());
|
||||
|
||||
EXPECT_EQ(randomData, data);
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, StoreAndLoad) {
|
||||
TempFile file;
|
||||
randomData.StoreToFile(file.path());
|
||||
Data loaded_data = Data::LoadFromFile(file.path()).value();
|
||||
|
||||
EXPECT_EQ(randomData, loaded_data);
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithSizeParam, Copy) {
|
||||
Data copy = randomData.copy();
|
||||
EXPECT_EQ(randomData, copy);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, ChangingCopyDoesntChangeOriginal) {
|
||||
Data original = DataFixture::generate(1024);
|
||||
Data copy = original.copy();
|
||||
serialize<uint8_t>(copy.data(), deserialize<uint8_t>(copy.data()) + 1);
|
||||
EXPECT_EQ(DataFixture::generate(1024), original);
|
||||
EXPECT_NE(copy, original);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, InitializeWithZeroes) {
|
||||
Data data(10*1024);
|
||||
data.FillWithZeroes();
|
||||
EXPECT_TRUE(DataIsZeroes(data));
|
||||
}
|
||||
|
||||
TEST_F(DataTest, FillModifiedDataWithZeroes) {
|
||||
Data data = DataFixture::generate(10*1024);
|
||||
EXPECT_FALSE(DataIsZeroes(data));
|
||||
|
||||
data.FillWithZeroes();
|
||||
EXPECT_TRUE(DataIsZeroes(data));
|
||||
}
|
||||
|
||||
TEST_F(DataTest, MoveConstructor) {
|
||||
Data original = DataFixture::generate(1024);
|
||||
Data copy(std::move(original));
|
||||
EXPECT_EQ(DataFixture::generate(1024), copy);
|
||||
EXPECT_EQ(nullptr, original.data()); // NOLINT (intentional use-after-move)
|
||||
EXPECT_EQ(0u, original.size()); // NOLINT (intentional use-after-move)
|
||||
}
|
||||
|
||||
TEST_F(DataTest, MoveAssignment) {
|
||||
Data original = DataFixture::generate(1024);
|
||||
Data copy(0);
|
||||
copy = std::move(original);
|
||||
EXPECT_EQ(DataFixture::generate(1024), copy);
|
||||
EXPECT_EQ(nullptr, original.data()); // NOLINT (intentional use-after-move)
|
||||
EXPECT_EQ(0u, original.size()); // NOLINT (intentional use-after-move)
|
||||
}
|
||||
|
||||
TEST_F(DataTest, Equality) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1024);
|
||||
EXPECT_TRUE(data1 == data2);
|
||||
EXPECT_FALSE(data1 != data2);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, Inequality_DifferentSize) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1023);
|
||||
EXPECT_FALSE(data1 == data2);
|
||||
EXPECT_TRUE(data1 != data2);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, Inequality_DifferentFirstByte) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1024);
|
||||
serialize<uint8_t>(data2.data(), deserialize<uint8_t>(data2.data()) + 1);
|
||||
EXPECT_FALSE(data1 == data2);
|
||||
EXPECT_TRUE(data1 != data2);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, Inequality_DifferentMiddleByte) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1024);
|
||||
serialize<uint8_t>(data2.dataOffset(500), deserialize<uint8_t>(data2.dataOffset(500)) + 1);
|
||||
EXPECT_FALSE(data1 == data2);
|
||||
EXPECT_TRUE(data1 != data2);
|
||||
}
|
||||
|
||||
TEST_F(DataTest, Inequality_DifferentLastByte) {
|
||||
Data data1 = DataFixture::generate(1024);
|
||||
Data data2 = DataFixture::generate(1024);
|
||||
serialize<uint8_t>(data2.dataOffset(1023), deserialize<uint8_t>(data2.dataOffset(1023)) + 1);
|
||||
EXPECT_FALSE(data1 == data2);
|
||||
EXPECT_TRUE(data1 != data2);
|
||||
}
|
||||
|
||||
#ifdef __x86_64__
|
||||
TEST_F(DataTest, LargesizeSize) {
|
||||
//Needs 64bit for representation. This value isn't in the size param list, because the list is also used for read/write checks.
|
||||
uint64_t size = static_cast<uint64_t>(4.5L*1024*1024*1024);
|
||||
Data data(size);
|
||||
EXPECT_EQ(size, data.size());
|
||||
}
|
||||
#else
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message This is not a 64bit architecture. Large size data tests are disabled.
|
||||
#else
|
||||
#warning This is not a 64bit architecture. Large size data tests are disabled.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
TEST_F(DataTest, LoadingNonexistingFile) {
|
||||
TempFile file(false); // Pass false to constructor, so the tempfile is not created
|
||||
EXPECT_FALSE(Data::LoadFromFile(file.path()));
|
||||
}
|
||||
|
||||
class DataTestWithStringParam: public DataTest, public WithParamInterface<string> {};
|
||||
INSTANTIATE_TEST_SUITE_P(DataTestWithStringParam, DataTestWithStringParam, Values("", "2898B4B8A13C0F0278CCE465DB", "6FFEBAD90C0DAA2B79628F0627CE9841"));
|
||||
|
||||
TEST_P(DataTestWithStringParam, FromAndToString) {
|
||||
Data data = Data::FromString(GetParam());
|
||||
EXPECT_EQ(GetParam(), data.ToString());
|
||||
}
|
||||
|
||||
TEST_P(DataTestWithStringParam, ToAndFromString) {
|
||||
Data data = Data::FromString(GetParam());
|
||||
Data data2 = Data::FromString(data.ToString());
|
||||
EXPECT_EQ(data, data2);
|
||||
}
|
||||
|
||||
struct MockAllocator final : public Allocator {
|
||||
MOCK_METHOD(void* , allocate, (size_t), (override));
|
||||
MOCK_METHOD(void, free, (void*, size_t), (override));
|
||||
};
|
||||
|
||||
class DataTestWithMockAllocator: public DataTest {
|
||||
public:
|
||||
char ptr_target{};
|
||||
|
||||
unique_ref<MockAllocator> allocator = make_unique_ref<MockAllocator>();
|
||||
MockAllocator* allocator_ptr = allocator.get();
|
||||
};
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenCreatingNewData_thenTakesItFromAllocator) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
Data data(5, std::move(allocator));
|
||||
|
||||
EXPECT_EQ(&ptr_target, data.data());
|
||||
}
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenDestructingData_thenFreesItInAllocator) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
Data data(5, std::move(allocator));
|
||||
|
||||
EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1);
|
||||
}
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenMoveConstructing_thenOnlyFreesOnce) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
|
||||
Data data(5, std::move(allocator));
|
||||
Data data2 = std::move(data);
|
||||
|
||||
EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1);
|
||||
}
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenMoveAssigning_thenOnlyFreesOnce) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
|
||||
Data data(5, std::move(allocator));
|
||||
Data data2(3);
|
||||
data2 = std::move(data);
|
||||
|
||||
EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1);
|
||||
}
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenMoveConstructing_thenOnlyFreesWhenSecondIsDestructed) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
EXPECT_CALL(*allocator_ptr, free(testing::_, testing::_)).Times(0);
|
||||
|
||||
auto data = std::make_unique<Data>(5, std::move(allocator));
|
||||
Data data2 = std::move(*data);
|
||||
data.reset();
|
||||
|
||||
EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1);
|
||||
}
|
||||
|
||||
TEST_F(DataTestWithMockAllocator, whenMoveAssigning_thenOnlyFreesWhenSecondIsDestructed) {
|
||||
EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target));
|
||||
EXPECT_CALL(*allocator_ptr, free(testing::_, testing::_)).Times(0);
|
||||
|
||||
auto data = std::make_unique<Data>(5, std::move(allocator));
|
||||
Data data2(3);
|
||||
data2 = std::move(*data);
|
||||
data.reset();
|
||||
|
||||
EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
#include "cpp-utils/data/FixedSizeData.h"
|
||||
|
||||
// Test the header can be included without needing additional dependencies
|
@ -1,192 +0,0 @@
|
||||
#include "cpp-utils/data/DataFixture.h"
|
||||
#include "cpp-utils/data/FixedSizeData.h"
|
||||
#include "cpp-utils/data/Data.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using ::testing::Test;
|
||||
using ::testing::WithParamInterface;
|
||||
using ::testing::Values;
|
||||
|
||||
using std::string;
|
||||
|
||||
using namespace cpputils;
|
||||
|
||||
class FixedSizeDataTest: public Test {
|
||||
public:
|
||||
static constexpr size_t SIZE = 16;
|
||||
|
||||
const string DATA1_AS_STRING = "1491BB4932A389EE14BC7090AC772972";
|
||||
const string DATA2_AS_STRING = "272EE5517627CFA147A971A8E6E747E0";
|
||||
|
||||
const Data DATA3_AS_BINARY;
|
||||
const Data DATA4_AS_BINARY;
|
||||
|
||||
FixedSizeDataTest() : DATA3_AS_BINARY(DataFixture::generate(SIZE, 1)), DATA4_AS_BINARY(DataFixture::generate(SIZE, 2)) {}
|
||||
|
||||
template<size_t SIZE>
|
||||
void EXPECT_DATA_EQ(const Data &expected, const FixedSizeData<SIZE> &actual) {
|
||||
EXPECT_EQ(expected.size(), SIZE);
|
||||
EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), SIZE));
|
||||
}
|
||||
};
|
||||
|
||||
constexpr size_t FixedSizeDataTest::SIZE;
|
||||
|
||||
TEST_F(FixedSizeDataTest, EqualsTrue) {
|
||||
FixedSizeData<SIZE> DATA1_1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> DATA1_2 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
|
||||
EXPECT_TRUE(DATA1_1 == DATA1_2);
|
||||
EXPECT_TRUE(DATA1_2 == DATA1_1);
|
||||
}
|
||||
|
||||
TEST_F(FixedSizeDataTest, EqualsFalse) {
|
||||
FixedSizeData<SIZE> DATA1_1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> DATA2_1 = FixedSizeData<SIZE>::FromString(DATA2_AS_STRING);
|
||||
|
||||
EXPECT_FALSE(DATA1_1 == DATA2_1);
|
||||
EXPECT_FALSE(DATA2_1 == DATA1_1);
|
||||
}
|
||||
|
||||
TEST_F(FixedSizeDataTest, NotEqualsFalse) {
|
||||
FixedSizeData<SIZE> DATA1_1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> DATA1_2 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
|
||||
EXPECT_FALSE(DATA1_1 != DATA1_2);
|
||||
EXPECT_FALSE(DATA1_2 != DATA1_1);
|
||||
}
|
||||
|
||||
TEST_F(FixedSizeDataTest, NotEqualsTrue) {
|
||||
FixedSizeData<SIZE> DATA1_1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> DATA2_1 = FixedSizeData<SIZE>::FromString(DATA2_AS_STRING);
|
||||
|
||||
EXPECT_TRUE(DATA1_1 != DATA2_1);
|
||||
EXPECT_TRUE(DATA2_1 != DATA1_1);
|
||||
}
|
||||
|
||||
class FixedSizeDataTestWithStringParam: public FixedSizeDataTest, public WithParamInterface<string> {};
|
||||
INSTANTIATE_TEST_SUITE_P(FixedSizeDataTestWithStringParam, FixedSizeDataTestWithStringParam, Values("2898B4B8A13CA63CBE0F0278CCE465DB", "6FFEBAD90C0DAA2B79628F0627CE9841"));
|
||||
|
||||
TEST_P(FixedSizeDataTestWithStringParam, FromAndToString) {
|
||||
FixedSizeData<SIZE> data = FixedSizeData<SIZE>::FromString(GetParam());
|
||||
EXPECT_EQ(GetParam(), data.ToString());
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithStringParam, ToAndFromString) {
|
||||
FixedSizeData<SIZE> data = FixedSizeData<SIZE>::FromString(GetParam());
|
||||
FixedSizeData<SIZE> data2 = FixedSizeData<SIZE>::FromString(data.ToString());
|
||||
EXPECT_EQ(data, data2);
|
||||
}
|
||||
|
||||
class FixedSizeDataTestWithBinaryParam: public FixedSizeDataTest, public WithParamInterface<const Data*> {
|
||||
public:
|
||||
static const Data VALUE1;
|
||||
static const Data VALUE2;
|
||||
};
|
||||
const Data FixedSizeDataTestWithBinaryParam::VALUE1(DataFixture::generate(SIZE, 3));
|
||||
const Data FixedSizeDataTestWithBinaryParam::VALUE2(DataFixture::generate(SIZE, 4));
|
||||
INSTANTIATE_TEST_SUITE_P(FixedSizeDataTestWithBinaryParam, FixedSizeDataTestWithBinaryParam, Values(&FixedSizeDataTestWithBinaryParam::VALUE1, &FixedSizeDataTestWithBinaryParam::VALUE2));
|
||||
|
||||
TEST_P(FixedSizeDataTestWithBinaryParam, FromBinary) {
|
||||
FixedSizeData<SIZE> data = FixedSizeData<SIZE>::FromBinary(GetParam()->data());
|
||||
EXPECT_DATA_EQ(*GetParam(), data);
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithBinaryParam, FromAndToBinary) {
|
||||
FixedSizeData<SIZE> data = FixedSizeData<SIZE>::FromBinary(GetParam()->data());
|
||||
Data output(FixedSizeData<SIZE>::BINARY_LENGTH);
|
||||
data.ToBinary(output.data());
|
||||
EXPECT_EQ(*GetParam(), output);
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithBinaryParam, ToAndFromBinary) {
|
||||
FixedSizeData<SIZE> data = FixedSizeData<SIZE>::FromBinary(GetParam()->data());
|
||||
Data stored(FixedSizeData<SIZE>::BINARY_LENGTH);
|
||||
data.ToBinary(stored.data());
|
||||
FixedSizeData<SIZE> loaded = FixedSizeData<SIZE>::FromBinary(stored.data());
|
||||
EXPECT_EQ(data, loaded);
|
||||
}
|
||||
|
||||
class FixedSizeDataTestWithParam: public FixedSizeDataTest, public WithParamInterface<FixedSizeData<FixedSizeDataTest::SIZE>> {};
|
||||
INSTANTIATE_TEST_SUITE_P(FixedSizeDataTestWithParam, FixedSizeDataTestWithParam, Values(FixedSizeData<FixedSizeDataTest::SIZE>::FromString("2898B4B8A13CA63CBE0F0278CCE465DB"), FixedSizeData<FixedSizeDataTest::SIZE>::FromString("6FFEBAD90C0DAA2B79628F0627CE9841")));
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, CopyConstructor) {
|
||||
FixedSizeData<SIZE> copy(GetParam());
|
||||
EXPECT_EQ(GetParam(), copy);
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Take_Half) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<SIZE/2> taken = source.take<SIZE/2>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE/2));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Drop_Half) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<SIZE/2> taken = source.drop<SIZE/2>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data() + SIZE/2, taken.data(), SIZE/2));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Take_One) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<1> taken = source.take<1>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), 1));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Drop_One) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<SIZE-1> taken = source.drop<1>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data() + 1, taken.data(), SIZE-1));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Take_Nothing) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<0> taken = source.take<0>();
|
||||
(void)taken; // silence unused variable warning
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Drop_Nothing) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<SIZE> taken = source.drop<0>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Take_All) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<SIZE> taken = source.take<SIZE>();
|
||||
EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE));
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, Drop_All) {
|
||||
FixedSizeData<SIZE> source(GetParam());
|
||||
FixedSizeData<0> taken = source.drop<SIZE>();
|
||||
(void)taken; // silence unused variable warning
|
||||
}
|
||||
|
||||
TEST_F(FixedSizeDataTest, CopyConstructorDoesntChangeSource) {
|
||||
FixedSizeData<SIZE> data1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> data2(data1);
|
||||
EXPECT_EQ(DATA1_AS_STRING, data1.ToString());
|
||||
(void)data2; // silence unused variable warning
|
||||
}
|
||||
|
||||
TEST_P(FixedSizeDataTestWithParam, IsEqualAfterAssignment1) {
|
||||
FixedSizeData<SIZE> data2 = FixedSizeData<SIZE>::FromString(DATA2_AS_STRING);
|
||||
EXPECT_NE(GetParam(), data2);
|
||||
data2 = GetParam();
|
||||
EXPECT_EQ(GetParam(), data2);
|
||||
}
|
||||
|
||||
TEST_F(FixedSizeDataTest, AssignmentDoesntChangeSource) {
|
||||
FixedSizeData<SIZE> data1 = FixedSizeData<SIZE>::FromString(DATA1_AS_STRING);
|
||||
FixedSizeData<SIZE> data2 = FixedSizeData<SIZE>::FromString(DATA2_AS_STRING);
|
||||
data2 = data1;
|
||||
EXPECT_EQ(DATA1_AS_STRING, data1.ToString());
|
||||
}
|
||||
|
||||
// This tests that a FixedSizeData object is very lightweight
|
||||
// (it is meant to be kept on stack and passed around)
|
||||
TEST_F(FixedSizeDataTest, IsLightweightObject) {
|
||||
EXPECT_EQ(FixedSizeData<SIZE>::BINARY_LENGTH, sizeof(FixedSizeData<SIZE>));
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cpp-utils/data/SerializationHelper.h>
|
||||
#include <cpp-utils/data/Data.h>
|
||||
|
||||
using cpputils::serialize;
|
||||
using cpputils::deserialize;
|
||||
using cpputils::deserializeWithOffset;
|
||||
using cpputils::Data;
|
||||
|
||||
TEST(SerializationHelperTest, uint8) {
|
||||
Data data(1);
|
||||
serialize<uint8_t>(data.data(), 5u);
|
||||
EXPECT_EQ(5u, deserialize<uint8_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int8_positive) {
|
||||
Data data(1);
|
||||
serialize<int8_t>(data.data(), 5);
|
||||
EXPECT_EQ(5, deserialize<int8_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int8_negative) {
|
||||
Data data(1);
|
||||
serialize<int8_t>(data.data(), -5);
|
||||
EXPECT_EQ(-5, deserialize<int8_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint16_aligned) {
|
||||
Data data(2);
|
||||
serialize<uint16_t>(data.data(), 1000u);
|
||||
EXPECT_EQ(1000u, deserialize<uint16_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint16_unaligned) {
|
||||
Data data(3);
|
||||
serialize<uint16_t>(data.dataOffset(1), 1000u);
|
||||
EXPECT_EQ(1000u, deserialize<uint16_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int16_postive_aligned) {
|
||||
Data data(2);
|
||||
serialize<int16_t>(data.data(), 1000);
|
||||
EXPECT_EQ(1000, deserialize<int16_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int16_positive_unaligned) {
|
||||
Data data(3);
|
||||
serialize<int16_t>(data.dataOffset(1), 1000);
|
||||
EXPECT_EQ(1000, deserialize<int16_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int16_negative_aligned) {
|
||||
Data data(2);
|
||||
serialize<int16_t>(data.data(), -1000);
|
||||
EXPECT_EQ(-1000, deserialize<int16_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int16_negative_unaligned) {
|
||||
Data data(3);
|
||||
serialize<int16_t>(data.dataOffset(1), -1000);
|
||||
EXPECT_EQ(-1000, deserialize<int16_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint32_aligned) {
|
||||
Data data(4);
|
||||
serialize<uint32_t>(data.data(), 100000u);
|
||||
EXPECT_EQ(100000u, deserialize<uint32_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint32_unaligned) {
|
||||
Data data(5);
|
||||
serialize<uint32_t>(data.dataOffset(1), 100000u);
|
||||
EXPECT_EQ(100000u, deserialize<uint32_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int32_positive_aligned) {
|
||||
Data data(4);
|
||||
serialize<int32_t>(data.data(), 100000);
|
||||
EXPECT_EQ(100000, deserialize<int32_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int32_positive_unaligned) {
|
||||
Data data(5);
|
||||
serialize<int32_t>(data.dataOffset(1), 100000);
|
||||
EXPECT_EQ(100000, deserialize<int32_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int32_negative_aligned) {
|
||||
Data data(4);
|
||||
serialize<int32_t>(data.data(), -100000);
|
||||
EXPECT_EQ(-100000, deserialize<int32_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int32_negative_unaligned) {
|
||||
Data data(5);
|
||||
serialize<int32_t>(data.dataOffset(1), -100000);
|
||||
EXPECT_EQ(-100000, deserialize<int32_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint64_aligned) {
|
||||
Data data(8);
|
||||
serialize<uint64_t>(data.data(), 10000000000u);
|
||||
EXPECT_EQ(10000000000u, deserialize<uint64_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, uint64_unaligned) {
|
||||
Data data(9);
|
||||
serialize<uint64_t>(data.dataOffset(1), 10000000000u);
|
||||
EXPECT_EQ(10000000000u, deserialize<uint64_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int64_positive_aligned) {
|
||||
Data data(8);
|
||||
serialize<int64_t>(data.data(), 10000000000);
|
||||
EXPECT_EQ(10000000000, deserialize<int64_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int64_positive_unaligned) {
|
||||
Data data(9);
|
||||
serialize<int64_t>(data.dataOffset(1), 10000000000);
|
||||
EXPECT_EQ(10000000000, deserialize<int64_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int64_negative_aligned) {
|
||||
Data data(8);
|
||||
serialize<int64_t>(data.data(), -10000000000);
|
||||
EXPECT_EQ(-10000000000, deserialize<int64_t>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, int64_negative_unaligned) {
|
||||
Data data(9);
|
||||
serialize<int64_t>(data.dataOffset(1), -10000000000);
|
||||
EXPECT_EQ(-10000000000, deserialize<int64_t>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, float_aligned) {
|
||||
Data data(sizeof(float));
|
||||
serialize<float>(data.data(), 3.1415f);
|
||||
EXPECT_EQ(3.1415f, deserialize<float>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, float_unaligned) {
|
||||
Data data(sizeof(float) + 1);
|
||||
serialize<float>(data.dataOffset(1), 3.1415f);
|
||||
EXPECT_EQ(3.1415f, deserialize<float>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, double_aligned) {
|
||||
Data data(sizeof(double));
|
||||
serialize<double>(data.data(), 3.1415);
|
||||
EXPECT_EQ(3.1415, deserialize<double>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, double_unaligned) {
|
||||
Data data(sizeof(double) + 1);
|
||||
serialize<double>(data.dataOffset(1), 3.1415);
|
||||
EXPECT_EQ(3.1415, deserialize<double>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct DataStructure final {
|
||||
uint64_t v1;
|
||||
uint32_t v2;
|
||||
uint16_t v3;
|
||||
uint8_t v4;
|
||||
};
|
||||
|
||||
bool operator==(const DataStructure &lhs, const DataStructure &rhs) {
|
||||
return lhs.v1 == rhs.v1 && lhs.v2 == rhs.v2 && lhs.v3 == rhs.v3 && lhs.v4 == rhs.v4;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, struct_aligned) {
|
||||
Data data(sizeof(DataStructure));
|
||||
const DataStructure fixture {10000000000u, 100000u, 1000u, 5u};
|
||||
serialize<DataStructure>(data.data(), fixture);
|
||||
EXPECT_EQ(fixture, deserialize<DataStructure>(data.data()));
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, struct_unaligned) {
|
||||
Data data(sizeof(DataStructure) + 1);
|
||||
const DataStructure fixture {10000000000u, 100000u, 1000u, 5u};
|
||||
serialize<DataStructure>(data.dataOffset(1), fixture);
|
||||
EXPECT_EQ(fixture, deserialize<DataStructure>(data.dataOffset(1)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct OneByteStruct final {
|
||||
uint8_t v;
|
||||
};
|
||||
static_assert(sizeof(OneByteStruct) == 1, "");
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, onebytestruct) {
|
||||
Data data(1);
|
||||
OneByteStruct fixture {5};
|
||||
serialize<OneByteStruct>(data.data(), fixture);
|
||||
EXPECT_EQ(fixture.v, deserialize<OneByteStruct>(data.data()).v);
|
||||
}
|
||||
|
||||
TEST(SerializationHelperTest, deserializeWithOffset) {
|
||||
Data data(5);
|
||||
serialize<uint16_t>(data.dataOffset(1), 1000);
|
||||
EXPECT_EQ(1000, deserializeWithOffset<uint16_t>(data.data(), 1));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
||||
#include "cpp-utils/io/Console.h"
|
||||
|
||||
// Test the header can be included without needing additional dependencies
|
@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef MESSMER_CPPUTILS_TEST_IO_CONSOLETEST_H
|
||||
#define MESSMER_CPPUTILS_TEST_IO_CONSOLETEST_H
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cpp-utils/io/IOStreamConsole.h"
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include "cpp-utils/io/pipestream.h"
|
||||
|
||||
class ConsoleThread {
|
||||
public:
|
||||
ConsoleThread(std::ostream &ostr, std::istream &istr): _console(ostr, istr) {}
|
||||
std::future<unsigned int> ask(const std::string &question, const std::vector<std::string> &options) {
|
||||
return std::async(std::launch::async, [this, question, options]() {
|
||||
return _console.ask(question, options);
|
||||
});
|
||||
}
|
||||
std::future<bool> askYesNo(const std::string &question) {
|
||||
return std::async(std::launch::async, [this, question]() {
|
||||
return _console.askYesNo(question, true);
|
||||
});
|
||||
}
|
||||
std::future<std::string> askPassword(const std::string &question) {
|
||||
return std::async(std::launch::async, [this, question]() {
|
||||
return _console.askPassword(question);
|
||||
});
|
||||
}
|
||||
void print(const std::string &output) {
|
||||
_console.print(output);
|
||||
}
|
||||
private:
|
||||
cpputils::IOStreamConsole _console;
|
||||
};
|
||||
|
||||
class ConsoleTest: public ::testing::Test {
|
||||
public:
|
||||
ConsoleTest(): _inputStr(), _outputStr(), _input(&_inputStr), _output(&_outputStr), _console(_output, _input) {}
|
||||
|
||||
void EXPECT_OUTPUT_LINES(std::initializer_list<std::string> lines) {
|
||||
for (const std::string &line : lines) {
|
||||
EXPECT_OUTPUT_LINE(line);
|
||||
}
|
||||
}
|
||||
|
||||
void EXPECT_OUTPUT_LINE(const std::string &expected, char delimiter = '\n', const std::string &expected_after_delimiter = "") {
|
||||
std::string actual;
|
||||
std::getline(_output, actual, delimiter);
|
||||
EXPECT_EQ(expected, actual);
|
||||
for (char expected_char : expected_after_delimiter) {
|
||||
char actual_char = 0;
|
||||
_output.get(actual_char);
|
||||
EXPECT_EQ(expected_char, actual_char);
|
||||
}
|
||||
}
|
||||
|
||||
void sendInputLine(const std::string &line) {
|
||||
_input << line << "\n" << std::flush;
|
||||
}
|
||||
|
||||
std::future<unsigned int> ask(const std::string &question, const std::vector<std::string> &options) {
|
||||
return _console.ask(question, options);
|
||||
}
|
||||
|
||||
std::future<bool> askYesNo(const std::string &question) {
|
||||
return _console.askYesNo(question);
|
||||
}
|
||||
|
||||
std::future<std::string> askPassword(const std::string &question) {
|
||||
return _console.askPassword(question);
|
||||
}
|
||||
|
||||
void print(const std::string &output) {
|
||||
_console.print(output);
|
||||
}
|
||||
|
||||
private:
|
||||
cpputils::pipestream _inputStr;
|
||||
cpputils::pipestream _outputStr;
|
||||
std::iostream _input;
|
||||
std::iostream _output;
|
||||
ConsoleThread _console;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,185 +0,0 @@
|
||||
#include "ConsoleTest.h"
|
||||
|
||||
using std::stringstream;
|
||||
using std::string;
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
|
||||
class ConsoleTest_Ask: public ConsoleTest {};
|
||||
|
||||
TEST_F(ConsoleTest_Ask, CrashesWithoutOptions) {
|
||||
EXPECT_THROW(
|
||||
(ask("My Question?", {}).get()),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, OneOption) {
|
||||
auto chosen = ask("My Question?", {"First Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-1]", ':', " ");
|
||||
sendInputLine("1");
|
||||
EXPECT_EQ(0u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, TwoOptions_ChooseFirst) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("1");
|
||||
EXPECT_EQ(0u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, TwoOptions_ChooseSecond) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseFirst) {
|
||||
auto chosen = ask("My Other Question?", {"1st Option", "2nd Option", "3rd Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Other Question?",
|
||||
" [1] 1st Option",
|
||||
" [2] 2nd Option",
|
||||
" [3] 3rd Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " ");
|
||||
sendInputLine("1");
|
||||
EXPECT_EQ(0u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseSecond) {
|
||||
auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] 1st Option",
|
||||
" [2] 2nd Option",
|
||||
" [3] 3rd Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " ");
|
||||
sendInputLine("2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseThird) {
|
||||
auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] 1st Option",
|
||||
" [2] 2nd Option",
|
||||
" [3] 3rd Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " ");
|
||||
sendInputLine("3");
|
||||
EXPECT_EQ(2u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputWithLeadingSpaces) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine(" 2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputWithFollowingSpaces) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("2 ");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputWithLeadingAndFollowingSpaces) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine(" 2 ");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputEmptyLine) {
|
||||
auto chosen = ask("My Question?", {"First Option", "Second Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] First Option",
|
||||
" [2] Second Option"
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine(" "); // empty line with space
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputWrongNumbers) {
|
||||
auto chosen = ask("My Question?", {"1st Option", "2nd Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] 1st Option",
|
||||
" [2] 2nd Option",
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("0");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("-1");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("3");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("1.5");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("1,5");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_Ask, InputNonNumbers) {
|
||||
auto chosen = ask("My Question?", {"1st Option", "2nd Option"});
|
||||
EXPECT_OUTPUT_LINES({
|
||||
"My Question?",
|
||||
" [1] 1st Option",
|
||||
" [2] 2nd Option",
|
||||
});
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("abc");
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("3a"); // Wrong number and string attached
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("1a"); // Right number but string attached
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("a3"); // Wrong number and string attached
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("a1"); // Right number but string attached
|
||||
EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " ");
|
||||
sendInputLine("2");
|
||||
EXPECT_EQ(1u, chosen.get());
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
#include "ConsoleTest.h"
|
||||
|
||||
using std::stringstream;
|
||||
using std::string;
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
|
||||
class ConsoleTest_AskPassword: public ConsoleTest {};
|
||||
|
||||
TEST_F(ConsoleTest_AskPassword, InputSomePassword) {
|
||||
auto chosen = askPassword("Please enter my password:");
|
||||
EXPECT_OUTPUT_LINE("Please enter my password", ':');
|
||||
sendInputLine("this is the password");
|
||||
EXPECT_EQ("this is the password", chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskPassword, InputEmptyPassword) {
|
||||
auto chosen = askPassword("Please enter my password:");
|
||||
EXPECT_OUTPUT_LINE("Please enter my password", ':');
|
||||
sendInputLine("");
|
||||
EXPECT_EQ("", chosen.get());
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
#include "ConsoleTest.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
class ConsoleTest_AskYesNo: public ConsoleTest {
|
||||
public:
|
||||
void EXPECT_TRUE_ON_INPUT(const string &input) {
|
||||
EXPECT_RESULT_ON_INPUT(true, input);
|
||||
}
|
||||
|
||||
void EXPECT_FALSE_ON_INPUT(const string &input) {
|
||||
EXPECT_RESULT_ON_INPUT(false, input);
|
||||
}
|
||||
|
||||
void EXPECT_RESULT_ON_INPUT(const bool expected, const string &input) {
|
||||
auto chosen = askYesNo("Are you sure blablub?");
|
||||
EXPECT_OUTPUT_LINES({"Are you sure blablub?"});
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine(input);
|
||||
EXPECT_EQ(expected, chosen.get());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_Yes) {
|
||||
EXPECT_TRUE_ON_INPUT("Yes");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_yes) {
|
||||
EXPECT_TRUE_ON_INPUT("yes");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_Y) {
|
||||
EXPECT_TRUE_ON_INPUT("Y");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_y) {
|
||||
EXPECT_TRUE_ON_INPUT("y");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_No) {
|
||||
EXPECT_FALSE_ON_INPUT("No");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_no) {
|
||||
EXPECT_FALSE_ON_INPUT("no");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_N) {
|
||||
EXPECT_FALSE_ON_INPUT("N");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, Input_n) {
|
||||
EXPECT_FALSE_ON_INPUT("n");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, InputWithLeadingSpaces) {
|
||||
EXPECT_TRUE_ON_INPUT(" y");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, InputWithFollowingSpaces) {
|
||||
EXPECT_TRUE_ON_INPUT("y ");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, InputWithLeadingAndFollowingSpaces) {
|
||||
EXPECT_TRUE_ON_INPUT(" y ");
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, InputEmptyLine) {
|
||||
auto chosen = askYesNo("My Question?");
|
||||
EXPECT_OUTPUT_LINES({"My Question?"});
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine(" "); // empty line with space
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("y");
|
||||
EXPECT_EQ(true, chosen.get());
|
||||
}
|
||||
|
||||
TEST_F(ConsoleTest_AskYesNo, WrongInput) {
|
||||
auto chosen = askYesNo("My Question?");
|
||||
EXPECT_OUTPUT_LINES({"My Question?"});
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("0");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("1");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("bla");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("Y_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("y_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("N_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("n_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("Yes_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("yes_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("No_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("no_andsomethingelse");
|
||||
EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " ");
|
||||
sendInputLine("y");
|
||||
EXPECT_EQ(true, chosen.get());
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user