593 lines
17 KiB
C
593 lines
17 KiB
C
#include "unity.h"
|
|
#include "bintree.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ============================================================================
|
|
* 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();
|
|
}
|