diff --git a/bintree.c b/bintree.c index 5cf82a9..ecf5a5f 100644 --- a/bintree.c +++ b/bintree.c @@ -2,35 +2,183 @@ #include "stack.h" #include "bintree.h" -//TODO: binären Suchbaum implementieren -/* * `addToTree`: fügt ein neues Element in den Baum ein (rekursiv), - * `clearTree`: gibt den gesamten Baum frei (rekursiv), - * `treeSize`: zählt die Knoten im Baum (rekursiv), - * `nextTreeData`: Traversierung mit Hilfe des zuvor implementierten Stacks. */ +// Global static stack used for in-order tree traversal in nextTreeData() +// This is needed because nextTreeData() follows strtok-like usage where consecutive calls +// with NULL argument continue the iteration +static StackNode *traversalStack = NULL; -// Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates -// if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added). +// Adds a copy of data's pointer destination to the tree using compareFct for ordering. +// Accepts duplicates if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added). +// Parameters: +// - root: pointer to the root of the tree (can be NULL for first insertion) +// - data: pointer to the data to be inserted +// - dataSize: size in bytes of the data to allocate and copy +// - compareFct: comparison function that returns <0 if arg10 if arg1>arg2 +// - isDuplicate: pointer to int flag (NULL to allow duplicates, non-NULL to detect and reject duplicates) +// Returns: pointer to the root of the modified tree TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFctType compareFct, int *isDuplicate) { - + // Base case: if root is NULL, we've found the insertion point + // Create a new tree node here + if (root == NULL) + { + // Allocate memory for the new tree node + TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode)); + if (newNode == NULL) + { + return NULL; // Allocation failed + } + + // Allocate memory for a copy of the data + newNode->data = malloc(dataSize); + if (newNode->data == NULL) + { + free(newNode); // Free the node if data allocation failed + return NULL; + } + + // Copy the data into the newly allocated space + memcpy(newNode->data, data, dataSize); + + // Initialize the left and right child pointers to NULL + newNode->left = NULL; + newNode->right = NULL; + + // If isDuplicate is not NULL, set it to 0 to indicate a new entry was added + if (isDuplicate != NULL) + { + *isDuplicate = 0; + } + + // Return the newly created node + return newNode; + } + + // Recursive case: compare the data with the root's data to determine where to insert + int comparisonResult = compareFct(data, root->data); + + // If data is less than root->data, insert into the left subtree + if (comparisonResult < 0) + { + root->left = addToTree(root->left, data, dataSize, compareFct, isDuplicate); + } + // If data is greater than root->data, insert into the right subtree + else if (comparisonResult > 0) + { + root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate); + } + // If data equals root->data, we have a duplicate + else + { + // If isDuplicate is not NULL, set it to 1 to indicate a duplicate was found + if (isDuplicate != NULL) + { + *isDuplicate = 1; + } + // If isDuplicate is NULL, we allow the duplicate to be inserted again (in either subtree) + // For standard BST behavior, we typically insert duplicates in the right subtree + else + { + root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate); + } + } + + // Return the root (possibly with new subtrees added) + return root; } -// Iterates over the tree given by root. Follows the usage of strtok. If tree is NULL, the next entry of the last tree given is returned in ordering direction. -// Use your implementation of a stack to organize the iterator. Push the root node and all left nodes first. On returning the next element, -// push the top node and push all its left nodes. +// Iterates over the tree given by root in in-order traversal (Left, Root, Right). +// Follows the usage pattern of strtok: first call with root pointer, subsequent calls with NULL to get next element. +// Uses a static stack to maintain traversal state between calls. +// Parameters: +// - root: pointer to tree root for first call, NULL for subsequent calls +// Returns: pointer to the data of the next node in in-order sequence, or NULL when traversal is complete void *nextTreeData(TreeNode *root) { - + // If root is not NULL, we're starting a new traversal + if (root != NULL) + { + // Clear any existing traversal state + clearStack(traversalStack); + traversalStack = NULL; + + // Initialize the traversal by pushing root and all its left descendants + // In in-order traversal, we always visit left children first + TreeNode *current = root; + while (current != NULL) + { + // Push current node onto the stack + traversalStack = push(traversalStack, current); + // Move to the left child + current = current->left; + } + } + + // If the traversal stack is empty, we've visited all nodes + if (traversalStack == NULL) + { + return NULL; + } + + // Pop the next node from the stack + TreeNode *nextNode = (TreeNode *)top(traversalStack); + traversalStack = pop(traversalStack); + + // If the popped node has a right child, push it and all its left descendants + // This handles the "Root, Right" part of in-order traversal + if (nextNode->right != NULL) + { + TreeNode *current = nextNode->right; + while (current != NULL) + { + // Push the current node + traversalStack = push(traversalStack, current); + // Move to the left child of the right subtree + current = current->left; + } + } + + // Return the data of the node we just processed + return nextNode->data; } -// Releases all memory resources (including data copies). +// Releases all memory resources used by the tree. +// Recursively frees all nodes and their data copies. +// Parameters: +// - root: pointer to the root of the tree to clear +// Returns: void void clearTree(TreeNode *root) { - + // Base case: if root is NULL, there's nothing to free + if (root == NULL) + { + return; + } + + // Recursive case: first recursively clear the left subtree + clearTree(root->left); + + // Then recursively clear the right subtree + clearTree(root->right); + + // Finally, free the data and the node itself + free(root->data); + free(root); } -// Returns the number of entries in the tree given by root. +// Returns the number of entries (nodes) in the tree. +// Recursively counts nodes in the left and right subtrees. +// Parameters: +// - root: pointer to the root of the tree +// Returns: unsigned int representing the total number of nodes in the tree unsigned int treeSize(const TreeNode *root) { - + // Base case: if root is NULL, there are no nodes + if (root == NULL) + { + return 0; + } + + // Recursive case: count 1 for the current node, plus all nodes in left and right subtrees + return 1 + treeSize(root->left) + treeSize(root->right); } \ No newline at end of file diff --git a/bintree.o b/bintree.o new file mode 100644 index 0000000..a1a30ce Binary files /dev/null and b/bintree.o differ diff --git a/bintree_tests b/bintree_tests new file mode 100755 index 0000000..a8e151a Binary files /dev/null and b/bintree_tests differ diff --git a/doble_initial b/doble_initial new file mode 100755 index 0000000..d60f79b Binary files /dev/null and b/doble_initial differ diff --git a/highscores.txt b/highscores.txt index 4edd5a7..a6a0d3e 100644 --- a/highscores.txt +++ b/highscores.txt @@ -1 +1,2 @@ +player_name;6999 player1;3999 diff --git a/makefile b/makefile index 97c5e30..7a7d9d2 100644 --- a/makefile +++ b/makefile @@ -38,12 +38,27 @@ $(program_obj_files): %.o: %.c unitTests: echo "needs to be implemented" +# -------------------------- +# Test: test_numbers +# -------------------------- +test_numbers: stack.o bintree.o numbers.o unity.o + $(CC) $(FLAGS) -Iunity test_numbers.c $^ -o runTest_numbers + +unity.o: + $(CC) -c $(FLAGS) -Iunity unity/unity.c -o unity.o + +# -------------------------- +# Test: test_bintree +# -------------------------- +test_bintree: stack.o bintree.o unity.o + $(CC) $(FLAGS) -Iunity test_bintree.c $^ -o runTest_bintree + # -------------------------- # Clean # -------------------------- clean: ifeq ($(OS),Windows_NT) - del /f *.o doble + del /f *.o doble runTest_numbers runTest_bintree else - rm -f *.o doble + rm -f *.o doble runTest_numbers runTest_bintree endif \ No newline at end of file diff --git a/numbers.c b/numbers.c index f59d9a2..6fcd80c 100644 --- a/numbers.c +++ b/numbers.c @@ -11,16 +11,151 @@ * Duplizieren eines zufälligen Eintrags im Array. * in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */ +// Comparison function for unsigned integers used in the binary search tree +// Returns: <0 if a < b, 0 if a == b, >0 if a > b +// This function is passed to addToTree to determine ordering +int compareUnsignedInts(const void *a, const void *b) +{ + unsigned int valA = *(const unsigned int *)a; + unsigned int valB = *(const unsigned int *)b; + + // Simple subtraction works for unsigned integers in this case + // (Note: in general subtraction can overflow, but our range is small) + if (valA < valB) return -1; + if (valA > valB) return 1; + return 0; +} + // Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries. // Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while // creating random numbers. +// +// Algorithm: +// 1. Generate len-1 unique random numbers using the BST for duplicate detection +// 2. Randomly duplicate one of those numbers by inserting it at a random position +// 3. Final array has len entries with exactly one value appearing twice unsigned int *createNumbers(unsigned int len) { + // Step 1: Validate input - we need at least 2 elements to create a duplicate + if (len < 2) + { + return NULL; + } + + // Step 2: Allocate memory for the final array (len elements total) + unsigned int *numbers = (unsigned int *)malloc(len * sizeof(unsigned int)); + if (numbers == NULL) + { + return NULL; + } + + // Step 3: Create binary search tree to track generated numbers and prevent accidental duplicates + // Note: Random seed should be initialized once in main() with srand(time(NULL)) + // Calling srand() here would reset the seed each time, reducing randomness + TreeNode *usedNumbers = NULL; + + // Step 4: Generate len-1 unique random numbers (we'll add one duplicate later) + unsigned int arrayIndex = 0; + + while (arrayIndex < len - 1) + { + // Generate random number in range [1, 2×len] + unsigned int randomNumber = (rand() % (2 * len)) + 1; + + // Check if this number was already generated + int isDuplicate = 0; + usedNumbers = addToTree(usedNumbers, &randomNumber, sizeof(unsigned int), compareUnsignedInts, &isDuplicate); + + if (usedNumbers == NULL) + { + free(numbers); + return NULL; + } + + // If new number, add to result array; if duplicate, retry + if (isDuplicate == 0) + { + numbers[arrayIndex] = randomNumber; + arrayIndex++; + } + } + + // Step 5: Pick a random value from our generated numbers to duplicate + unsigned int indexToDuplicate = rand() % (len - 1); + unsigned int valueToDuplicate = numbers[indexToDuplicate]; + + // Step 6: Pick a random position to insert the duplicate (anywhere in the array) + unsigned int insertPos = rand() % len; + + // Step 7: Shift elements right to make room for the duplicate + for (unsigned int i = len - 1; i > insertPos; i--) + { + numbers[i] = numbers[i - 1]; + } + + // Step 8: Insert the duplicate at the chosen position + numbers[insertPos] = valueToDuplicate; + + // Step 9: Clean up the binary search tree (we don't need it anymore) + clearTree(usedNumbers); + + // Return the array with len elements (len-1 unique + 1 duplicate) + return numbers; +} +// Comparison function for qsort to sort unsigned integers in ascending order +// Returns: <0 if a < b, 0 if a == b, >0 if a > b +static int compareUnsigned(const void *a, const void *b) +{ + unsigned int valA = *(const unsigned int *)a; + unsigned int valB = *(const unsigned int *)b; + + if (valA < valB) return -1; + if (valA > valB) return 1; + return 0; } // Returns only the only number in numbers which is present twice. Returns zero on errors. -unsigned int getDuplicate(const unsigned int numbers[], unsigned int len) +// +// Algorithm: +// 1. Create a copy of the array to avoid modifying the input +// 2. Sort the copy using qsort() +// 3. Compare adjacent elements to find the duplicate +// 4. Return the duplicate value, or 0 on error +unsigned int getDuplicate(const unsigned int *numbers, unsigned int len) { - + // Step 1: Input validation - need at least 2 elements to have a duplicate + if (numbers == NULL || len < 2) + { + return 0; + } + + // Step 2: Create a copy of the array to sort (we don't modify the input) + unsigned int *sorted = (unsigned int *)malloc(len * sizeof(unsigned int)); + if (sorted == NULL) + { + return 0; + } + + // Step 3: Copy the array + memcpy(sorted, numbers, len * sizeof(unsigned int)); + + // Step 4: Sort the copy + qsort(sorted, len, sizeof(unsigned int), compareUnsigned); + + // Step 5: Compare adjacent elements to find the duplicate + for (unsigned int i = 0; i < len - 1; i++) + { + if (sorted[i] == sorted[i + 1]) + { + // Found the duplicate! + unsigned int result = sorted[i]; + free(sorted); + return result; + } + } + + // Step 6: No duplicate found (should not happen if input is valid) + free(sorted); + return 0; } \ No newline at end of file diff --git a/numbers.o b/numbers.o new file mode 100644 index 0000000..b6079d0 Binary files /dev/null and b/numbers.o differ diff --git a/runTest_bintree b/runTest_bintree new file mode 100755 index 0000000..32091ce Binary files /dev/null and b/runTest_bintree differ diff --git a/runTest_numbers b/runTest_numbers new file mode 100755 index 0000000..545ec6c Binary files /dev/null and b/runTest_numbers differ diff --git a/stack.c b/stack.c index e3a90d4..be93edb 100644 --- a/stack.c +++ b/stack.c @@ -1,33 +1,99 @@ #include #include "stack.h" -//TODO: grundlegende Stackfunktionen implementieren: -/* * `push`: legt ein Element oben auf den Stack, - * `pop`: entfernt das oberste Element, - * `top`: liefert das oberste Element zurück, - * `clearStack`: gibt den gesamten Speicher frei. */ - // Pushes data as pointer onto the stack. +// Parameters: +// - stack: pointer to the current top of the stack (can be NULL if stack is empty) +// - data: void pointer to the data to be pushed onto the stack +// Returns: pointer to the new top of the stack (the newly created node) +// Note: The function allocates memory for a new StackNode, sets its data pointer +// and links it to the previous top, making it the new top of the stack. StackNode *push(StackNode *stack, void *data) { - + // Allocate memory for a new StackNode + StackNode *newNode = (StackNode *)malloc(sizeof(StackNode)); + + // Check if memory allocation was successful + if (newNode == NULL) + { + return NULL; // Return NULL if allocation failed + } + + // Store the data pointer in the new node + newNode->data = data; + + // Link the new node to the previous top of the stack + // If stack was NULL (empty), newNode->next will be NULL + // If stack was not NULL, newNode->next points to the old top + newNode->next = stack; + + // Return the new node, which is now the new top of the stack + return newNode; } -// Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be -// freed by caller.) +// Deletes the top element of the stack (latest added element) and releases its memory. +// Parameters: +// - stack: pointer to the current top of the stack +// Returns: pointer to the new top of the stack (the second element, or NULL if stack becomes empty) +// Note: This function only frees the StackNode structure itself. The caller is responsible +// for freeing the data that the node was pointing to, if that data was dynamically allocated. StackNode *pop(StackNode *stack) { - + // Check if the stack is empty + if (stack == NULL) + { + return NULL; // Cannot pop from an empty stack + } + + // Save a pointer to the next node (which will become the new top) + StackNode *newTop = stack->next; + + // Free the memory of the current top node + // NOTE: We do NOT free stack->data here because the caller may still need it + // or may be responsible for freeing it themselves + free(stack); + + // Return the new top of the stack (or NULL if the stack is now empty) + return newTop; } -// Returns the data of the top element. +// Returns the data of the top element without removing it from the stack. +// Parameters: +// - stack: pointer to the current top of the stack +// Returns: void pointer to the data of the top element, or NULL if stack is empty +// Note: This is a "peek" operation - it looks at the top without removing it. void *top(StackNode *stack) { - + // Check if the stack is empty + if (stack == NULL) + { + return NULL; // Return NULL if stack is empty + } + + // Return the data pointer stored in the top node + // The caller can then use this pointer to access the actual data + return stack->data; } -// Clears stack and releases all memory. +// Clears the entire stack and releases all memory used by the StackNodes. +// Parameters: +// - stack: pointer to the current top of the stack +// Returns: void +// Note: This function only frees the StackNode structures. The caller is responsible +// for freeing any dynamically allocated data that the nodes were pointing to, +// unless that is done elsewhere in the program before calling clearStack. void clearStack(StackNode *stack) { - + // Base case: if the stack is NULL (empty), we're done + if (stack == NULL) + { + return; + } + + // Recursive case: first, recursively clear all the nodes below this one + // This processes the stack from top to bottom + clearStack(stack->next); + + // After recursively clearing all deeper nodes, free the current node + free(stack); } \ No newline at end of file diff --git a/stack.h b/stack.h index f7d542d..77993da 100644 --- a/stack.h +++ b/stack.h @@ -7,7 +7,12 @@ The latest element is taken from the stack. */ #include -//TODO: passenden Datentyp als struct anlegen +// Stack node structure: each node contains data and a pointer to the next node +typedef struct stackNode +{ + void *data; + struct stackNode *next; +} StackNode; // Pushes data as pointer onto the stack. StackNode *push(StackNode *stack, void *data); diff --git a/stack.o b/stack.o new file mode 100644 index 0000000..5676a8c Binary files /dev/null and b/stack.o differ diff --git a/test_bintree.c b/test_bintree.c new file mode 100644 index 0000000..a889753 --- /dev/null +++ b/test_bintree.c @@ -0,0 +1,592 @@ +#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(); +} diff --git a/test_debug b/test_debug new file mode 100755 index 0000000..62ba641 Binary files /dev/null and b/test_debug differ diff --git a/test_debug.c b/test_debug.c new file mode 100644 index 0000000..ae1f2a9 --- /dev/null +++ b/test_debug.c @@ -0,0 +1,61 @@ +#include +#include +#include "numbers.h" + +int main(void) +{ + printf("=== Testing createNumbers() ===\n\n"); + + for (int run = 0; run < 3; run++) + { + printf("RUN %d:\n", run + 1); + unsigned int *numbers = createNumbers(5); + + if (numbers == NULL) + { + printf("ERROR: createNumbers returned NULL\n\n"); + continue; + } + + printf("Array contents: "); + for (int i = 0; i < 5; i++) + { + printf("%u ", numbers[i]); + } + printf("\n"); + + // Count occurrences of each value + printf("Value counts:\n"); + for (int i = 0; i < 5; i++) + { + int count = 0; + for (int j = 0; j < 5; j++) + { + if (numbers[i] == numbers[j]) + count++; + } + printf(" Value %u appears %d times\n", numbers[i], count); + } + + // Check for duplicates + int duplicateCount = 0; + for (int i = 0; i < 5; i++) + { + int count = 0; + for (int j = 0; j < 5; j++) + { + if (numbers[i] == numbers[j]) + count++; + } + if (count == 2 && (i == 0 || numbers[i] != numbers[i-1])) + { + duplicateCount++; + } + } + printf("Duplicate values found: %d\n\n", duplicateCount); + + free(numbers); + } + + return 0; +} diff --git a/test_numbers b/test_numbers new file mode 100755 index 0000000..545ec6c Binary files /dev/null and b/test_numbers differ diff --git a/test_numbers.c b/test_numbers.c new file mode 100644 index 0000000..ab69573 --- /dev/null +++ b/test_numbers.c @@ -0,0 +1,599 @@ +#include "unity.h" +#include "numbers.h" +#include +#include +#include + +/* ============================================================================ + * UNIT TESTS FOR SORTING AND DUPLICATE DETECTION (Sortierung) + * + * This test suite covers: + * - createNumbers(): Generate random numbers with exactly one duplicate + * - getDuplicate(): Find the duplicate number using sorting + * ========================================================================== */ + +// ============================================================================ +// HELPER FUNCTIONS FOR TESTING +// ============================================================================ + +// Count how many times a specific value appears in an array +unsigned int countOccurrences(const unsigned int *numbers, unsigned int len, unsigned int value) +{ + unsigned int count = 0; + for (unsigned int i = 0; i < len; i++) + { + if (numbers[i] == value) + { + count++; + } + } + return count; +} + +// Check if a value is in the valid range [1, 2*len] +int isInValidRange(unsigned int value, unsigned int len) +{ + return (value >= 1) && (value <= 2 * len); +} + +// Count total number of distinct values in the array +unsigned int countDistinctValues(const unsigned int *numbers, unsigned int len) +{ + unsigned int distinct = 0; + for (unsigned int i = 0; i < len; i++) + { + int isNew = 1; + for (unsigned int j = 0; j < i; j++) + { + if (numbers[i] == numbers[j]) + { + isNew = 0; + break; + } + } + if (isNew) + { + distinct++; + } + } + return distinct; +} + +// Find the value that appears exactly twice +unsigned int findDuplicate(const unsigned int *numbers, unsigned int len) +{ + for (unsigned int i = 0; i < len; i++) + { + unsigned int count = countOccurrences(numbers, len, numbers[i]); + if (count == 2) + { + return numbers[i]; + } + } + return 0; // No duplicate found +} + +// ============================================================================ +// TEST SETUP AND TEARDOWN +// ============================================================================ + +void setUp(void) +{ + // Setup before each test +} + +void tearDown(void) +{ + // Cleanup after each test +} + +// ============================================================================ +// TEST SUITE 1: createNumbers() - Input Validation and Error Handling +// ============================================================================ + +// Test 1.1: Invalid input - len = 0 should return NULL +void test_createNumbers_InvalidLenZero(void) +{ + unsigned int *numbers = createNumbers(0); + + TEST_ASSERT_NULL(numbers); +} + +// Test 1.2: Invalid input - len = 1 should return NULL (need at least 2 for duplicate) +void test_createNumbers_InvalidLenOne(void) +{ + unsigned int *numbers = createNumbers(1); + + TEST_ASSERT_NULL(numbers); +} + +// ============================================================================ +// TEST SUITE 2: createNumbers() - Minimum Valid Input +// ============================================================================ + +// Test 2.1: Minimum valid input - len = 2 +void test_createNumbers_MinimumSize(void) +{ + unsigned int *numbers = createNumbers(2); + + // Should not be NULL + TEST_ASSERT_NOT_NULL(numbers); + + // One value should equal the other (the duplicate) + TEST_ASSERT_EQUAL_INT(numbers[0], numbers[1]); + + free(numbers); +} + +// ============================================================================ +// TEST SUITE 3: createNumbers() - Array Size and Contents +// ============================================================================ + +// Test 3.1: Check array size for len=5 +void test_createNumbers_ArraySize5(void) +{ + unsigned int *numbers = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers); + + // Access all 5 elements to verify they exist + for (unsigned int i = 0; i < 5; i++) + { + // Just verify we can read without segfault + (void)numbers[i]; + } + + free(numbers); +} + +// Test 3.2: Check array size for len=10 +void test_createNumbers_ArraySize10(void) +{ + unsigned int *numbers = createNumbers(10); + + TEST_ASSERT_NOT_NULL(numbers); + + // Access all 10 elements + for (unsigned int i = 0; i < 10; i++) + { + // Just verify we can read without segfault + (void)numbers[i]; + } + + free(numbers); +} + +// ============================================================================ +// TEST SUITE 4: createNumbers() - Value Range Validation +// ============================================================================ + +// Test 4.1: All values are positive (basic sanity check) +void test_createNumbers_ValueRangeLen5(void) +{ + unsigned int *numbers = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers); + + // Check each value is positive and not too large + for (unsigned int i = 0; i < 5; i++) + { + TEST_ASSERT_TRUE(numbers[i] > 0); + TEST_ASSERT_TRUE(numbers[i] <= 10); + } + + free(numbers); +} + +// Test 4.2: All values are positive (for len=20) +void test_createNumbers_ValueRangeLen20(void) +{ + unsigned int *numbers = createNumbers(20); + + TEST_ASSERT_NOT_NULL(numbers); + + // Check each value is positive and reasonable + for (unsigned int i = 0; i < 20; i++) + { + TEST_ASSERT_TRUE(numbers[i] > 0); + TEST_ASSERT_TRUE(numbers[i] <= 40); + } + + free(numbers); +} + +// ============================================================================ +// TEST SUITE 5: createNumbers() - Duplicate Detection +// ============================================================================ + +// Test 5.1: Exactly one value appears twice (len=5) +void test_createNumbers_ExactlyOneDuplicateLen5(void) +{ + unsigned int *numbers = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers); + + // Count how many distinct values appear exactly twice + unsigned int valuesAppearingTwice = 0; + unsigned int counted[5] = {0}; // Track which values we've counted + + for (unsigned int i = 0; i < 5; i++) + { + unsigned int value = numbers[i]; + unsigned int count = countOccurrences(numbers, 5, value); + + // If this value appears twice and we haven't counted it yet, count it now + if (count == 2) + { + int alreadyCounted = 0; + for (unsigned int j = 0; j < 5; j++) + { + if (counted[j] == value) + { + alreadyCounted = 1; + break; + } + } + + if (!alreadyCounted) + { + valuesAppearingTwice++; + counted[i] = value; // Mark this value as counted + } + } + } + + // Should have exactly one value that appears twice + TEST_ASSERT_EQUAL_INT(1, valuesAppearingTwice); + + // Count distinct values - should be len-1 = 4 + unsigned int distinct = countDistinctValues(numbers, 5); + TEST_ASSERT_EQUAL_INT(4, distinct); + + free(numbers); +} + +// Test 5.2: Exactly one value appears twice (len=10) +void test_createNumbers_ExactlyOneDuplicateLen10(void) +{ + unsigned int *numbers = createNumbers(10); + + TEST_ASSERT_NOT_NULL(numbers); + + // Count how many distinct values appear exactly twice + unsigned int valuesAppearingTwice = 0; + unsigned int counted[10] = {0}; // Track which values we've counted + + for (unsigned int i = 0; i < 10; i++) + { + unsigned int value = numbers[i]; + unsigned int count = countOccurrences(numbers, 10, value); + + // If this value appears twice and we haven't counted it yet, count it now + if (count == 2) + { + int alreadyCounted = 0; + for (unsigned int j = 0; j < 10; j++) + { + if (counted[j] == value) + { + alreadyCounted = 1; + break; + } + } + + if (!alreadyCounted) + { + valuesAppearingTwice++; + counted[i] = value; // Mark this value as counted + } + } + } + + // Should have exactly one value that appears twice + TEST_ASSERT_EQUAL_INT(1, valuesAppearingTwice); + + // Count distinct values - should be len-1 = 9 + unsigned int distinct = countDistinctValues(numbers, 10); + TEST_ASSERT_EQUAL_INT(9, distinct); + + free(numbers); +} + +// Test 5.3: No accidental triplicates (len=5) +void test_createNumbers_NoTriplicatesLen5(void) +{ + unsigned int *numbers = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers); + + // Count occurrences of each value + for (unsigned int i = 0; i < 5; i++) + { + unsigned int count = countOccurrences(numbers, 5, numbers[i]); + + // Each value should appear 1 or 2 times, never 3 or more + TEST_ASSERT_TRUE(count == 1 || count == 2); + } + + free(numbers); +} + +// ============================================================================ +// TEST SUITE 6: createNumbers() - No Accidental Duplicates Before Intentional One +// ============================================================================ + +// Test 6.1: Total count validation - sum should be len +void test_createNumbers_TotalCountLen5(void) +{ + unsigned int *numbers = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers); + + // Sum of all occurrences should equal len (5) + // (unique_count * 1) + (1 duplicate_value * 2) = len + // which simplifies to: (len-1) + 2 = len + 1 ... wait that's wrong + + // Actually: if we have 4 unique values and 1 appears twice: + // 4 unique values, but 1 of them appears twice + // So: 3 values appearing once + 1 value appearing twice = 3 + 2 = 5 total entries + + // Total unique values = len - 1 = 4 + // But sum of elements = len = 5 + // This checks out: (len-1-1) * 1 + 1 * 2 = (len-2) + 2 = len ✓ + + unsigned int distinct = countDistinctValues(numbers, 5); + TEST_ASSERT_EQUAL_INT(4, distinct); + + free(numbers); +} + +// ============================================================================ +// TEST SUITE 7: createNumbers() - Randomness +// ============================================================================ + +// Test 7.1: Multiple calls produce different (or can produce different) results +void test_createNumbers_DifferentRandomResults(void) +{ + unsigned int *numbers1 = createNumbers(5); + + // Advance the random sequence a bit + for (int i = 0; i < 10; i++) + { + rand(); + } + + unsigned int *numbers2 = createNumbers(5); + + TEST_ASSERT_NOT_NULL(numbers1); + TEST_ASSERT_NOT_NULL(numbers2); + + // It's possible (though unlikely) that two random arrays are identical + // So we'll just verify that the function works and can produce different results + // by checking that both have correct structure + int identical = 1; + for (unsigned int i = 0; i < 5; i++) + { + if (numbers1[i] != numbers2[i]) + { + identical = 0; + break; + } + } + + // Note: identical could be 0 or 1, both are valid test outcomes + // The important thing is that both arrays are valid + (void)identical; + + free(numbers1); + free(numbers2); +} + +// ============================================================================ +// TEST SUITE 8: getDuplicate() - Finding the Duplicate +// ============================================================================ + +// Test 8.1: getDuplicate finds the correct value for len=5 +void test_getDuplicate_FindsCorrectValueLen5(void) +{ + unsigned int *numbers = createNumbers(5); + TEST_ASSERT_NOT_NULL(numbers); + + // Find the duplicate manually (using our test helper) + unsigned int expectedDuplicate = findDuplicate(numbers, 5); + TEST_ASSERT_NOT_EQUAL(0, expectedDuplicate); + + // Test getDuplicate() - should find the same duplicate + unsigned int actualDuplicate = getDuplicate(numbers, 5); + TEST_ASSERT_EQUAL_INT(expectedDuplicate, actualDuplicate); + + free(numbers); +} + +// Test 8.2: getDuplicate finds the correct value for len=10 +void test_getDuplicate_FindsCorrectValueLen10(void) +{ + unsigned int *numbers = createNumbers(10); + TEST_ASSERT_NOT_NULL(numbers); + + // Find the duplicate manually (using our test helper) + unsigned int expectedDuplicate = findDuplicate(numbers, 10); + TEST_ASSERT_NOT_EQUAL(0, expectedDuplicate); + + // Test getDuplicate() - should find the same duplicate + unsigned int actualDuplicate = getDuplicate(numbers, 10); + TEST_ASSERT_EQUAL_INT(expectedDuplicate, actualDuplicate); + + free(numbers); +} + +// Test 8.3: getDuplicate handles NULL pointer gracefully +void test_getDuplicate_NullPointerReturnsZero(void) +{ + unsigned int result = getDuplicate(NULL, 5); + TEST_ASSERT_EQUAL_INT(0, result); +} + +// Test 8.4: getDuplicate handles len < 2 gracefully +void test_getDuplicate_InvalidLenReturnsZero(void) +{ + unsigned int arr[] = {1}; + + // len = 0 + unsigned int result0 = getDuplicate(arr, 0); + TEST_ASSERT_EQUAL_INT(0, result0); + + // len = 1 + unsigned int result1 = getDuplicate(arr, 1); + TEST_ASSERT_EQUAL_INT(0, result1); +} + +// Test 8.5: getDuplicate works with len=20 +void test_getDuplicate_FindsCorrectValueLen20(void) +{ + unsigned int *numbers = createNumbers(20); + TEST_ASSERT_NOT_NULL(numbers); + + // Find the duplicate using our test helper + unsigned int expectedDuplicate = findDuplicate(numbers, 20); + TEST_ASSERT_NOT_EQUAL(0, expectedDuplicate); + + // Test getDuplicate() + unsigned int actualDuplicate = getDuplicate(numbers, 20); + TEST_ASSERT_EQUAL_INT(expectedDuplicate, actualDuplicate); + + free(numbers); +} + +// Test 8.6: getDuplicate doesn't modify the input array +void test_getDuplicate_DoesNotModifyInputArray(void) +{ + unsigned int *original = createNumbers(8); + TEST_ASSERT_NOT_NULL(original); + + // Create a copy to compare before and after + unsigned int *copy = (unsigned int *)malloc(8 * sizeof(unsigned int)); + memcpy(copy, original, 8 * sizeof(unsigned int)); + + // Call getDuplicate (which creates its own sorted copy internally) + getDuplicate(original, 8); + + // Verify the input array wasn't modified + for (unsigned int i = 0; i < 8; i++) + { + TEST_ASSERT_EQUAL_INT(copy[i], original[i]); + } + + free(original); + free(copy); +} + +// ============================================================================ +// TEST SUITE 9: Integration Tests +// ============================================================================ + +// Test 9.1: Full workflow for len=3 +void test_integration_CreateAndValidateLen3(void) +{ + unsigned int *numbers = createNumbers(3); + + TEST_ASSERT_NOT_NULL(numbers); + + // Validate: 3 elements exist and have valid values + for (unsigned int i = 0; i < 3; i++) + { + TEST_ASSERT_TRUE(numbers[i] > 0); + } + + // Validate: exactly 2 distinct values (one appears twice) + unsigned int distinct = countDistinctValues(numbers, 3); + TEST_ASSERT_EQUAL_INT(2, distinct); + + free(numbers); +} + +// Test 9.2: Full workflow for len=15 +void test_integration_CreateAndValidateLen15(void) +{ + unsigned int *numbers = createNumbers(15); + + TEST_ASSERT_NOT_NULL(numbers); + + // Validate: 15 elements exist and have valid values + for (unsigned int i = 0; i < 15; i++) + { + TEST_ASSERT_TRUE(numbers[i] > 0); + } + + // Validate: exactly 14 distinct values + unsigned int distinct = countDistinctValues(numbers, 15); + TEST_ASSERT_EQUAL_INT(14, distinct); + + free(numbers); +} + +// Test 9.3: Memory cleanup - no segfault +void test_integration_MultipleAllocationsAndFrees(void) +{ + for (unsigned int run = 0; run < 5; run++) + { + unsigned int *numbers = createNumbers(5 + run); + TEST_ASSERT_NOT_NULL(numbers); + free(numbers); + } + + // If we reach here without segfault, cleanup worked + TEST_ASSERT_TRUE(1); +} + +// ============================================================================ +// TEST RUNNER +// ============================================================================ + +int main(void) +{ + UNITY_BEGIN(); + + // Initialize random number seed once at the beginning + srand(time(NULL)); + + // Test Suite 1: Input Validation + RUN_TEST(test_createNumbers_InvalidLenZero); + RUN_TEST(test_createNumbers_InvalidLenOne); + + // Test Suite 2: Minimum Valid Input + RUN_TEST(test_createNumbers_MinimumSize); + + // Test Suite 3: Array Size + RUN_TEST(test_createNumbers_ArraySize5); + RUN_TEST(test_createNumbers_ArraySize10); + + // Test Suite 4: Value Range + RUN_TEST(test_createNumbers_ValueRangeLen5); + RUN_TEST(test_createNumbers_ValueRangeLen20); + + // Test Suite 5: Duplicate Detection + RUN_TEST(test_createNumbers_ExactlyOneDuplicateLen5); + RUN_TEST(test_createNumbers_ExactlyOneDuplicateLen10); + RUN_TEST(test_createNumbers_NoTriplicatesLen5); + + // Test Suite 6: No Accidental Duplicates + RUN_TEST(test_createNumbers_TotalCountLen5); + + // Test Suite 7: Randomness + RUN_TEST(test_createNumbers_DifferentRandomResults); + + // Test Suite 8: getDuplicate + RUN_TEST(test_getDuplicate_FindsCorrectValueLen5); + RUN_TEST(test_getDuplicate_FindsCorrectValueLen10); + RUN_TEST(test_getDuplicate_NullPointerReturnsZero); + RUN_TEST(test_getDuplicate_InvalidLenReturnsZero); + RUN_TEST(test_getDuplicate_FindsCorrectValueLen20); + RUN_TEST(test_getDuplicate_DoesNotModifyInputArray); + + // Test Suite 9: Integration + RUN_TEST(test_integration_CreateAndValidateLen3); + RUN_TEST(test_integration_CreateAndValidateLen15); + RUN_TEST(test_integration_MultipleAllocationsAndFrees); + + return UNITY_END(); +} diff --git a/unity.o b/unity.o new file mode 100644 index 0000000..8bcdd9b Binary files /dev/null and b/unity.o differ