#include "unity.h" #include "bintree.h" #include #include /* ============================================================================ * UNIT TESTS FOR BINARY SEARCH TREE IMPLEMENTATION (Binärer Suchbaum) * * This test suite covers all four functions: * - addToTree(): Insert elements maintaining BST ordering * - clearTree(): Free all memory in the tree * - treeSize(): Count nodes in the tree * - nextTreeData(): In-order traversal using stack * ========================================================================== */ // ============================================================================ // HELPER FUNCTIONS FOR TESTING // ============================================================================ // Comparison function for integers (ascending order: smaller values first) // Returns: <0 if a < b, 0 if a == b, >0 if a > b int compareIntegers(const void *a, const void *b) { int valA = *(const int *)a; int valB = *(const int *)b; return valA - valB; } // Comparison function for strings (alphabetical order) int compareStrings(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } // ============================================================================ // TEST SETUP AND TEARDOWN // ============================================================================ void setUp(void) { // Perform any setup needed before each test } void tearDown(void) { // Perform any cleanup needed after each test // Individual tests are responsible for clearing their trees } // ============================================================================ // TEST SUITE 1: addToTree() - Inserting Elements // ============================================================================ // Test 1.1: Insert a single element into an empty tree (NULL root) void test_addToTree_SingleElement(void) { TreeNode *root = NULL; int value = 42; int isDuplicate = 0; // Insert the first element into an empty tree root = addToTree(root, &value, sizeof(int), compareIntegers, &isDuplicate); // Verify the root is not NULL TEST_ASSERT_NOT_NULL(root); // Verify the data was copied correctly TEST_ASSERT_EQUAL_INT(42, *(int *)root->data); // Verify isDuplicate was set to 0 (new entry added) TEST_ASSERT_EQUAL_INT(0, isDuplicate); // Verify the node has no children TEST_ASSERT_NULL(root->left); TEST_ASSERT_NULL(root->right); // Cleanup clearTree(root); } // Test 1.2: Insert multiple elements in ascending order void test_addToTree_AscendingOrder(void) { TreeNode *root = NULL; int values[] = {10, 20, 30}; // Insert values in ascending order for (int i = 0; i < 3; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Verify root value TEST_ASSERT_EQUAL_INT(10, *(int *)root->data); // Verify each value goes to the right (since they're ascending) TEST_ASSERT_NULL(root->left); TEST_ASSERT_NOT_NULL(root->right); TEST_ASSERT_EQUAL_INT(20, *(int *)root->right->data); TEST_ASSERT_NULL(root->right->left); TEST_ASSERT_NOT_NULL(root->right->right); TEST_ASSERT_EQUAL_INT(30, *(int *)root->right->right->data); // Cleanup clearTree(root); } // Test 1.3: Insert multiple elements in descending order void test_addToTree_DescendingOrder(void) { TreeNode *root = NULL; int values[] = {30, 20, 10}; // Insert values in descending order for (int i = 0; i < 3; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Verify root value TEST_ASSERT_EQUAL_INT(30, *(int *)root->data); // Verify each value goes to the left (since they're descending) TEST_ASSERT_NULL(root->right); TEST_ASSERT_NOT_NULL(root->left); TEST_ASSERT_EQUAL_INT(20, *(int *)root->left->data); TEST_ASSERT_NULL(root->left->right); TEST_ASSERT_NOT_NULL(root->left->left); TEST_ASSERT_EQUAL_INT(10, *(int *)root->left->left->data); // Cleanup clearTree(root); } // Test 1.4: Insert elements in balanced order (creating a balanced tree) void test_addToTree_BalancedTree(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 5, 15, 25, 35}; // Insert values to create a somewhat balanced tree for (int i = 0; i < 7; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Verify root value TEST_ASSERT_EQUAL_INT(20, *(int *)root->data); // Verify left subtree TEST_ASSERT_EQUAL_INT(10, *(int *)root->left->data); TEST_ASSERT_EQUAL_INT(5, *(int *)root->left->left->data); TEST_ASSERT_EQUAL_INT(15, *(int *)root->left->right->data); // Verify right subtree TEST_ASSERT_EQUAL_INT(30, *(int *)root->right->data); TEST_ASSERT_EQUAL_INT(25, *(int *)root->right->left->data); TEST_ASSERT_EQUAL_INT(35, *(int *)root->right->right->data); // Cleanup clearTree(root); } // Test 1.5: Attempt to insert a duplicate when isDuplicate is NULL (allows duplicates) void test_addToTree_AllowDuplicates(void) { TreeNode *root = NULL; int value1 = 42; int value2 = 42; // Insert first instance root = addToTree(root, &value1, sizeof(int), compareIntegers, NULL); // Insert duplicate (isDuplicate is NULL, so duplicates are allowed) root = addToTree(root, &value2, sizeof(int), compareIntegers, NULL); // Verify tree has both entries (duplicate goes right) TEST_ASSERT_EQUAL_INT(42, *(int *)root->data); TEST_ASSERT_NULL(root->left); TEST_ASSERT_NOT_NULL(root->right); TEST_ASSERT_EQUAL_INT(42, *(int *)root->right->data); // Cleanup clearTree(root); } // Test 1.6: Attempt to insert a duplicate when isDuplicate is provided (rejects duplicates) void test_addToTree_RejectDuplicates(void) { TreeNode *root = NULL; int value1 = 42; int value2 = 42; int isDuplicate = 0; // Insert first instance root = addToTree(root, &value1, sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(0, isDuplicate); // Attempt to insert duplicate root = addToTree(root, &value2, sizeof(int), compareIntegers, &isDuplicate); // Verify isDuplicate was set to 1 TEST_ASSERT_EQUAL_INT(1, isDuplicate); // Verify tree still has only one node with the first value TEST_ASSERT_EQUAL_INT(42, *(int *)root->data); TEST_ASSERT_NULL(root->left); TEST_ASSERT_NULL(root->right); // Cleanup clearTree(root); } // Test 1.7: Insert multiple elements with duplicate detection void test_addToTree_MultipleWithDuplicateDetection(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 10}; // 10 is a duplicate int isDuplicate = 0; // Insert 20 root = addToTree(root, &values[0], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(0, isDuplicate); // Insert 10 (new) root = addToTree(root, &values[1], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(0, isDuplicate); // Insert 30 (new) root = addToTree(root, &values[2], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(0, isDuplicate); // Insert 10 (duplicate) root = addToTree(root, &values[3], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(1, isDuplicate); // Verify tree structure hasn't changed (3 nodes, not 4) TEST_ASSERT_EQUAL_INT(3, treeSize(root)); // Cleanup clearTree(root); } // Test 1.8: Insert strings into a tree void test_addToTree_WithStrings(void) { TreeNode *root = NULL; const char *words[] = {"dog", "cat", "elephant", "bear"}; // Insert strings for (int i = 0; i < 4; i++) { // Note: We pass pointer to the string pointer root = addToTree(root, &words[i], sizeof(char *), compareStrings, NULL); } // Verify root value (first insertion) TEST_ASSERT_EQUAL_STRING("dog", *(char **)root->data); // Verify tree has 4 nodes TEST_ASSERT_EQUAL_INT(4, treeSize(root)); // Cleanup clearTree(root); } // ============================================================================ // TEST SUITE 2: treeSize() - Counting Nodes // ============================================================================ // Test 2.1: Size of empty tree (NULL root) void test_treeSize_EmptyTree(void) { TreeNode *root = NULL; unsigned int size = treeSize(root); // Verify empty tree has size 0 TEST_ASSERT_EQUAL_INT(0, size); } // Test 2.2: Size of single-node tree void test_treeSize_SingleNode(void) { TreeNode *root = NULL; int value = 42; root = addToTree(root, &value, sizeof(int), compareIntegers, NULL); unsigned int size = treeSize(root); // Verify single-node tree has size 1 TEST_ASSERT_EQUAL_INT(1, size); clearTree(root); } // Test 2.2: Size of multi-node tree void test_treeSize_MultipleNodes(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 5, 15, 25, 35}; // Insert all values for (int i = 0; i < 7; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } unsigned int size = treeSize(root); // Verify tree has 7 nodes TEST_ASSERT_EQUAL_INT(7, size); clearTree(root); } // Test 2.3: Size after multiple insertions void test_treeSize_IncrementalGrowth(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 5, 15}; for (int i = 0; i < 5; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); // Check size increases with each insertion unsigned int expectedSize = i + 1; TEST_ASSERT_EQUAL_INT(expectedSize, treeSize(root)); } clearTree(root); } // ============================================================================ // TEST SUITE 3: clearTree() - Freeing Memory // ============================================================================ // Test 3.1: Clear empty tree (NULL root) void test_clearTree_EmptyTree(void) { TreeNode *root = NULL; // Should not crash when clearing NULL clearTree(root); // Verify still NULL after clearing TEST_ASSERT_NULL(root); } // Test 3.2: Clear single-node tree void test_clearTree_SingleNode(void) { TreeNode *root = NULL; int value = 42; root = addToTree(root, &value, sizeof(int), compareIntegers, NULL); // Clear the tree clearTree(root); root = NULL; // Reset pointer // Verify root is now NULL TEST_ASSERT_NULL(root); } // Test 3.3: Clear multi-node tree void test_clearTree_MultipleNodes(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 5, 15, 25, 35}; for (int i = 0; i < 7; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Clear the entire tree clearTree(root); root = NULL; // Verify root is now NULL TEST_ASSERT_NULL(root); // If we got here without segfault, the memory was properly freed } // ============================================================================ // TEST SUITE 4: nextTreeData() - In-Order Traversal // ============================================================================ // Test 4.1: In-order traversal of simple tree (ascending linear) void test_nextTreeData_AscendingLinearTree(void) { TreeNode *root = NULL; int values[] = {1, 2, 3}; for (int i = 0; i < 3; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Traverse and collect results int result1 = *(int *)nextTreeData(root); int result2 = *(int *)nextTreeData(NULL); int result3 = *(int *)nextTreeData(NULL); void *result4 = nextTreeData(NULL); // Verify in-order traversal produces ascending order TEST_ASSERT_EQUAL_INT(1, result1); TEST_ASSERT_EQUAL_INT(2, result2); TEST_ASSERT_EQUAL_INT(3, result3); TEST_ASSERT_NULL(result4); // No more elements clearTree(root); } // Test 4.2: In-order traversal of balanced tree void test_nextTreeData_BalancedTree(void) { TreeNode *root = NULL; int values[] = {20, 10, 30, 5, 15, 25, 35}; for (int i = 0; i < 7; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Traverse and collect results int results[7]; results[0] = *(int *)nextTreeData(root); for (int i = 1; i < 7; i++) { results[i] = *(int *)nextTreeData(NULL); } // Verify in-order traversal produces ascending order int expectedOrder[] = {5, 10, 15, 20, 25, 30, 35}; for (int i = 0; i < 7; i++) { TEST_ASSERT_EQUAL_INT(expectedOrder[i], results[i]); } // Verify traversal is complete void *result = nextTreeData(NULL); TEST_ASSERT_NULL(result); clearTree(root); } // Test 4.3: In-order traversal of descending linear tree void test_nextTreeData_DescendingLinearTree(void) { TreeNode *root = NULL; int values[] = {30, 20, 10}; for (int i = 0; i < 3; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, NULL); } // Traverse and collect results int result1 = *(int *)nextTreeData(root); int result2 = *(int *)nextTreeData(NULL); int result3 = *(int *)nextTreeData(NULL); void *result4 = nextTreeData(NULL); // Verify in-order traversal produces ascending order (despite insertion order) TEST_ASSERT_EQUAL_INT(10, result1); TEST_ASSERT_EQUAL_INT(20, result2); TEST_ASSERT_EQUAL_INT(30, result3); TEST_ASSERT_NULL(result4); clearTree(root); } // Test 4.4: Traversal of single-node tree void test_nextTreeData_SingleNode(void) { TreeNode *root = NULL; int value = 42; root = addToTree(root, &value, sizeof(int), compareIntegers, NULL); int result1 = *(int *)nextTreeData(root); void *result2 = nextTreeData(NULL); TEST_ASSERT_EQUAL_INT(42, result1); TEST_ASSERT_NULL(result2); clearTree(root); } // Test 4.5: Traversal of empty tree void test_nextTreeData_EmptyTree(void) { TreeNode *root = NULL; void *result = nextTreeData(root); TEST_ASSERT_NULL(result); } // ============================================================================ // INTEGRATION TEST: Complete BST Workflow // ============================================================================ // Test 5.1: Complete workflow - insert, size, traverse, clear void test_integration_CompleteWorkflow(void) { TreeNode *root = NULL; int values[] = {50, 25, 75, 12, 37, 62, 87}; int isDuplicate = 0; // Insert all values for (int i = 0; i < 7; i++) { root = addToTree(root, &values[i], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(0, isDuplicate); } // Verify size TEST_ASSERT_EQUAL_INT(7, treeSize(root)); // Verify in-order traversal int expectedOrder[] = {12, 25, 37, 50, 62, 75, 87}; int results[7]; results[0] = *(int *)nextTreeData(root); for (int i = 1; i < 7; i++) { results[i] = *(int *)nextTreeData(NULL); } for (int i = 0; i < 7; i++) { TEST_ASSERT_EQUAL_INT(expectedOrder[i], results[i]); } // Verify duplicate detection root = addToTree(root, &values[0], sizeof(int), compareIntegers, &isDuplicate); TEST_ASSERT_EQUAL_INT(1, isDuplicate); TEST_ASSERT_EQUAL_INT(7, treeSize(root)); // Size should not increase // Clear and verify clearTree(root); root = NULL; TEST_ASSERT_NULL(root); } // ============================================================================ // TEST RUNNER // ============================================================================ int main(void) { UNITY_BEGIN(); // Test Suite 1: addToTree() RUN_TEST(test_addToTree_SingleElement); RUN_TEST(test_addToTree_AscendingOrder); RUN_TEST(test_addToTree_DescendingOrder); RUN_TEST(test_addToTree_BalancedTree); RUN_TEST(test_addToTree_AllowDuplicates); RUN_TEST(test_addToTree_RejectDuplicates); RUN_TEST(test_addToTree_MultipleWithDuplicateDetection); RUN_TEST(test_addToTree_WithStrings); // Test Suite 2: treeSize() RUN_TEST(test_treeSize_EmptyTree); RUN_TEST(test_treeSize_SingleNode); RUN_TEST(test_treeSize_MultipleNodes); RUN_TEST(test_treeSize_IncrementalGrowth); // Test Suite 3: clearTree() RUN_TEST(test_clearTree_EmptyTree); RUN_TEST(test_clearTree_SingleNode); RUN_TEST(test_clearTree_MultipleNodes); // Test Suite 4: nextTreeData() RUN_TEST(test_nextTreeData_AscendingLinearTree); RUN_TEST(test_nextTreeData_BalancedTree); RUN_TEST(test_nextTreeData_DescendingLinearTree); RUN_TEST(test_nextTreeData_SingleNode); RUN_TEST(test_nextTreeData_EmptyTree); // Integration Test RUN_TEST(test_integration_CompleteWorkflow); return UNITY_END(); }