Added unit tests for String data type and updated CI
This commit is contained in:
2
.github/workflows/clang-build.yml
vendored
2
.github/workflows/clang-build.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
./test_vector && ./test_map && ./test_bigint
|
||||
./test_vector && ./test_map && ./test_bigint && ./test_string
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
|
||||
2
.github/workflows/gcc-build.yml
vendored
2
.github/workflows/gcc-build.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
./test_vector && ./test_map && ./test_bigint
|
||||
./test_vector && ./test_map && ./test_bigint && ./test_string
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
|
||||
8
Makefile
8
Makefile
@@ -17,6 +17,7 @@ TARGET = usage
|
||||
TEST_V_TARGET = test_vector
|
||||
TEST_M_TARGET = test_map
|
||||
TEST_B_TARGET = test_bigint
|
||||
TEST_S_TARGET = test_string
|
||||
BENCH_TARGET = benchmark_datum
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
$(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
|
||||
$(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)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
@@ -65,4 +69,4 @@ $(BENCH_OBJ_DIR):
|
||||
mkdir -p $(BENCH_OBJ_DIR)
|
||||
|
||||
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)
|
||||
|
||||
@@ -451,13 +451,20 @@ string_result_t string_set_at(string_t *str, size_t position, const char *utf8_c
|
||||
|
||||
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;
|
||||
SET_MSG(result, "Invalid index or character");
|
||||
|
||||
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;
|
||||
for (size_t idx = 0; idx < position; idx++) {
|
||||
pos += utf8_char_len((unsigned char)*pos);
|
||||
|
||||
153
tests/test_string.c
Normal file
153
tests/test_string.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user