330 lines
9.4 KiB
C
330 lines
9.4 KiB
C
/*
|
|
* Unit tests for String data type
|
|
*/
|
|
|
|
#define TEST(NAME) do { \
|
|
printf("Running test_%s...", #NAME); \
|
|
test_##NAME(); \
|
|
printf(" PASSED\n"); \
|
|
} while(0)
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "../src/string.h"
|
|
|
|
// Test string creation
|
|
void test_string_new(void) {
|
|
string_result_t res = string_new("hello");
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(res.value.string != NULL);
|
|
assert(strcmp(res.value.string->data, "hello") == 0);
|
|
assert(string_size(res.value.string) == 5);
|
|
assert(res.value.string->byte_size == 5);
|
|
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test empty string
|
|
void test_string_new_empty(void) {
|
|
string_result_t res = string_new("");
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(string_size(res.value.string) == 0);
|
|
assert(res.value.string->byte_size == 0);
|
|
assert(res.value.string->data[0] == '\0');
|
|
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test cloning an existing string
|
|
void test_string_clone(void) {
|
|
string_t *original = string_new("Original").value.string;
|
|
string_result_t res = string_clone(original);
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(res.value.string != original); // Different memory address
|
|
assert(strcmp(res.value.string->data, original->data) == 0);
|
|
assert(res.value.string->byte_size == original->byte_size);
|
|
|
|
string_destroy(original);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test string concatenation
|
|
void test_string_concat(void) {
|
|
string_t *str1 = string_new("Foo").value.string;
|
|
string_t *str2 = string_new(" Bar").value.string;
|
|
|
|
string_result_t res = string_concat(str1, str2);
|
|
assert(res.status == STRING_OK);
|
|
assert(strcmp(res.value.string->data, "Foo Bar") == 0);
|
|
assert(string_size(res.value.string) == 7);
|
|
|
|
string_destroy(str1);
|
|
string_destroy(str2);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test if string contains a substring
|
|
void test_string_contains(void) {
|
|
string_t *haystack = string_new("Hello 🌍 World").value.string;
|
|
string_t *needle_ascii = string_new("World").value.string;
|
|
string_t *needle_utf8 = string_new("🌍").value.string;
|
|
string_t *needle_none = string_new("not found").value.string;
|
|
|
|
// World starts at symbol 8
|
|
string_result_t res1 = string_contains(haystack, needle_ascii);
|
|
assert(res1.status == STRING_OK);
|
|
assert(res1.value.idx == 8);
|
|
|
|
// 🌍 is at position 6
|
|
string_result_t res2 = string_contains(haystack, needle_utf8);
|
|
assert(res2.status == STRING_OK);
|
|
assert(res2.value.idx == 6);
|
|
|
|
// Not found should return -1
|
|
string_result_t res3 = string_contains(haystack, needle_none);
|
|
assert(res3.status == STRING_OK);
|
|
assert(res3.value.idx == -1);
|
|
|
|
string_destroy(haystack);
|
|
string_destroy(needle_ascii);
|
|
string_destroy(needle_utf8);
|
|
string_destroy(needle_none);
|
|
}
|
|
|
|
// Test string slicing
|
|
void test_string_slice(void) {
|
|
// ASCII slice
|
|
string_t *str1 = string_new("foobar").value.string;
|
|
string_result_t res1 = string_slice(str1, 2, 4);
|
|
|
|
assert(res1.status == STRING_OK);
|
|
assert(strcmp(res1.value.string->data, "oba") == 0);
|
|
assert(res1.value.string->char_count == 3);
|
|
|
|
// UTF-8 slice
|
|
string_t *str2 = string_new("AB😆🌍").value.string;
|
|
string_result_t res2 = string_slice(str2, 2, 2);
|
|
|
|
assert(res2.status == STRING_OK);
|
|
assert(strcmp(res2.value.string->data, "😆") == 0);
|
|
assert(res2.value.string->byte_size == 4); // emoji = 4 bytes
|
|
|
|
// UTF-8 + ASCII slice
|
|
string_result_t res3 = string_slice(str2, 0, 2);
|
|
assert(res3.status == STRING_OK);
|
|
assert(strcmp(res3.value.string->data, "AB😆") == 0);
|
|
|
|
// Invalid bounds
|
|
string_result_t res4 = string_slice(str1, 5, 2);
|
|
assert(res4.status == STRING_ERR_OVERFLOW);
|
|
|
|
res4 = string_slice(str1, 1, 50);
|
|
assert(res4.status == STRING_ERR_OVERFLOW);
|
|
|
|
string_destroy(str1);
|
|
string_destroy(str2);
|
|
string_destroy(res1.value.string);
|
|
string_destroy(res2.value.string);
|
|
string_destroy(res3.value.string);
|
|
}
|
|
|
|
// Test case-insensitive and sensitive comparison
|
|
void test_string_eq(void) {
|
|
string_t *str1 = string_new("Foo").value.string;
|
|
string_t *str2 = string_new("foo").value.string;
|
|
|
|
// Case sensitive comparison should be false
|
|
assert(string_eq(str1, str2, true).value.is_equ == false);
|
|
// Case insensitive comparison should be true
|
|
assert(string_eq(str1, str2, false).value.is_equ == true);
|
|
|
|
string_destroy(str1);
|
|
string_destroy(str2);
|
|
}
|
|
|
|
// Test string reverse using UTF-8 symbols
|
|
void test_string_reverse_utf8(void) {
|
|
string_t *str = string_new("A🌍Z").value.string;
|
|
|
|
string_result_t res = string_reverse(str);
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(string_size(res.value.string) == 3);
|
|
assert(strcmp(res.value.string->data, "Z🌍A") == 0);
|
|
assert(string_size(res.value.string) == 3);
|
|
|
|
string_destroy(str);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test string get_at
|
|
void test_string_get_at(void) {
|
|
string_t *str = string_new("AB😆🌍").value.string;
|
|
|
|
// 😆 is at index 2
|
|
string_result_t res1 = string_get_at(str, 2);
|
|
assert(res1.status == STRING_OK);
|
|
assert(strcmp((char*)res1.value.symbol, "😆") == 0);
|
|
free(res1.value.symbol);
|
|
|
|
// 🌍 is at index 3
|
|
string_result_t res2 = string_get_at(str, 3);
|
|
assert(res2.status == STRING_OK);
|
|
assert(strcmp((char*)res2.value.symbol, "🌍") == 0);
|
|
free(res2.value.symbol);
|
|
|
|
string_destroy(str);
|
|
}
|
|
|
|
// Test string get_at with invalid index
|
|
void test_string_get_at_overflow(void) {
|
|
string_t *str = string_new("ABC").value.string;
|
|
|
|
string_result_t res = string_get_at(str, 50);
|
|
assert(res.status == STRING_ERR_OVERFLOW);
|
|
|
|
string_destroy(str);
|
|
}
|
|
|
|
// Test mutation of UTF-8 symbol
|
|
void test_string_set_at(void) {
|
|
string_t *str = string_new("ABC").value.string;
|
|
|
|
// Replace 'B' with emoji
|
|
string_result_t res = string_set_at(str, 1, "😆");
|
|
string_t *altered = res.value.string;
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(strcmp(altered->data, "A😆C") == 0);
|
|
assert(string_size(altered) == 3);
|
|
assert(altered->byte_size == 6); // that is: A (1B) + emoji (4B) + C (1B)
|
|
|
|
string_destroy(str);
|
|
string_destroy(altered);
|
|
}
|
|
|
|
// Test mutation of invalid UTF-8 symbol
|
|
void test_string_set_at_invalid_utf8(void) {
|
|
string_t *str = string_new("ABC").value.string;
|
|
|
|
const char * const invalid_sym1 = "\xFF";
|
|
const char * const invalid_sym2 = "\x80";
|
|
|
|
string_result_t res1 = string_set_at(str, 1, invalid_sym1);
|
|
assert(res1.status == STRING_ERR_INVALID_UTF8);
|
|
|
|
string_result_t res2 = string_set_at(str, 1, invalid_sym2);
|
|
assert(res2.status == STRING_ERR_INVALID_UTF8);
|
|
|
|
string_destroy(str);
|
|
}
|
|
|
|
// Test mutation with overflow
|
|
void test_string_set_at_overflow(void) {
|
|
string_t *str = string_new("ABC").value.string;
|
|
|
|
string_result_t res = string_set_at(str, 10, "a");
|
|
assert(res.status == STRING_ERR_OVERFLOW);
|
|
|
|
string_destroy(str);
|
|
}
|
|
|
|
// Test string to lowercase
|
|
void test_string_to_lower(void) {
|
|
string_t *str = string_new("AbC").value.string;
|
|
string_result_t res = string_to_lower(str);
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(strcmp(res.value.string->data, "abc") == 0);
|
|
|
|
string_destroy(str);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test string to uppercase
|
|
void test_string_to_upper(void) {
|
|
string_t *str = string_new("aBc").value.string;
|
|
string_result_t res = string_to_upper(str);
|
|
|
|
assert(res.status == STRING_OK);
|
|
assert(strcmp(res.value.string->data, "ABC") == 0);
|
|
|
|
string_destroy(str);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test whitespace trimming
|
|
void test_string_trim(void) {
|
|
string_t *str = string_new(" \t Foo Bar \n ").value.string;
|
|
|
|
string_result_t res = string_trim(str);
|
|
assert(res.status == STRING_OK);
|
|
assert(strcmp(res.value.string->data, "Foo Bar") == 0);
|
|
|
|
string_destroy(str);
|
|
string_destroy(res.value.string);
|
|
}
|
|
|
|
// Test string splitting into an array
|
|
void test_string_split(void) {
|
|
string_t *str = string_new("Red,Green,Blue").value.string;
|
|
|
|
string_result_t res = string_split(str, ",");
|
|
assert(res.status == STRING_OK);
|
|
assert(res.value.split.count == 3);
|
|
|
|
const size_t count = res.value.split.count;
|
|
string_t **strings = res.value.split.strings;
|
|
|
|
const char *expected[] = { "Red", "Green", "Blue" };
|
|
for (size_t idx = 0; idx < count; idx++) {
|
|
assert(strcmp(strings[idx]->data, expected[idx]) == 0);
|
|
}
|
|
|
|
string_split_destroy(strings, count);
|
|
string_destroy(str);
|
|
}
|
|
|
|
// Test string destroy
|
|
void test_string_destroy(void) {
|
|
string_t *str = string_new("delete me").value.string;
|
|
|
|
string_result_t res = string_destroy(str);
|
|
assert(res.status == STRING_OK);
|
|
|
|
string_result_t res_null = string_destroy(NULL);
|
|
assert(res_null.status == STRING_ERR_INVALID);
|
|
}
|
|
|
|
int main(void) {
|
|
printf("=== Running String unit tests ===\n\n");
|
|
|
|
TEST(string_new);
|
|
TEST(string_new_empty);
|
|
TEST(string_clone);
|
|
TEST(string_concat);
|
|
TEST(string_contains);
|
|
TEST(string_slice);
|
|
TEST(string_eq);
|
|
TEST(string_reverse_utf8);
|
|
TEST(string_get_at);
|
|
TEST(string_get_at_overflow);
|
|
TEST(string_set_at);
|
|
TEST(string_set_at_overflow);
|
|
TEST(string_set_at_invalid_utf8);
|
|
TEST(string_to_lower);
|
|
TEST(string_to_upper);
|
|
TEST(string_trim);
|
|
TEST(string_split);
|
|
TEST(string_destroy);
|
|
|
|
printf("\n=== All tests passed! ===\n");
|
|
return 0;
|
|
}
|