diff --git a/implementations/onblocks/BlobOnBlocks.cpp b/implementations/onblocks/BlobOnBlocks.cpp index ebbae610..cb14a9b4 100644 --- a/implementations/onblocks/BlobOnBlocks.cpp +++ b/implementations/onblocks/BlobOnBlocks.cpp @@ -33,13 +33,17 @@ void BlobOnBlocks::resize(uint64_t numBytes) { void BlobOnBlocks::traverseLeaves(uint64_t beginByte, uint64_t sizeBytes, function func) const { uint64_t endByte = beginByte + sizeBytes; - assert(endByte <= size()); uint32_t firstLeaf = beginByte / _datatree->maxBytesPerLeaf(); uint32_t endLeaf = utils::ceilDivision(endByte, _datatree->maxBytesPerLeaf()); - _datatree->traverseLeaves(firstLeaf, endLeaf, [&func, beginByte, endByte](DataLeafNode *leaf, uint32_t leafIndex) { + bool traversingOutOfRange = _datatree->numStoredBytes() < endByte; //TODO numBytes() inefficient + _datatree->traverseLeaves(firstLeaf, endLeaf, [&func, beginByte, endByte, endLeaf, traversingOutOfRange](DataLeafNode *leaf, uint32_t leafIndex) { uint64_t indexOfFirstLeafByte = leafIndex * leaf->maxStoreableBytes(); uint32_t dataBegin = utils::maxZeroSubtraction(beginByte, indexOfFirstLeafByte); uint32_t dataEnd = std::min((uint64_t)leaf->maxStoreableBytes(), endByte - indexOfFirstLeafByte); + if (leafIndex == endLeaf-1 && traversingOutOfRange) { + // If we are traversing an area that didn't exist before, then the last leaf was just created with a wrong size. We have to fix it. + leaf->resize(dataEnd); + } func(indexOfFirstLeafByte, leaf, dataBegin, dataEnd-dataBegin); }); } @@ -61,7 +65,7 @@ uint64_t BlobOnBlocks::tryRead(void *target, uint64_t offset, uint64_t count) co } void BlobOnBlocks::write(const void *source, uint64_t offset, uint64_t size) { - resizeIfSmallerThan(offset + size); + //resizeIfSmallerThan(offset + size); traverseLeaves(offset, size, [source, offset] (uint64_t indexOfFirstLeafByte, DataLeafNode *leaf, uint32_t leafDataOffset, uint32_t leafDataSize) { //TODO Simplify formula, make it easier to understand leaf->write((uint8_t*)source + indexOfFirstLeafByte - offset + leafDataOffset, leafDataOffset, leafDataSize); diff --git a/implementations/onblocks/cachingdatatreestore/CachedDataTreeRef.h b/implementations/onblocks/cachingdatatreestore/CachedDataTreeRef.h index f026cd84..69a5b6a6 100644 --- a/implementations/onblocks/cachingdatatreestore/CachedDataTreeRef.h +++ b/implementations/onblocks/cachingdatatreestore/CachedDataTreeRef.h @@ -21,14 +21,14 @@ public: return _baseTree->maxBytesPerLeaf(); } - void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function func) const { - return const_cast(_baseTree)->traverseLeaves(beginIndex, endIndex, func); - } - void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function func) { return _baseTree->traverseLeaves(beginIndex, endIndex, func); } + uint32_t numLeaves() const { + return _baseTree->numLeaves(); + } + void resizeNumBytes(uint64_t newNumBytes) { return _baseTree->resizeNumBytes(newNumBytes); } diff --git a/implementations/onblocks/datatreestore/DataTree.cpp b/implementations/onblocks/datatreestore/DataTree.cpp index caa53131..ecf3667f 100644 --- a/implementations/onblocks/datatreestore/DataTree.cpp +++ b/implementations/onblocks/datatreestore/DataTree.cpp @@ -24,6 +24,7 @@ using std::function; using boost::shared_mutex; using boost::shared_lock; using boost::unique_lock; +using std::vector; using cpputils::dynamic_pointer_move; using cpputils::optional_ownership_ptr; @@ -82,8 +83,9 @@ unique_ptr DataTree::addDataLeafAt(DataInnerNode *insertPos) { return new_leaf; } -optional_ownership_ptr DataTree::createChainOfInnerNodes(unsigned int num, DataLeafNode *leaf) { - optional_ownership_ptr chain = cpputils::WithoutOwnership(leaf); +optional_ownership_ptr DataTree::createChainOfInnerNodes(unsigned int num, DataNode *child) { + //TODO This function is implemented twice, once with optional_ownership_ptr, once with unique_ptr. Redundancy! + optional_ownership_ptr chain = cpputils::WithoutOwnership(child); for(unsigned int i=0; icreateNewInnerNode(*chain); chain = cpputils::WithOwnership(std::move(newnode)); @@ -91,11 +93,27 @@ optional_ownership_ptr DataTree::createChainOfInnerNodes(unsigned int return chain; } -unique_ptr DataTree::addDataLeafToFullTree() { +unique_ptr DataTree::createChainOfInnerNodes(unsigned int num, unique_ptr child) { + unique_ptr chain = std::move(child); + for(unsigned int i=0; icreateNewInnerNode(*chain); + } + return chain; +} + +DataInnerNode* DataTree::increaseTreeDepth(unsigned int levels) { + assert(levels >= 1); auto copyOfOldRoot = _nodeStore->createNewNodeAsCopyFrom(*_rootNode); - auto newRootNode = DataNode::convertToNewInnerNode(std::move(_rootNode), *copyOfOldRoot); - auto newLeaf = addDataLeafAt(newRootNode.get()); + auto chain = createChainOfInnerNodes(levels-1, copyOfOldRoot.get()); + auto newRootNode = DataNode::convertToNewInnerNode(std::move(_rootNode), *chain); + DataInnerNode *result = newRootNode.get(); _rootNode = std::move(newRootNode); + return result; +} + +unique_ptr DataTree::addDataLeafToFullTree() { + DataInnerNode *rootNode = increaseTreeDepth(1); + auto newLeaf = addDataLeafAt(rootNode); return newLeaf; } @@ -111,22 +129,46 @@ unique_ptr DataTree::releaseRootNode() { return std::move(_rootNode); } +//TODO Test numLeaves() +uint32_t DataTree::numLeaves() const { + //TODO Direct calculating the number of leaves would be faster + uint64_t currentNumBytes = _numStoredBytes(); + if(currentNumBytes == 0) { + //We always have at least one leaf + currentNumBytes = 1; + } + return utils::ceilDivision(currentNumBytes, _nodeStore->layout().maxBytesPerLeaf()); +} + void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function func) { - const_cast(this)->traverseLeaves(beginIndex, endIndex, [func](const DataLeafNode* leaf, uint32_t leafIndex) { - func(const_cast(leaf), leafIndex); - }); -} - -void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function func) const { - shared_lock lock(_mutex); + unique_lock lock(_mutex); //TODO Only lock when resizing assert(beginIndex <= endIndex); - //TODO assert(beginIndex <= numLeaves()); - //TODO assert(endIndex <= numLeaves()); - traverseLeaves(_rootNode.get(), 0, beginIndex, endIndex, func); + + uint8_t neededTreeDepth = utils::ceilLog(_nodeStore->layout().maxChildrenPerInnerNode(), endIndex); + uint32_t numLeaves = this->numLeaves(); + if (_rootNode->depth() < neededTreeDepth) { + //TODO Test cases that actually increase it here by 0 level / 1 level / more than 1 level + increaseTreeDepth(neededTreeDepth - _rootNode->depth()); + } + if (numLeaves < endIndex) { + //TODO Can this case be efficiently combined with the traversing? + LastLeaf(_rootNode.get())->resize(_nodeStore->layout().maxBytesPerLeaf()); + } + uint32_t lastLeafIndex = std::max(numLeaves, endIndex) - 1; + if (numLeaves < beginIndex) { + //TODO Test cases with numLeaves < / >= beginIndex + return _traverseLeaves(_rootNode.get(), 0, numLeaves, endIndex, [beginIndex, numLeaves, lastLeafIndex, &func](DataLeafNode* node, uint32_t index) { + if (index >= beginIndex) { + func(node, index); + } + }); + } else { + return _traverseLeaves(_rootNode.get(), 0, beginIndex, endIndex, func); + } } -void DataTree::traverseLeaves(const DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, function func) const { - const DataLeafNode *leaf = dynamic_cast(root); +void DataTree::_traverseLeaves(DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, function func) { + DataLeafNode *leaf = dynamic_cast(root); if (leaf != nullptr) { assert(beginIndex <= 1 && endIndex <= 1); if (beginIndex == 0 && endIndex == 1) { @@ -135,20 +177,42 @@ void DataTree::traverseLeaves(const DataNode *root, uint32_t leafOffset, uint32_ return; } - const DataInnerNode *inner = dynamic_cast(root); + DataInnerNode *inner = dynamic_cast(root); uint32_t leavesPerChild = leavesPerFullChild(*inner); uint32_t beginChild = beginIndex/leavesPerChild; uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild); + vector> children = getOrCreateChildren(inner, beginChild, endChild); for (uint32_t childIndex = beginChild; childIndex < endChild; ++childIndex) { uint32_t childOffset = childIndex * leavesPerChild; uint32_t localBeginIndex = utils::maxZeroSubtraction(beginIndex, childOffset); uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset); - auto child = _nodeStore->load(inner->getChild(childIndex)->key()); - traverseLeaves(child.get(), leafOffset + childOffset, localBeginIndex, localEndIndex, func); + auto child = std::move(children[childIndex-beginChild]); + _traverseLeaves(child.get(), leafOffset + childOffset, localBeginIndex, localEndIndex, func); } } +vector> DataTree::getOrCreateChildren(DataInnerNode *node, uint32_t begin, uint32_t end) { + vector> children; + children.reserve(end-begin); + for (uint32_t childIndex = begin; childIndex < std::min(node->numChildren(), end); ++childIndex) { + children.emplace_back(_nodeStore->load(node->getChild(childIndex)->key())); + } + for (uint32_t childIndex = node->numChildren(); childIndex < end; ++childIndex) { + children.emplace_back(addChildTo(node)); + } + assert(children.size() == end-begin); + return children; +} + +unique_ptr DataTree::addChildTo(DataInnerNode *node) { + auto new_leaf = _nodeStore->createNewLeafNode(); + new_leaf->resize(_nodeStore->layout().maxBytesPerLeaf()); + auto chain = createChainOfInnerNodes(node->depth()-1, std::move(new_leaf)); + node->addChild(*chain); + return std::move(chain); +} + uint32_t DataTree::leavesPerFullChild(const DataInnerNode &root) const { return utils::intPow(_nodeStore->layout().maxChildrenPerInnerNode(), root.depth()-1); } diff --git a/implementations/onblocks/datatreestore/DataTree.h b/implementations/onblocks/datatreestore/DataTree.h index 0d726e13..7723b0bd 100644 --- a/implementations/onblocks/datatreestore/DataTree.h +++ b/implementations/onblocks/datatreestore/DataTree.h @@ -29,10 +29,10 @@ public: const blockstore::Key &key() const; uint32_t maxBytesPerLeaf() const; - void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function func) const; void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function func); void resizeNumBytes(uint64_t newNumBytes); + uint32_t numLeaves() const; uint64_t numStoredBytes() const; void flush() const; @@ -49,19 +49,23 @@ private: friend class DataTreeStore; std::unique_ptr addDataLeafAt(datanodestore::DataInnerNode *insertPos); - cpputils::optional_ownership_ptr createChainOfInnerNodes(unsigned int num, datanodestore::DataLeafNode *leaf); + cpputils::optional_ownership_ptr createChainOfInnerNodes(unsigned int num, datanodestore::DataNode *child); + std::unique_ptr createChainOfInnerNodes(unsigned int num, std::unique_ptr child); std::unique_ptr addDataLeafToFullTree(); void deleteLastChildSubtree(datanodestore::DataInnerNode *node); void ifRootHasOnlyOneChildReplaceRootWithItsChild(); //TODO Use underscore for private methods - void traverseLeaves(const datanodestore::DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, std::function func) const; + void _traverseLeaves(datanodestore::DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, std::function func); uint32_t leavesPerFullChild(const datanodestore::DataInnerNode &root) const; uint64_t _numStoredBytes() const; uint64_t _numStoredBytes(const datanodestore::DataNode &root) const; cpputils::optional_ownership_ptr LastLeaf(datanodestore::DataNode *root); std::unique_ptr LastLeaf(std::unique_ptr root); + datanodestore::DataInnerNode* increaseTreeDepth(unsigned int levels); + std::vector> getOrCreateChildren(datanodestore::DataInnerNode *node, uint32_t begin, uint32_t end); + std::unique_ptr addChildTo(datanodestore::DataInnerNode *node); DISALLOW_COPY_AND_ASSIGN(DataTree); }; diff --git a/implementations/onblocks/utils/Math.cpp b/implementations/onblocks/utils/Math.cpp index 5bec6846..ab096a36 100644 --- a/implementations/onblocks/utils/Math.cpp +++ b/implementations/onblocks/utils/Math.cpp @@ -1,5 +1,7 @@ #include "Math.h" +#include + namespace blobstore { namespace onblocks { namespace utils { @@ -23,6 +25,10 @@ uint32_t maxZeroSubtraction(uint32_t minuend, uint32_t subtrahend) { return minuend-subtrahend; } +uint32_t ceilLog(uint32_t base, uint32_t value) { + return std::ceil((long double)std::log(value)/(long double)std::log(base)); +} + } } } diff --git a/implementations/onblocks/utils/Math.h b/implementations/onblocks/utils/Math.h index d5ae3318..5de48a1d 100644 --- a/implementations/onblocks/utils/Math.h +++ b/implementations/onblocks/utils/Math.h @@ -11,6 +11,7 @@ namespace utils { uint32_t intPow(uint32_t base, uint32_t exponent); uint32_t ceilDivision(uint32_t dividend, uint32_t divisor); uint32_t maxZeroSubtraction(uint32_t minuend, uint32_t subtrahend); +uint32_t ceilLog(uint32_t base, uint32_t value); } } diff --git a/test/implementations/onblocks/BlobReadWriteTest.cpp b/test/implementations/onblocks/BlobReadWriteTest.cpp index 8de5c928..49054000 100644 --- a/test/implementations/onblocks/BlobReadWriteTest.cpp +++ b/test/implementations/onblocks/BlobReadWriteTest.cpp @@ -109,6 +109,12 @@ INSTANTIATE_TEST_CASE_P(BlobReadWriteDataTest, BlobReadWriteDataTest, Values( 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); diff --git a/test/implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp b/test/implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp new file mode 100644 index 00000000..3a17d8f4 --- /dev/null +++ b/test/implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp @@ -0,0 +1,200 @@ +#include "testutils/DataTreeTest.h" +#include "testutils/TwoLevelDataFixture.h" +#include "../../../../implementations/onblocks/utils/Math.h" +#include + +#include + +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::Key; +using blockstore::Data; + +using std::unique_ptr; + +class DataTreeTest_ResizeByTraversing: public DataTreeTest { +public: + static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES); + + unique_ptr CreateTree(unique_ptr root) { + Key key = root->key(); + root.reset(); + return treeStore.load(key); + } + + unique_ptr CreateLeafTreeWithSize(uint32_t size) { + return CreateTree(CreateLeafWithSize(size)); + } + + unique_ptr CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) { + return CreateTree(CreateTwoLeafWithSecondLeafSize(size)); + } + + unique_ptr CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) { + return CreateTree(CreateFullTwoLevelWithLastLeafSize(size)); + } + + unique_ptr CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) { + return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size)); + } + + unique_ptr CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) { + return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size)); + } + + unique_ptr CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) { + return CreateTree(CreateFullThreeLevelWithLastLeafSize(size)); + } + + unique_ptr CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) { + return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size)); + } + + void EXPECT_IS_LEFTMAXDATA_TREE(const Key &key) { + auto root = nodeStore->load(key); + DataInnerNode *inner = dynamic_cast(root.get()); + if (inner != nullptr) { + for (uint32_t i = 0; i < inner->numChildren()-1; ++i) { + EXPECT_IS_MAXDATA_TREE(inner->getChild(i)->key()); + } + EXPECT_IS_LEFTMAXDATA_TREE(inner->LastChild()->key()); + } + } + + void EXPECT_IS_MAXDATA_TREE(const Key &key) { + auto root = nodeStore->load(key); + DataInnerNode *inner = dynamic_cast(root.get()); + if (inner != nullptr) { + for (uint32_t i = 0; i < inner->numChildren(); ++i) { + EXPECT_IS_MAXDATA_TREE(inner->getChild(i)->key()); + } + } else { + DataLeafNode *leaf = dynamic_cast(root.get()); + EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes()); + } + } +}; +constexpr DataNodeLayout DataTreeTest_ResizeByTraversing::LAYOUT; + +class DataTreeTest_ResizeByTraversing_P: public DataTreeTest_ResizeByTraversing, public WithParamInterface(DataTreeTest_ResizeByTraversing*, uint32_t)>, uint32_t, uint32_t>> { +public: + DataTreeTest_ResizeByTraversing_P() + : oldLastLeafSize(get<1>(GetParam())), + tree(get<0>(GetParam())(this, oldLastLeafSize)), + numberOfLeavesToAdd(get<2>(GetParam())), + newNumberOfLeaves(tree->numLeaves()+numberOfLeavesToAdd), + ZEROES(LAYOUT.maxBytesPerLeaf()) + { + ZEROES.FillWithZeroes(); + } + + void GrowTree(const Key &key, uint32_t numLeavesToAdd) { + auto tree = treeStore.load(key); + GrowTree(tree.get(), numLeavesToAdd); + } + + void GrowTree(DataTree *tree, uint32_t numLeavesToAdd) { + uint32_t oldNumLeaves = tree->numLeaves(); + uint32_t newNumLeaves = oldNumLeaves + numLeavesToAdd; + //TODO Test cases where beginIndex is inside of the existing leaves + tree->traverseLeaves(newNumLeaves-1, newNumLeaves, [] (DataLeafNode*,uint32_t){}); + tree->flush(); + } + + unique_ptr LastLeaf(const Key &key) { + auto root = nodeStore->load(key); + auto leaf = dynamic_pointer_move(root); + if (leaf.get() != nullptr) { + return leaf; + } + auto inner = dynamic_pointer_move(root); + return LastLeaf(inner->LastChild()->key()); + } + + uint32_t oldLastLeafSize; + unique_ptr tree; + uint32_t numberOfLeavesToAdd; + uint32_t newNumberOfLeaves; + Data ZEROES; +}; +INSTANTIATE_TEST_CASE_P(DataTreeTest_ResizeByTraversing_P, DataTreeTest_ResizeByTraversing_P, + Combine( + //Tree we're starting with + Values(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 + ) + ) +); + +TEST_P(DataTreeTest_ResizeByTraversing_P, StructureIsValid) { + GrowTree(tree.get(), numberOfLeavesToAdd); + EXPECT_IS_LEFTMAXDATA_TREE(tree->key()); +} + +TEST_P(DataTreeTest_ResizeByTraversing_P, NumBytesIsCorrect) { + GrowTree(tree.get(), numberOfLeavesToAdd); + // 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, DepthFlagsAreCorrect) { + GrowTree(tree.get(), numberOfLeavesToAdd); + uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode())); + CHECK_DEPTH(depth, tree->key()); +} + +TEST_P(DataTreeTest_ResizeByTraversing_P, KeyDoesntChange) { + Key key = tree->key(); + tree->flush(); + GrowTree(tree.get(), numberOfLeavesToAdd); + EXPECT_EQ(key, tree->key()); +} + +TEST_P(DataTreeTest_ResizeByTraversing_P, DataStaysIntact) { + uint32_t oldNumberOfLeaves = std::max(1u, ceilDivision(tree->numStoredBytes(), nodeStore->layout().maxBytesPerLeaf())); + TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged); + Key key = tree->key(); + tree.reset(); + data.FillInto(nodeStore->load(key).get()); + + GrowTree(key, newNumberOfLeaves); + + data.EXPECT_DATA_CORRECT(nodeStore->load(key).get(), oldNumberOfLeaves, oldLastLeafSize); +} diff --git a/test/implementations/onblocks/utils/CeilLogTest.cpp b/test/implementations/onblocks/utils/CeilLogTest.cpp new file mode 100644 index 00000000..4851da39 --- /dev/null +++ b/test/implementations/onblocks/utils/CeilLogTest.cpp @@ -0,0 +1,32 @@ +#include +#include "../../../../implementations/onblocks/utils/Math.h" + +#include + +using namespace blobstore::onblocks::utils; +using ::testing::Test; +using std::numeric_limits; + +class CeilLogTest: public Test {}; + +TEST_F(CeilLogTest, Log3_0) { + EXPECT_EQ(0, ceilLog(3, 0)); +} + +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)); +} + +//TODO ...