Added tests ensuring that the data stays intact on shrinking
This commit is contained in:
parent
6c9d4cca39
commit
f7e710f305
@ -1,6 +1,6 @@
|
||||
#include "testutils/DataTreeGrowingTest.h"
|
||||
#include "testutils/LeafDataFixture.h"
|
||||
#include "testutils/TwoLevelDataFixture.h"
|
||||
#include "../testutils/LeafDataFixture.h"
|
||||
#include "../testutils/TwoLevelDataFixture.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
@ -16,22 +16,22 @@ using cpputils::dynamic_pointer_move;
|
||||
|
||||
class DataTreeGrowingTest_DataStaysIntact: public DataTreeGrowingTest {
|
||||
public:
|
||||
unique_ptr<DataTree> CreateLeafOnlyTreeWithData(const LeafDataFixture &data) {
|
||||
unique_ptr<DataTree> CreateLeafOnlyTreeWithData(TwoLevelDataFixture *data) {
|
||||
auto leafnode = nodeStore.createNewLeafNode();
|
||||
data.FillInto(leafnode.get());
|
||||
data->FillInto(leafnode.get());
|
||||
|
||||
return make_unique<DataTree>(&nodeStore, std::move(leafnode));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateTwoNodeTreeWithData(const LeafDataFixture &data) {
|
||||
auto tree = CreateLeafOnlyTreeWithData(data);
|
||||
tree->addDataLeaf();
|
||||
return tree;
|
||||
unique_ptr<DataTree> CreateTwoNodeTreeWithData(TwoLevelDataFixture *data) {
|
||||
auto root = CreateInner({CreateLeaf(), CreateLeaf()});
|
||||
data->FillInto(root.get());
|
||||
return make_unique<DataTree>(&nodeStore, std::move(root));
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateThreeNodeChainedTreeWithData(const LeafDataFixture &data) {
|
||||
unique_ptr<DataTree> CreateThreeNodeChainedTreeWithData(TwoLevelDataFixture *data) {
|
||||
auto leaf = nodeStore.createNewLeafNode();
|
||||
data.FillInto(leaf.get());
|
||||
data->FillInto(leaf.get());
|
||||
|
||||
auto inner = nodeStore.createNewInnerNode(*leaf);
|
||||
return make_unique<DataTree>(&nodeStore, nodeStore.createNewInnerNode(*inner));
|
||||
@ -45,26 +45,9 @@ public:
|
||||
}
|
||||
|
||||
unique_ptr<DataTree> CreateThreeLevelTreeWithLowerLevelFullWithData(TwoLevelDataFixture *data) {
|
||||
auto _node = LoadFirstChildOf(CreateThreeLevelTreeWithLowerLevelFullReturnRootKey());
|
||||
auto node = dynamic_pointer_move<DataInnerNode>(_node);
|
||||
data->FillInto(node.get());
|
||||
return make_unique<DataTree>(&nodeStore, std::move(node));
|
||||
}
|
||||
|
||||
unique_ptr<DataNode> LoadFirstChildOf(const Key &key) {
|
||||
auto root = LoadInnerNode(key);
|
||||
return nodeStore.load(root->getChild(0)->key());
|
||||
}
|
||||
|
||||
unique_ptr<DataLeafNode> LoadFirstLeafOf(const Key &key) {
|
||||
auto root = LoadInnerNode(key);
|
||||
return LoadLeafNode(root->getChild(0)->key());
|
||||
}
|
||||
|
||||
unique_ptr<DataLeafNode> LoadTwoLevelFirstLeafOf(const Key &key) {
|
||||
auto root = LoadInnerNode(key);
|
||||
auto inner = LoadInnerNode(root->getChild(0)->key());
|
||||
return LoadLeafNode(inner->getChild(0)->key());
|
||||
auto root = CreateInner({CreateFullTwoLevel()});
|
||||
data->FillInto(root.get());
|
||||
return make_unique<DataTree>(&nodeStore, std::move(root));
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,8 +57,8 @@ TEST_F(DataTreeGrowingTest_DataStaysIntact, GrowAFullTwoLevelTree) {
|
||||
tree->addDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
auto node = LoadFirstChildOf(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(*dynamic_pointer_move<DataInnerNode>(node));
|
||||
auto root = LoadInnerNode(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(dynamic_pointer_move<DataInnerNode>(root).get(), DataInnerNode::MAX_STORED_CHILDREN);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeGrowingTest_DataStaysIntact, GrowAThreeLevelTreeWithLowerLevelFull) {
|
||||
@ -84,8 +67,8 @@ TEST_F(DataTreeGrowingTest_DataStaysIntact, GrowAThreeLevelTreeWithLowerLevelFul
|
||||
tree->addDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
auto node = LoadFirstChildOf(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(*dynamic_pointer_move<DataInnerNode>(node));
|
||||
auto root = LoadInnerNode(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(dynamic_pointer_move<DataInnerNode>(root).get(), DataInnerNode::MAX_STORED_CHILDREN);
|
||||
}
|
||||
|
||||
class DataTreeGrowingTest_DataStaysIntact_OneDataLeaf: public DataTreeGrowingTest_DataStaysIntact, public WithParamInterface<uint32_t> {
|
||||
@ -93,31 +76,31 @@ class DataTreeGrowingTest_DataStaysIntact_OneDataLeaf: public DataTreeGrowingTes
|
||||
INSTANTIATE_TEST_CASE_P(DataTreeGrowingTest_DataStaysIntact_OneDataLeaf, DataTreeGrowingTest_DataStaysIntact_OneDataLeaf, Values(0, 1, DataLeafNode::MAX_STORED_BYTES-2, DataLeafNode::MAX_STORED_BYTES-1, DataLeafNode::MAX_STORED_BYTES));
|
||||
|
||||
TEST_P(DataTreeGrowingTest_DataStaysIntact_OneDataLeaf, GrowAOneNodeTree) {
|
||||
LeafDataFixture data(GetParam());
|
||||
auto tree = CreateLeafOnlyTreeWithData(data);
|
||||
TwoLevelDataFixture data(&nodeStore, GetParam(), true);
|
||||
auto tree = CreateLeafOnlyTreeWithData(&data);
|
||||
tree->addDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
auto leaf = LoadFirstLeafOf(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(*leaf);
|
||||
auto root = LoadInnerNode(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(root.get(), 1);
|
||||
}
|
||||
|
||||
TEST_P(DataTreeGrowingTest_DataStaysIntact_OneDataLeaf, GrowATwoNodeTree) {
|
||||
LeafDataFixture data(GetParam());
|
||||
auto tree = CreateTwoNodeTreeWithData(data);
|
||||
TwoLevelDataFixture data(&nodeStore, GetParam(), true);
|
||||
auto tree = CreateTwoNodeTreeWithData(&data);
|
||||
tree->addDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
auto leaf = LoadFirstLeafOf(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(*leaf);
|
||||
auto root = LoadInnerNode(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(root.get(), 2);
|
||||
}
|
||||
|
||||
TEST_P(DataTreeGrowingTest_DataStaysIntact_OneDataLeaf, GrowAThreeNodeChainedTree) {
|
||||
LeafDataFixture data(GetParam());
|
||||
auto tree = CreateThreeNodeChainedTreeWithData(data);
|
||||
TwoLevelDataFixture data(&nodeStore, GetParam(), true);
|
||||
auto tree = CreateThreeNodeChainedTreeWithData(&data);
|
||||
tree->addDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
auto leaf = LoadTwoLevelFirstLeafOf(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(*leaf);
|
||||
auto root = LoadInnerNode(tree->key());
|
||||
data.EXPECT_DATA_CORRECT(root.get(), 1);
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef BLOCKS_MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
#define BLOCKS_MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
|
||||
#include <messmer/cpp-utils/macros.h>
|
||||
#include <messmer/cpp-utils/pointer.h>
|
||||
|
||||
// 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:
|
||||
TwoLevelDataFixture(blobstore::onblocks::datanodestore::DataNodeStore *dataNodeStore): _dataNodeStore(dataNodeStore) {}
|
||||
|
||||
void FillInto(blobstore::onblocks::datanodestore::DataInnerNode *node) {
|
||||
for (int i = 0; i < node->numChildren(); ++i) {
|
||||
auto leafnode = _dataNodeStore->load(node->getChild(i)->key());
|
||||
auto leaf = cpputils::dynamic_pointer_move<blobstore::onblocks::datanodestore::DataLeafNode>(leafnode);
|
||||
LeafDataFixture(size(i), i).FillInto(leaf.get());
|
||||
}
|
||||
}
|
||||
|
||||
void EXPECT_DATA_CORRECT(const blobstore::onblocks::datanodestore::DataInnerNode &node) const {
|
||||
for (int i = 0; i < node.numChildren(); ++i) {
|
||||
auto leafnode =_dataNodeStore->load(node.getChild(i)->key());
|
||||
auto leaf = cpputils::dynamic_pointer_move<blobstore::onblocks::datanodestore::DataLeafNode>(leafnode);
|
||||
LeafDataFixture(size(i), i).EXPECT_DATA_CORRECT(*leaf);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
blobstore::onblocks::datanodestore::DataNodeStore *_dataNodeStore;
|
||||
|
||||
static int size(int childIndex) {
|
||||
return blobstore::onblocks::datanodestore::DataLeafNode::MAX_STORED_BYTES-childIndex;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,126 @@
|
||||
#include "testutils/DataTreeShrinkingTest.h"
|
||||
#include "../testutils/TwoLevelDataFixture.h"
|
||||
|
||||
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||
using blobstore::onblocks::datatreestore::DataTree;
|
||||
using blockstore::Key;
|
||||
|
||||
using std::make_unique;
|
||||
using std::unique_ptr;
|
||||
using std::function;
|
||||
|
||||
class DataTreeShrinkingTest_DataStaysIntact: public DataTreeShrinkingTest {
|
||||
public:
|
||||
unique_ptr<DataTree> TwoLevelTreeWithData(unique_ptr<DataInnerNode> root, TwoLevelDataFixture *data) {
|
||||
data->FillInto(root.get());
|
||||
return make_unique<DataTree>(&nodeStore, std::move(root));
|
||||
}
|
||||
|
||||
void TestDataStaysIntactOnShrinking(unique_ptr<DataInnerNode> root, TwoLevelDataFixture *data) {
|
||||
auto tree = TwoLevelTreeWithData(std::move(root), data);
|
||||
tree->removeLastDataLeaf();
|
||||
tree->flush();
|
||||
|
||||
data->EXPECT_DATA_CORRECT(nodeStore.load(tree->key()).get());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoLeafTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoLeaf(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoLeafTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoLeaf(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourNodeThreeLeafTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateFourNodeThreeLeaf(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourNodeThreeLeafTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateFourNodeThreeLeaf(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoInnerNodeOneTwoLeavesTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoInnerNodeOneTwoLeaves(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoInnerNodeOneTwoLeavesTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoInnerNodeOneTwoLeaves(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoInnerNodeTwoOneLeavesTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoInnerNodeTwoOneLeaves(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATwoInnerNodeTwoOneLeavesTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateTwoInnerNodeTwoOneLeaves(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAThreeLevelMinDataTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateThreeLevelMinData(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAThreeLevelMinDataTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateThreeLevelMinData(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelMinDataTree_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelMinData(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelMinDataTree_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelMinData(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelTreeWithTwoSiblingLeaves1_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelWithTwoSiblingLeaves1(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelTreeWithTwoSiblingLeaves1_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelWithTwoSiblingLeaves1(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelTreeWithTwoSiblingLeaves2_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelWithTwoSiblingLeaves2(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAFourLevelTreeWithTwoSiblingLeaves2_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateFourLevelWithTwoSiblingLeaves2(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATreeWithFirstChildOfRootFullThreelevelAndSecondChildMindataThreelevel_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateWithFirstChildOfRootFullThreelevelAndSecondChildMindataThreelevel(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkATreeWithFirstChildOfRootFullThreelevelAndSecondChildMindataThreelevel_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateWithFirstChildOfRootFullThreelevelAndSecondChildMindataThreelevel(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAThreeLevelTreeWithThreeChildrenOfRoot_FullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, true);
|
||||
TestDataStaysIntactOnShrinking(CreateThreeLevelWithThreeChildrenOfRoot(), &data);
|
||||
}
|
||||
|
||||
TEST_F(DataTreeShrinkingTest_DataStaysIntact, ShrinkAThreeLevelTreeWithThreeChildrenOfRoot_NonFullLeaves) {
|
||||
TwoLevelDataFixture data(&nodeStore, 0, false);
|
||||
TestDataStaysIntactOnShrinking(CreateThreeLevelWithThreeChildrenOfRoot(), &data);
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include <google/gtest/gtest.h>
|
||||
|
||||
#include "../../../../../testutils/DataBlockFixture.h"
|
||||
#include "../../../../testutils/DataBlockFixture.h"
|
||||
|
||||
// A data fixture containing data for a leaf.
|
||||
// The class can fill this data into a given leaf
|
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#ifndef BLOCKS_MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
#define BLOCKS_MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_
|
||||
|
||||
#include <messmer/cpp-utils/macros.h>
|
||||
#include <messmer/cpp-utils/pointer.h>
|
||||
#include "LeafDataFixture.h"
|
||||
|
||||
// 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:
|
||||
TwoLevelDataFixture(blobstore::onblocks::datanodestore::DataNodeStore *dataNodeStore, int iv=0, bool useFullSizeLeaves = false): _dataNodeStore(dataNodeStore), _iv(iv), _useFullSizeLeaves(useFullSizeLeaves) {}
|
||||
|
||||
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), leafIndex).FillInto(leaf);
|
||||
});
|
||||
}
|
||||
|
||||
void EXPECT_DATA_CORRECT(blobstore::onblocks::datanodestore::DataNode *node, int maxCheckedLeaves = 0) {
|
||||
ForEachLeaf(node, _iv, _iv+maxCheckedLeaves, [this] (blobstore::onblocks::datanodestore::DataLeafNode *leaf, int leafIndex) {
|
||||
LeafDataFixture(size(leafIndex), leafIndex).EXPECT_DATA_CORRECT(*leaf);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
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 (int i = 0; i < inner->numChildren(); ++i) {
|
||||
auto child = _dataNodeStore->load(inner->getChild(i)->key());
|
||||
leafIndex = ForEachLeaf(child.get(), leafIndex, endLeafIndex, action);
|
||||
}
|
||||
return leafIndex;
|
||||
}
|
||||
}
|
||||
|
||||
blobstore::onblocks::datanodestore::DataNodeStore *_dataNodeStore;
|
||||
int _iv;
|
||||
bool _useFullSizeLeaves;
|
||||
|
||||
int size(int childIndex) {
|
||||
if (_useFullSizeLeaves) {
|
||||
return blobstore::onblocks::datanodestore::DataLeafNode::MAX_STORED_BYTES;
|
||||
} else {
|
||||
return mod(blobstore::onblocks::datanodestore::DataLeafNode::MAX_STORED_BYTES - childIndex, blobstore::onblocks::datanodestore::DataLeafNode::MAX_STORED_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
int mod(int value, int mod) {
|
||||
while(value < 0) {
|
||||
value += mod;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user