diff --git a/src/BPTree.c b/src/BPTree.c index 9c984e39af841ada2bd3ecb8e8c7677e79c2b6fb..67da7a471c878714cd48757327806bd0db130a1f 100644 --- a/src/BPTree.c +++ b/src/BPTree.c @@ -304,7 +304,7 @@ static bool _BPTree_delete(BPTreeNode *root, uint64_t key, BPTreeNode *parent) { root->keys->items[index] = find_smallest_key(root->children->items[index + 1]); } - if (parent == NULL && root->keys->size == 0) { + if (parent == NULL && !root->is_leaf && root->keys->size == 0) { shrink(root); } diff --git a/src/Makefile b/src/Makefile index 5589bd614b3a4f38c7fb4149fde61d4b25d76a43..04628dbb0588d1ca47ee98d85768c998da8490e6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -22,13 +22,13 @@ $(TARGET): $(OBJECTS) # TESTS_BEGIN -unity.o: tests/unity.c tests/unity.h +Unity.o: tests/Unity/unity.c tests/Unity/unity.h $(CC) $(CFLAGS) -c $< -o $@ -testtest.o: tests/testtest.c +BPTreeTests.o: tests/BPTreeTests.c $(CC) $(CFLAGS) -c $< -o $@ -make_run_tests: unity.o testtest.o Array.o +make_run_tests: Unity.o BPTreeTests.o Array.o BPTree.o $(CC) $^ $(CFLAGS) $(LIBS) -o tests_exec ./tests_exec || true diff --git a/src/tests/BPTreeTests.c b/src/tests/BPTreeTests.c new file mode 100644 index 0000000000000000000000000000000000000000..06a1249b06fa437f5d2130a2b83586dd8650e74e --- /dev/null +++ b/src/tests/BPTreeTests.c @@ -0,0 +1,402 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../Array.h" +#include "../BPTree.h" +#include "Unity/unity.h" + +#define RANDOM_MIN 0 +#define RANDOM_MAX 1000 + +BPTreeNode *tree; + +void setUp(void) { +} + +void tearDown(void) { + if (tree != NULL) { + BPTree_destroy(&tree); + } +} + +// BEGIN : Compliance with B+ Tree rules + +static bool check_if_BPTree_is_compliant_with_the_rules(BPTreeNode *root); +static bool check_if_the_leaves_are_all_at_the_same_depth(BPTreeNode *root); +static int compute_first_leaf_depth(BPTreeNode *root); +static bool _check_if_the_leaves_are_all_at_the_same_depth(BPTreeNode *root, int expected_depth, int depth); +static bool check_if_all_internal_nodes_have_no_data(BPTreeNode *root); +static bool check_if_all_leaf_nodes_have_as_much_data_as_keys(BPTreeNode *root); +static bool check_if_the_keys_are_sorted_in_all_nodes(BPTreeNode *root); +static bool check_if_the_array_is_sorted(IntegerArray *array); +static bool check_if_all_nodes_except_the_root_have_not_less_keys_than_the_minimum(BPTreeNode *root, BPTreeNode *parent); +static bool check_if_all_nodes_have_no_more_keys_than_the_maximum(BPTreeNode *root); +static bool check_in_all_internal_nodes_there_is_1_child_more_than_the_number_of_keys(BPTreeNode *root); +static bool check_if_the_keys_are_inserted_in_the_right_place(BPTreeNode *root); +static bool check_if_the_keys_are_inserted_in_the_right_place_with_range(BPTreeNode *root, uint64_t left, uint64_t right); + +static bool check_if_BPTree_is_compliant_with_the_rules(BPTreeNode *root) { + bool is_compliant = true; + is_compliant &= check_if_the_leaves_are_all_at_the_same_depth(root); + is_compliant &= check_if_all_internal_nodes_have_no_data(root); + is_compliant &= check_if_all_leaf_nodes_have_as_much_data_as_keys(root); + is_compliant &= check_if_the_keys_are_sorted_in_all_nodes(root); + is_compliant &= check_if_all_nodes_except_the_root_have_not_less_keys_than_the_minimum(root, NULL); + is_compliant &= check_if_all_nodes_have_no_more_keys_than_the_maximum(root); + is_compliant &= check_in_all_internal_nodes_there_is_1_child_more_than_the_number_of_keys(root); + is_compliant &= check_if_the_keys_are_inserted_in_the_right_place(root); + // TODO : check if linked list is ok + return is_compliant; +} + +static bool check_if_the_leaves_are_all_at_the_same_depth(BPTreeNode *root) { + int expected_depth = compute_first_leaf_depth(root); + return _check_if_the_leaves_are_all_at_the_same_depth(root, expected_depth, 0); +} + +static int compute_first_leaf_depth(BPTreeNode *root) { + if (root->is_leaf) { + return 0; + } + + return compute_first_leaf_depth(root->children->items[0]) + 1; +} + +static bool _check_if_the_leaves_are_all_at_the_same_depth(BPTreeNode *root, int expected_depth, int depth) { + if (root->is_leaf) { + return depth == expected_depth; + } + + for (int i = 0; i < root->children->size; i++) { + if (!_check_if_the_leaves_are_all_at_the_same_depth(root->children->items[i], expected_depth, depth + 1)) { + return false; + } + } + + return true; +} + +static bool check_if_all_internal_nodes_have_no_data(BPTreeNode *root) { + if (root->is_leaf) { + return true; + } + + for (int i = 0; i < root->children->size; i++) { + if (!check_if_all_internal_nodes_have_no_data(root->children->items[i])) { + return false; + } + } + + return root->data->size == 0; +} + +static bool check_if_all_leaf_nodes_have_as_much_data_as_keys(BPTreeNode *root) { + if (root->is_leaf) { + return root->keys->size == root->data->size; + } + + for (int i = 0; i < root->children->size; i++) { + if (!check_if_all_leaf_nodes_have_as_much_data_as_keys(root->children->items[i])) { + return false; + } + } + + return true; +} + +static bool check_if_the_keys_are_sorted_in_all_nodes(BPTreeNode *root) { + for (int i = 0; i < root->children->size; i++) { + if (!check_if_the_keys_are_sorted_in_all_nodes(root->children->items[i])) { + return false; + } + } + + return check_if_the_array_is_sorted(root->keys); +} + +static bool check_if_the_array_is_sorted(IntegerArray *array) { + for (int i = 0; i < array->size - 1; i++) { + if (array->items[i] > array->items[i + 1]) { + return false; + } + } + + return true; +} + +static bool check_if_all_nodes_except_the_root_have_not_less_keys_than_the_minimum(BPTreeNode *root, BPTreeNode *parent) { + for (int i = 0; i < root->children->size; i++) { + if (!check_if_all_nodes_except_the_root_have_not_less_keys_than_the_minimum(root->children->items[i], root)) { + return false; + } + } + + if (parent == NULL) { + return true; + } + + return root->keys->size >= root->order; +} + +static bool check_if_all_nodes_have_no_more_keys_than_the_maximum(BPTreeNode *root) { + for (int i = 0; i < root->children->size; i++) { + if (!check_if_all_nodes_have_no_more_keys_than_the_maximum(root->children->items[i])) { + return false; + } + } + + return root->keys->size <= 2 * root->order; +} + +static bool check_in_all_internal_nodes_there_is_1_child_more_than_the_number_of_keys(BPTreeNode *root) { + if (root->is_leaf) { + return true; + } + + for (int i = 0; i < root->children->size; i++) { + if (!check_in_all_internal_nodes_there_is_1_child_more_than_the_number_of_keys(root->children->items[i])) { + return false; + } + } + + return root->children->size == root->keys->size + 1; +} + +static bool check_if_the_keys_are_inserted_in_the_right_place(BPTreeNode *root) { + if (root->is_leaf) { + return true; + } + + for (int i = 0; i < root->children->size; i++) { + if (i == 0) { + if (!check_if_the_keys_are_inserted_in_the_right_place_with_range(root->children->items[i], 0, root->keys->items[i])) { + return false; + } + } else if (i == root->children->size - 1) { + if (!check_if_the_keys_are_inserted_in_the_right_place_with_range(root->children->items[i], root->keys->items[i - 1], 18446744073709551615LLU)) { + return false; + } + } else { + if (!check_if_the_keys_are_inserted_in_the_right_place_with_range(root->children->items[i], root->keys->items[i - 1], root->keys->items[i])) { + return false; + } + } + } + + for (int i = 0; i < root->children->size; i++) { + if (!check_if_the_keys_are_inserted_in_the_right_place(root->children->items[i])) { + return false; + } + } + + return true; +} + +static bool check_if_the_keys_are_inserted_in_the_right_place_with_range(BPTreeNode *root, uint64_t left, uint64_t right) { + for (int i = 0; i < root->keys->size; i++) { + if (root->keys->items[i] < left || root->keys->items[i] >= right) { + return false; + } + } + + for (int i = 0; i < root->children->size; i++) { + if (!check_if_the_keys_are_inserted_in_the_right_place_with_range(root->children->items[i], left, right)) { + return false; + } + } + + return true; +} + +// END : BPTree Compliance + +/** + * @brief Performs a linear search for an item in an array. + * + * @param array The array in which the search is to be performed. + * @param item The item searched for. + * @return true The item has been found. + * @return false The item was not found. + */ +static bool IntegerArray_search(IntegerArray *array, uint64_t item) { + for (int i = 0; i < array->size; i++) { + if (array->items[i] == item) { + return true; + } + } + + return false; +} + +/** + * @brief Generates an array of random numbers. + * + * @param size The number of numbers to generate. + * @param random_min The smallest random value. + * @param random_max The greatest random value. + * @return IntegerArray* The random number array. + */ +static IntegerArray *generate_random_numbers_array(int size, int random_min, int random_max) { + IntegerArray *array = IntegerArray_init(size); + + for (int i = 0; i < size; i++) { + uint64_t number; + + // While the generated random nomber already exists in the array. + do { + number = random_min + rand() % (random_max - random_min); + } while (IntegerArray_search(array, number)); + + IntegerArray_append(array, number); + } + + return array; +} + +static uint64_t transform_key_to_data(uint64_t key) { + return key * 10000; +} + +// BEGIN : Tests + +// **** BEGIN : test_BPTree_insert + +static void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(BPTreeNode **root, int order) { + int size = 1; + + // Test 10 times, variable i being the seed. + for (int i = 0; i < 10; i++) { + srand(i); + + // Test with seed "i" 8 array size, at each end of loop the size variable is multiplied by 2, the array of random keys + // will have the size of 1 then 2, 4, 8, 16, 32, ... + for (int j = 0; j < 8; j++) { + IntegerArray *keys = generate_random_numbers_array(size, RANDOM_MIN, RANDOM_MAX); + *root = BPTree_init(order); + + for (int i = 0; i < keys->size; i++) { + BPTree_insert(*root, keys->items[i], transform_key_to_data(keys->items[i])); + // After each insertion is verified that the B+ Tree is compliant with the rules. + TEST_ASSERT(check_if_BPTree_is_compliant_with_the_rules(*root)); + } + + IntegerArray_destroy(&keys); + BPTree_destroy(root); + + size *= 2; + } + + size = 1; + } +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_1() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 1); +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_2() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 2); +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_3() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 3); +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_4() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 4); +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_8() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 8); +} + +void test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_16() { + test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 16); +} + +// **** END : test_BPTree_insert + +// **** BEGIN : test_BPTree_delete + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(BPTreeNode **root, int order) { + int size = 1; + + // Test 10 times, variable i being the seed. + for (int i = 0; i < 10; i++) { + srand(i); + + // Test with seed "i" 8 array size, at each end of loop the size variable is multiplied by 2, the array of random keys + // will have the size of 1 then 2, 4, 8, 16, 32, ... + for (int j = 0; j < 8; j++) { + IntegerArray *keys = generate_random_numbers_array(size, RANDOM_MIN, RANDOM_MAX); + *root = BPTree_init(order); + + for (int i = 0; i < keys->size; i++) { + BPTree_insert(*root, keys->items[i], transform_key_to_data(keys->items[i])); + } + + while (keys->size > 0) { + int index = rand() % keys->size; + BPTree_delete(*root, keys->items[index]); + IntegerArray_delete_at_index(keys, index); + } + + IntegerArray_destroy(&keys); + BPTree_destroy(&(*root)); + + size *= 2; + } + + size = 1; + } +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_1() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 1); +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_2() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 2); +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_3() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 3); +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_4() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 4); +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_8() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 8); +} + +void test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_16() { + test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_given_order(&tree, 16); +} + +// **** END : test_BPTree_delete + +// END : Tests + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_1); + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_2); + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_3); + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_4); + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_8); + RUN_TEST(test_BPTree_insert_should_comply_with_BPTree_rules_using_BPTree_of_order_16); + + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_1); + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_2); + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_3); + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_4); + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_8); + RUN_TEST(test_BPTree_delete_should_comply_with_BPTree_rules_using_BPTree_of_order_16); + + return UNITY_END(); +} diff --git a/src/tests/unity.c b/src/tests/Unity/unity.c similarity index 100% rename from src/tests/unity.c rename to src/tests/Unity/unity.c diff --git a/src/tests/unity.h b/src/tests/Unity/unity.h similarity index 100% rename from src/tests/unity.h rename to src/tests/Unity/unity.h diff --git a/src/tests/unity_internals.h b/src/tests/Unity/unity_internals.h similarity index 100% rename from src/tests/unity_internals.h rename to src/tests/Unity/unity_internals.h diff --git a/src/tests/testtest.c b/src/tests/testtest.c deleted file mode 100644 index db9d3a7a4f4f32c116b9ead2a0ecf483012b8e93..0000000000000000000000000000000000000000 --- a/src/tests/testtest.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "../Array.h" -#include "unity.h" - -IntegerArray *array; - -void setUp(void) { - array = IntegerArray_init(4); -} - -void tearDown(void) { - IntegerArray_destroy(&array); -} - -void test_function_should_doAlsoDoBlah(void) { - IntegerArray_append(array, 10); - TEST_ASSERT_EQUAL_INT(1, array->size); - TEST_ASSERT_EQUAL_INT(11, array->items[0]); -} - -int main(void) { - UNITY_BEGIN(); - RUN_TEST(test_function_should_doAlsoDoBlah); - return UNITY_END(); -}