Added unit tests for String data type and updated CI

This commit is contained in:
2026-01-08 16:29:34 +01:00
parent 3b7e2dabc9
commit 44e3dfa58d
5 changed files with 171 additions and 7 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Run unit tests - name: Run unit tests
run: | run: |
./test_vector && ./test_map && ./test_bigint ./test_vector && ./test_map && ./test_bigint && ./test_string
- name: Run benchmarks - name: Run benchmarks
run: | run: |

View File

@@ -13,7 +13,7 @@ jobs:
- name: Run unit tests - name: Run unit tests
run: | run: |
./test_vector && ./test_map && ./test_bigint ./test_vector && ./test_map && ./test_bigint && ./test_string
- name: Run benchmarks - name: Run benchmarks
run: | run: |

View File

@@ -17,6 +17,7 @@ TARGET = usage
TEST_V_TARGET = test_vector TEST_V_TARGET = test_vector
TEST_M_TARGET = test_map TEST_M_TARGET = test_map
TEST_B_TARGET = test_bigint TEST_B_TARGET = test_bigint
TEST_S_TARGET = test_string
BENCH_TARGET = benchmark_datum BENCH_TARGET = benchmark_datum
LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o
@@ -24,7 +25,7 @@ PROG_OBJS = $(OBJ_DIR)/usage.o
.PHONY: all clean .PHONY: all clean
all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET) all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET)
bench: $(BENCH_TARGET) bench: $(BENCH_TARGET)
$(TARGET): $(PROG_OBJS) $(LIB_OBJS) $(TARGET): $(PROG_OBJS) $(LIB_OBJS)
@@ -39,6 +40,9 @@ $(TEST_M_TARGET): $(OBJ_DIR)/test_map.o $(OBJ_DIR)/map.o
$(TEST_B_TARGET): $(OBJ_DIR)/test_bigint.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o $(TEST_B_TARGET): $(OBJ_DIR)/test_bigint.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
$(TEST_S_TARGET): $(OBJ_DIR)/test_string.o $(OBJ_DIR)/string.o $(OBJ_DIR)/vector.o
$(CC) $(CFLAGS) -o $@ $^
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
@@ -65,4 +69,4 @@ $(BENCH_OBJ_DIR):
mkdir -p $(BENCH_OBJ_DIR) mkdir -p $(BENCH_OBJ_DIR)
clean: clean:
rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET) rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET)

View File

@@ -451,13 +451,20 @@ string_result_t string_set_at(string_t *str, size_t position, const char *utf8_c
int new_len; int new_len;
if (str == NULL || position >= str->char_count || utf8_is_char_valid(utf8_char, &new_len) == 0) { if (str == NULL || utf8_is_char_valid(utf8_char, &new_len) == 0) {
result.status = STRING_ERR_INVALID; result.status = STRING_ERR_INVALID;
SET_MSG(result, "Invalid index or character"); SET_MSG(result, "Invalid index or character");
return result; return result;
} }
if (position >= str->char_count) {
result.status = STRING_ERR_OVERFLOW;
SET_MSG(result, "Index out of bounds");
return result;
}
char *pos = str->data; char *pos = str->data;
for (size_t idx = 0; idx < position; idx++) { for (size_t idx = 0; idx < position; idx++) {
pos += utf8_char_len((unsigned char)*pos); pos += utf8_char_len((unsigned char)*pos);

153
tests/test_string.c Normal file
View File

@@ -0,0 +1,153 @@
/*
* 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 "../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_len(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_len(res.value.string) == 0);
assert(res.value.string->byte_size == 0);
assert(res.value.string->data[0] == '\0');
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_len(res.value.string) == 7);
string_destroy(str1);
string_destroy(str2);
string_destroy(res.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_len(res.value.string) == 3);
assert(strcmp(res.value.string->data, "Z🌍A") == 0);
assert(string_len(res.value.string) == 3);
string_destroy(str);
string_destroy(res.value.string);
}
// 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, "😆");
assert(res.status == STRING_OK);
assert(strcmp(str->data, "A😆C") == 0);
assert(string_len(str) == 3);
assert(str->byte_size == 6); // that is: A (1B) + emoji (4B) + C (1B)
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 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);
assert(strcmp(res.value.split.strings[0]->data, "Red") == 0);
assert(strcmp(res.value.split.strings[1]->data, "Green") == 0);
assert(strcmp(res.value.split.strings[2]->data, "Blue") == 0);
string_split_destroy(res.value.split.strings, res.value.split.count);
string_destroy(str);
}
int main(void) {
printf("=== Running Vector unit tests ===\n\n");
TEST(string_new);
TEST(string_new_empty);
TEST(string_concat);
TEST(string_eq);
TEST(string_reverse_utf8);
TEST(string_set_at);
TEST(string_set_at_overflow);
TEST(string_trim);
TEST(string_split);
printf("\n=== All tests passed! ===\n");
return 0;
}