When traversing a blob out of range, automatically create the leaves up to the traversed point
This commit is contained in:
parent
bb98e9b31d
commit
bee68464dc
@ -33,13 +33,17 @@ void BlobOnBlocks::resize(uint64_t numBytes) {
|
||||
|
||||
void BlobOnBlocks::traverseLeaves(uint64_t beginByte, uint64_t sizeBytes, function<void (uint64_t, DataLeafNode *leaf, uint32_t, uint32_t)> 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);
|
||||
|
@ -21,14 +21,14 @@ public:
|
||||
return _baseTree->maxBytesPerLeaf();
|
||||
}
|
||||
|
||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (const datanodestore::DataLeafNode*, uint32_t)> func) const {
|
||||
return const_cast<const datatreestore::DataTree*>(_baseTree)->traverseLeaves(beginIndex, endIndex, func);
|
||||
}
|
||||
|
||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func) {
|
||||
return _baseTree->traverseLeaves(beginIndex, endIndex, func);
|
||||
}
|
||||
|
||||
uint32_t numLeaves() const {
|
||||
return _baseTree->numLeaves();
|
||||
}
|
||||
|
||||
void resizeNumBytes(uint64_t newNumBytes) {
|
||||
return _baseTree->resizeNumBytes(newNumBytes);
|
||||
}
|
||||
|
@ -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<DataLeafNode> DataTree::addDataLeafAt(DataInnerNode *insertPos) {
|
||||
return new_leaf;
|
||||
}
|
||||
|
||||
optional_ownership_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int num, DataLeafNode *leaf) {
|
||||
optional_ownership_ptr<DataNode> chain = cpputils::WithoutOwnership<DataNode>(leaf);
|
||||
optional_ownership_ptr<DataNode> 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<DataNode> chain = cpputils::WithoutOwnership<DataNode>(child);
|
||||
for(unsigned int i=0; i<num; ++i) {
|
||||
auto newnode = _nodeStore->createNewInnerNode(*chain);
|
||||
chain = cpputils::WithOwnership<DataNode>(std::move(newnode));
|
||||
@ -91,11 +93,27 @@ optional_ownership_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int
|
||||
return chain;
|
||||
}
|
||||
|
||||
unique_ptr<DataLeafNode> DataTree::addDataLeafToFullTree() {
|
||||
unique_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int num, unique_ptr<DataNode> child) {
|
||||
unique_ptr<DataNode> chain = std::move(child);
|
||||
for(unsigned int i=0; i<num; ++i) {
|
||||
chain = _nodeStore->createNewInnerNode(*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<DataLeafNode> DataTree::addDataLeafToFullTree() {
|
||||
DataInnerNode *rootNode = increaseTreeDepth(1);
|
||||
auto newLeaf = addDataLeafAt(rootNode);
|
||||
return newLeaf;
|
||||
}
|
||||
|
||||
@ -111,22 +129,46 @@ unique_ptr<DataNode> 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<void (DataLeafNode*, uint32_t)> func) {
|
||||
const_cast<const DataTree*>(this)->traverseLeaves(beginIndex, endIndex, [func](const DataLeafNode* leaf, uint32_t leafIndex) {
|
||||
func(const_cast<DataLeafNode*>(leaf), leafIndex);
|
||||
});
|
||||
}
|
||||
|
||||
void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function<void (const DataLeafNode*, uint32_t)> func) const {
|
||||
shared_lock<shared_mutex> lock(_mutex);
|
||||
unique_lock<shared_mutex> 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<void (const DataLeafNode*, uint32_t)> func) const {
|
||||
const DataLeafNode *leaf = dynamic_cast<const DataLeafNode*>(root);
|
||||
void DataTree::_traverseLeaves(DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, function<void (DataLeafNode*, uint32_t)> func) {
|
||||
DataLeafNode *leaf = dynamic_cast<DataLeafNode*>(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<const DataInnerNode*>(root);
|
||||
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root);
|
||||
uint32_t leavesPerChild = leavesPerFullChild(*inner);
|
||||
uint32_t beginChild = beginIndex/leavesPerChild;
|
||||
uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild);
|
||||
vector<unique_ptr<DataNode>> 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<unique_ptr<DataNode>> DataTree::getOrCreateChildren(DataInnerNode *node, uint32_t begin, uint32_t end) {
|
||||
vector<unique_ptr<DataNode>> 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<DataNode> 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);
|
||||
}
|
||||
|
@ -29,10 +29,10 @@ public:
|
||||
const blockstore::Key &key() const;
|
||||
uint32_t maxBytesPerLeaf() const;
|
||||
|
||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (const datanodestore::DataLeafNode*, uint32_t)> func) const;
|
||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> 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<datanodestore::DataLeafNode> addDataLeafAt(datanodestore::DataInnerNode *insertPos);
|
||||
cpputils::optional_ownership_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, datanodestore::DataLeafNode *leaf);
|
||||
cpputils::optional_ownership_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, datanodestore::DataNode *child);
|
||||
std::unique_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, std::unique_ptr<datanodestore::DataNode> child);
|
||||
std::unique_ptr<datanodestore::DataLeafNode> 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<void (const datanodestore::DataLeafNode*, uint32_t)> func) const;
|
||||
void _traverseLeaves(datanodestore::DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> 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<datanodestore::DataLeafNode> LastLeaf(datanodestore::DataNode *root);
|
||||
std::unique_ptr<datanodestore::DataLeafNode> LastLeaf(std::unique_ptr<datanodestore::DataNode> root);
|
||||
datanodestore::DataInnerNode* increaseTreeDepth(unsigned int levels);
|
||||
std::vector<std::unique_ptr<datanodestore::DataNode>> getOrCreateChildren(datanodestore::DataInnerNode *node, uint32_t begin, uint32_t end);
|
||||
std::unique_ptr<datanodestore::DataNode> addChildTo(datanodestore::DataInnerNode *node);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DataTree);
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "Math.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,200 @@
|
||||
#include "testutils/DataTreeTest.h"
|
||||
#include "testutils/TwoLevelDataFixture.h"
|
||||
#include "../../../../implementations/onblocks/utils/Math.h"
|
||||
#include <messmer/blockstore/utils/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::Key;
|
||||
using blockstore::Data;
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
class DataTreeTest_ResizeByTraversing: public DataTreeTest {
|
||||
public:
|
||||
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||
|
||||
unique_ptr<DataTree> CreateTree(unique_ptr<DataNode> root) {
|
||||
Key key = root->key();
|
||||
root.reset();
|
||||
return treeStore.load(key);
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateLeafTreeWithSize(uint32_t size) {
|
||||
return CreateTree(CreateLeafWithSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateTwoLeafWithSecondLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullTwoLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) {
|
||||
return CreateTree(CreateFullThreeLevelWithLastLeafSize(size));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> 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<DataInnerNode*>(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<DataInnerNode*>(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<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_ptr<DataTree>(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<DataLeafNode> LastLeaf(const Key &key) {
|
||||
auto root = nodeStore->load(key);
|
||||
auto leaf = dynamic_pointer_move<DataLeafNode>(root);
|
||||
if (leaf.get() != nullptr) {
|
||||
return leaf;
|
||||
}
|
||||
auto inner = dynamic_pointer_move<DataInnerNode>(root);
|
||||
return LastLeaf(inner->LastChild()->key());
|
||||
}
|
||||
|
||||
uint32_t oldLastLeafSize;
|
||||
unique_ptr<DataTree> 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<function<unique_ptr<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
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
32
test/implementations/onblocks/utils/CeilLogTest.cpp
Normal file
32
test/implementations/onblocks/utils/CeilLogTest.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include <google/gtest/gtest.h>
|
||||
#include "../../../../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_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 ...
|
Loading…
x
Reference in New Issue
Block a user