This commit is contained in:
alessandro bason 2025-06-24 17:47:08 +02:00
parent 95d74c2ef4
commit a66e58193f
25 changed files with 2600 additions and 93 deletions

237
tests/arena_tests.c Normal file
View file

@ -0,0 +1,237 @@
#include "../arena.h"
#include "../os.h"
#include "../core.h"
#include "runner.h"
UNIT_TEST(arena_init_virtual) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
ASSERT(arena.type == ARENA_VIRTUAL);
ASSERT(arena.beg != NULL);
ASSERT(arena.cur == arena.beg);
ASSERT(arena.end == arena.beg + MB(1));
arena_cleanup(&arena);
}
UNIT_TEST(arena_init_malloc) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
ASSERT(arena.type == ARENA_MALLOC);
ASSERT(arena.beg != NULL);
ASSERT(arena.cur == arena.beg);
ASSERT(arena.end == arena.beg + KB(4));
arena_cleanup(&arena);
}
UNIT_TEST(arena_init_malloc_always) {
arena_t arena = arena_make(ARENA_MALLOC_ALWAYS, KB(4));
ASSERT(arena.type == ARENA_MALLOC_ALWAYS);
arena_cleanup(&arena);
}
UNIT_TEST(arena_init_static) {
u8 buffer[KB(4)];
arena_t arena = arena_make(ARENA_STATIC, KB(4), buffer);
ASSERT(arena.type == ARENA_STATIC);
ASSERT(arena.beg == buffer);
ASSERT(arena.cur == arena.beg);
ASSERT(arena.end == arena.beg + KB(4));
arena_cleanup(&arena);
}
UNIT_TEST(arena_alloc_basic) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
int *ptr = alloc(&arena, int);
ASSERT(ptr != NULL);
*ptr = 42;
ASSERT(*ptr == 42);
ASSERT(arena.cur > arena.beg);
arena_cleanup(&arena);
}
UNIT_TEST(arena_alloc_array) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
int *arr = alloc(&arena, int, .count = 10);
ASSERT(arr != NULL);
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
for (int i = 0; i < 10; i++) {
ASSERT(arr[i] == i);
}
arena_cleanup(&arena);
}
UNIT_TEST(arena_alloc_custom_align) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
void *ptr = alloc(&arena, char, .align = 64);
ASSERT(ptr != NULL);
ASSERT(((uintptr_t)ptr & 63) == 0); // Should be 64-byte aligned
arena_cleanup(&arena);
}
UNIT_TEST(arena_alloc_nozero) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
int *ptr1 = alloc(&arena, int);
ASSERT(*ptr1 == 0); // Default zeroed
int *ptr2 = alloc(&arena, int, .flags = ALLOC_NOZERO);
// We can't assert on the value as it's uninitialized
ASSERT(ptr2 != NULL);
arena_cleanup(&arena);
}
UNIT_TEST(arena_alloc_soft_fail) {
u8 buffer[10];
arena_t arena = arena_make(ARENA_STATIC, 10, buffer);
void *ptr1 = alloc(&arena, char, .count = 5);
ASSERT(ptr1 != NULL);
// This would normally fail, but with SOFT_FAIL it returns NULL
void *ptr2 = alloc(&arena, char, .count = 10, .flags = ALLOC_SOFT_FAIL);
ASSERT(ptr2 == NULL);
arena_cleanup(&arena);
}
UNIT_TEST(arena_scratch) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
arena_t scratch = arena_scratch(&arena, KB(1));
ASSERT(scratch.beg != NULL);
ASSERT(scratch.type == ARENA_STATIC);
void *ptr = alloc(&scratch, int);
ASSERT(ptr != NULL);
// Scratch cleanup happens implicitly when parent arena is cleaned up
arena_cleanup(&arena);
}
UNIT_TEST(arena_tell) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
usize pos1 = arena_tell(&arena);
ASSERT(pos1 == 0);
alloc(&arena, int);
usize pos2 = arena_tell(&arena);
ASSERT(pos2 > pos1);
arena_cleanup(&arena);
}
UNIT_TEST(arena_remaining) {
arena_t arena = arena_make(ARENA_VIRTUAL, KB(64));
usize initial_remaining = arena_remaining(&arena);
ASSERT(initial_remaining == KB(64));
alloc(&arena, char, .count = KB(4));
usize after_alloc = arena_remaining(&arena);
ASSERT(after_alloc < initial_remaining);
ASSERT(after_alloc >= KB(60)); // Account for possible alignment padding
arena_cleanup(&arena);
}
UNIT_TEST(arena_capacity) {
arena_t arena = arena_make(ARENA_VIRTUAL, KB(64));
usize cap = arena_capacity(&arena);
ASSERT(cap == KB(64));
arena_cleanup(&arena);
}
UNIT_TEST(arena_rewind) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
usize mark = arena_tell(&arena);
int *ptr1 = alloc(&arena, int);
*ptr1 = 42;
alloc(&arena, char, .count = 100);
arena_rewind(&arena, mark);
int *ptr2 = alloc(&arena, int);
ASSERT(ptr2 == ptr1); // Should reuse the same memory
// Original value is lost after rewind
*ptr2 = 24;
ASSERT(*ptr2 == 24);
arena_cleanup(&arena);
}
UNIT_TEST(arena_pop) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
alloc(&arena, char, .count = 100);
usize pos = arena_tell(&arena);
alloc(&arena, char, .count = 50);
arena_pop(&arena, 50);
ASSERT(arena_tell(&arena) == pos);
arena_cleanup(&arena);
}
UNIT_TEST(arena_malloc_arena) {
void *ptr = alloc(&malloc_arena, int);
ASSERT(ptr != NULL);
// We need to free each allocation from malloc_arena manually
os_free(ptr);
}
UNIT_TEST(arena_alloc_mixed_types) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
int *i = alloc(&arena, int);
float *f = alloc(&arena, float);
char *c = alloc(&arena, char);
*i = 42;
*f = 3.14f;
*c = 'A';
ASSERT(*i == 42);
ASSERT(*f == 3.14f);
ASSERT(*c == 'A');
arena_cleanup(&arena);
}
UNIT_TEST(arena_multiple_arenas) {
arena_t arena1 = arena_make(ARENA_VIRTUAL, KB(4));
arena_t arena2 = arena_make(ARENA_VIRTUAL, KB(4));
int *ptr1 = alloc(&arena1, int);
int *ptr2 = alloc(&arena2, int);
*ptr1 = 42;
*ptr2 = 24;
ASSERT(*ptr1 == 42);
ASSERT(*ptr2 == 24);
arena_cleanup(&arena1);
arena_cleanup(&arena2);
}
UNIT_TEST(arena_stress_test) {
arena_t arena = arena_make(ARENA_VIRTUAL, MB(10));
// Allocate many objects
for (int i = 0; i < 1000; i++) {
int *ptr = alloc(&arena, int);
ASSERT(ptr != NULL);
*ptr = i;
}
// Allocate a large block
void *large = alloc(&arena, char, .count = MB(5));
ASSERT(large != NULL);
arena_cleanup(&arena);
}

229
tests/core_tests.c Normal file
View file

@ -0,0 +1,229 @@
#include "runner.h"
#include "../core.h"
#include <stdio.h>
#include <assert.h>
UNIT_TEST(arrlen_macro) {
int array[5] = {1, 2, 3, 4, 5};
ASSERT(arrlen(array) == 5);
char str[] = "hello";
ASSERT(arrlen(str) == 6); // Including null terminator
}
UNIT_TEST(min_max_macros) {
ASSERT(MIN(5, 10) == 5);
ASSERT(MIN(-5, 10) == -5);
ASSERT(MIN(5.5, 10.1) == 5.5);
ASSERT(MAX(5, 10) == 10);
ASSERT(MAX(-5, 10) == 10);
ASSERT(MAX(5.5, 10.1) == 10.1);
}
UNIT_TEST(size_constants) {
ASSERT(KB(1) == 1024);
ASSERT(MB(1) == 1024 * 1024);
ASSERT(GB(1) == 1024 * 1024 * 1024);
ASSERT(TB(1) == 1024ULL * 1024ULL * 1024ULL * 1024ULL);
ASSERT(KB(2) == 2048);
ASSERT(MB(2) == 2 * 1024 * 1024);
}
UNIT_TEST(linked_list) {
// Define a simple node structure
typedef struct Node {
int value;
struct Node *next;
} Node;
// Create some nodes
Node n1 = {1, NULL};
Node n2 = {2, NULL};
Node n3 = {3, NULL};
// Initialize list
Node *list = NULL;
// Push nodes onto list
list_push(list, &n3);
list_push(list, &n2);
list_push(list, &n1);
// Check list order
ASSERT(list == &n1);
ASSERT(list->next == &n2);
ASSERT(list->next->next == &n3);
ASSERT(list->next->next->next == NULL);
// Pop from list
list_pop(list);
ASSERT(list == &n2);
ASSERT(list->next == &n3);
list_pop(list);
ASSERT(list == &n3);
list_pop(list);
ASSERT(list == NULL);
}
UNIT_TEST(double_linked_list) {
// Define a double linked node
typedef struct DNode {
int value;
struct DNode *next;
struct DNode *prev;
} DNode;
// Create some nodes
DNode n1 = {1, NULL, NULL};
DNode n2 = {2, NULL, NULL};
DNode n3 = {3, NULL, NULL};
// Initialize list
DNode *list = NULL;
// Push nodes
dlist_push(list, &n3);
dlist_push(list, &n2);
dlist_push(list, &n1);
// Check list structure
ASSERT(list == &n1);
ASSERT(list->next == &n2);
ASSERT(list->next->next == &n3);
ASSERT(list->prev == NULL);
ASSERT(list->next->prev == &n1);
ASSERT(list->next->next->prev == &n2);
// Pop middle node
dlist_pop(list, &n2);
// Check updated structure
ASSERT(list == &n1);
ASSERT(list->next == &n3);
ASSERT(list->next->prev == &n1);
// Pop first node
dlist_pop(list, &n1);
ASSERT(list == &n3);
ASSERT(list->prev == NULL);
}
UNIT_TEST(ordered_linked_list) {
// Define a simple node
typedef struct ONode {
int value;
struct ONode *next;
} ONode;
// Create nodes
ONode n1 = {1, NULL};
ONode n2 = {2, NULL};
ONode n3 = {3, NULL};
// Initialize head and tail
ONode *head = NULL;
ONode *tail = NULL;
// Push nodes in order
olist_push(head, tail, &n1);
ASSERT(head == &n1);
ASSERT(tail == &n1);
olist_push(head, tail, &n2);
ASSERT(head == &n1);
ASSERT(tail == &n2);
ASSERT(head->next == &n2);
olist_push(head, tail, &n3);
ASSERT(head == &n1);
ASSERT(tail == &n3);
ASSERT(head->next == &n2);
ASSERT(head->next->next == &n3);
}
UNIT_TEST(for_each_macro) {
// Define a simple node
typedef struct Node {
int value;
struct Node *next;
} Node;
// Create linked list
Node n1 = {1, NULL};
Node n2 = {2, NULL};
Node n3 = {3, NULL};
n1.next = &n2;
n2.next = &n3;
Node *list = &n1;
// Use for_each to sum values
int sum = 0;
for_each(it, list) {
sum += it->value;
}
ASSERT(sum == 6); // 1 + 2 + 3
}
UNIT_TEST(fmt_print) {
// This function outputs to stdout, so we can't easily test its output
// Just verify it doesn't crash and returns a positive value
int result = fmt_print("Test %d %s\n", 42, "hello");
ASSERT(result > 0);
}
UNIT_TEST(fmt_buffer) {
char buffer[128];
// Basic formatting
int result = fmt_buffer(buffer, sizeof(buffer), "Int: %d", 42);
ASSERT(result > 0);
ASSERT(strcmp(buffer, "Int: 42") == 0);
// Multiple arguments
result = fmt_buffer(buffer, sizeof(buffer), "%s %d %.2f", "Test", 123, 3.14159);
ASSERT(result > 0);
ASSERT(strcmp(buffer, "Test 123 3.14") == 0);
// Buffer size limiting
result = fmt_buffer(buffer, 5, "Long text that won't fit");
ASSERT(result == 24); // fmt_buffer returns the lenght if it did fit
ASSERT(strlen(buffer) == 4);
}
// Helper function to test variadic function
int test_fmt_printv(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int result = fmt_printv(fmt, args);
va_end(args);
return result;
}
UNIT_TEST(fmt_printv) {
// Just verify it doesn't crash and returns positive value
int result = test_fmt_printv("Test %d %s\n", 42, "hello");
ASSERT(result > 0);
}
// Helper function to test variadic function
int test_fmt_bufferv(char *buf, usize len, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int result = fmt_bufferv(buf, len, fmt, args);
va_end(args);
return result;
}
UNIT_TEST(fmt_bufferv) {
char buffer[128];
int result = test_fmt_bufferv(buffer, sizeof(buffer), "%d %s", 42, "test");
ASSERT(result > 0);
ASSERT(strcmp(buffer, "42 test") == 0);
}

179
tests/highlight_tests.c Normal file
View file

@ -0,0 +1,179 @@
#include "runner.h"
#include "../highlight.h"
#include "../arena.h"
#include <stdio.h>
UNIT_TEST(highlight_init) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Create a basic configuration
hl_config_t config = {0};
// Define custom colors
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
config.colors[HL_COLOR_STRING] = strv_init("string");
config.colors[HL_COLOR_COMMENT] = strv_init("comment");
// Initialize highlighter
hl_ctx_t *ctx = hl_init(&arena, &config);
ASSERT(ctx != NULL);
arena_cleanup(&arena);
}
UNIT_TEST(highlight_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Create a configuration
hl_config_t config = {0};
// Define custom colors for HTML output
config.colors[HL_COLOR_NORMAL] = strv_init("color:black");
config.colors[HL_COLOR_KEYWORDS] = strv_init("color:blue");
config.colors[HL_COLOR_STRING] = strv_init("color:green");
config.colors[HL_COLOR_COMMENT] = strv_init("color:gray");
config.colors[HL_COLOR_NUMBER] = strv_init("color:purple");
// Set HTML output flag
config.flags = HL_FLAG_HTML;
// Initialize highlighter
hl_ctx_t *ctx = hl_init(&arena, &config);
ASSERT(ctx != NULL);
// Sample C code to highlight
strview_t code = strv_init(
"// This is a comment\n"
"int main() {\n"
" printf(\"Hello, World!\\n\");\n"
" return 0;\n"
"}\n"
);
// Highlight the code
str_t highlighted = hl_highlight(&arena, ctx, code);
// Verify the output
ASSERT(!str_is_empty(highlighted));
// We can't easily test the exact output without parsing HTML,
// but we can check that key strings are present
const char *html_start = "<span";
ASSERT(strstr(highlighted.buf, html_start) != NULL);
// Check if comment coloring is present
ASSERT(strstr(highlighted.buf, "// This is a comment") != NULL);
// Check if string highlighting is present
ASSERT(strstr(highlighted.buf, "Hello, World!") != NULL);
arena_cleanup(&arena);
}
UNIT_TEST(highlight_custom_keywords) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Create configuration
hl_config_t config = {0};
// Define colors
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
config.colors[HL_COLOR_CUSTOM_TYPES] = strv_init("custom-type");
// Define custom keywords
hl_keyword_t custom_keywords[] = {
{.keyword = strv_init("MyClass"), .color = HL_COLOR_CUSTOM_TYPES},
{.keyword = strv_init("custom_func"), .color = HL_COLOR_FUNC}
};
config.extra_kwrds = custom_keywords;
config.kwrds_count = 2;
// Initialize highlighter
hl_ctx_t *ctx = hl_init(&arena, &config);
ASSERT(ctx != NULL);
// Test code with custom keywords
strview_t code = strv_init(
"MyClass obj;\n"
"custom_func(obj);\n"
);
// Highlight the code
str_t highlighted = hl_highlight(&arena, ctx, code);
// Verify output contains our code
ASSERT(!str_is_empty(highlighted));
ASSERT(strstr(highlighted.buf, "MyClass") != NULL);
ASSERT(strstr(highlighted.buf, "custom_func") != NULL);
arena_cleanup(&arena);
}
UNIT_TEST(highlight_add_keyword) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Create configuration
hl_config_t config = {0};
// Define colors
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
config.colors[HL_COLOR_CUSTOM_TYPES] = strv_init("custom-type");
// Initialize highlighter
hl_ctx_t *ctx = hl_init(&arena, &config);
ASSERT(ctx != NULL);
// Add a custom keyword after initialization
hl_keyword_t new_keyword = {
.keyword = strv_init("NewKeyword"),
.color = HL_COLOR_CUSTOM_TYPES
};
hl_add_keyword(&arena, ctx, &new_keyword);
// Test code with the new keyword
strview_t code = strv_init("NewKeyword x;\n");
// Highlight the code
str_t highlighted = hl_highlight(&arena, ctx, code);
// Verify output contains our code
ASSERT(!str_is_empty(highlighted));
ASSERT(strstr(highlighted.buf, "NewKeyword") != NULL);
arena_cleanup(&arena);
}
UNIT_TEST(highlight_symbol_table) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Create configuration
hl_config_t config = {0};
// Define colors
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
config.colors[HL_COLOR_SYMBOL] = strv_init("symbol");
// Initialize highlighter
hl_ctx_t *ctx = hl_init(&arena, &config);
ASSERT(ctx != NULL);
// Set '@' as a symbol
hl_set_symbol_in_table(ctx, '@', true);
// Test code with the symbol
strview_t code = strv_init("@decorator\n");
// Highlight the code
str_t highlighted = hl_highlight(&arena, ctx, code);
// Verify output contains our code
ASSERT(!str_is_empty(highlighted));
ASSERT(strstr(highlighted.buf, "@") != NULL);
arena_cleanup(&arena);
}

238
tests/net_tests.c Normal file
View file

@ -0,0 +1,238 @@
#include "runner.h"
#include "../net.h"
#include "../arena.h"
#include <stdio.h>
// Initialization Tests
UNIT_TEST(net_init_cleanup) {
net_init();
// Simple test to make sure initialization succeeds
ASSERT(net_get_last_error() == 0);
net_cleanup();
}
// HTTP Method/Status Tests
UNIT_TEST(http_method_strings) {
ASSERT(strcmp(http_get_method_string(HTTP_GET), "GET") == 0);
ASSERT(strcmp(http_get_method_string(HTTP_POST), "POST") == 0);
ASSERT(strcmp(http_get_method_string(HTTP_HEAD), "HEAD") == 0);
ASSERT(strcmp(http_get_method_string(HTTP_PUT), "PUT") == 0);
ASSERT(strcmp(http_get_method_string(HTTP_DELETE), "DELETE") == 0);
}
UNIT_TEST(http_status_strings) {
ASSERT(strcmp(http_get_status_string(200), "OK") == 0);
ASSERT(strcmp(http_get_status_string(404), "NOT FOUND") == 0);
ASSERT(strcmp(http_get_status_string(500), "INTERNAL SERVER ERROR") == 0);
}
// HTTP Headers Tests
UNIT_TEST(http_headers) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Parse headers
strview_t header_str = strv_init("Content-Type: application/json\r\nUser-Agent: Colla\r\n");
http_header_t *headers = http_parse_headers(&arena, header_str);
// Check headers were parsed correctly
ASSERT(headers != NULL);
// Headers are parsed in reverse order
ASSERT(strv_equals(headers->key, strv_init("User-Agent")));
ASSERT(strv_equals(headers->value, strv_init("Colla")));
ASSERT(strv_equals(headers->next->key, strv_init("Content-Type")));
ASSERT(strv_equals(headers->next->value, strv_init("application/json")));
// Test header operations
ASSERT(http_has_header(headers, strv_init("Content-Type")));
ASSERT(!http_has_header(headers, strv_init("Accept")));
strview_t content_type = http_get_header(headers, strv_init("Content-Type"));
ASSERT(strv_equals(content_type, strv_init("application/json")));
// Don't try to free headers as they're allocated in the arena
arena_cleanup(&arena);
}
// HTTP Request/Response Parsing Tests
UNIT_TEST(http_request_parsing) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t req_str = strv_init(
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"User-Agent: Colla\r\n"
"\r\n"
);
http_req_t req = http_parse_req(&arena, req_str);
ASSERT(req.method == HTTP_GET);
ASSERT(req.version.major == 1);
ASSERT(req.version.minor == 1);
ASSERT(strv_equals(req.url, strv("index.html")));
ASSERT(http_has_header(req.headers, strv_init("Host")));
ASSERT(strv_equals(http_get_header(req.headers, strv_init("Host")), strv_init("example.com")));
// Convert back to string
str_t req_out = http_req_to_str(&arena, &req);
ASSERT(!str_is_empty(req_out));
arena_cleanup(&arena);
}
UNIT_TEST(http_response_parsing) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t res_str = strv_init(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 13\r\n"
"\r\n"
"Hello, World!"
);
http_res_t res = http_parse_res(&arena, res_str);
ASSERT(res.status_code == 200);
ASSERT(res.version.major == 1);
ASSERT(res.version.minor == 1);
ASSERT(strv_equals(res.body, strv_init("Hello, World!")));
ASSERT(http_has_header(res.headers, strv_init("Content-Type")));
ASSERT(strv_equals(http_get_header(res.headers, strv_init("Content-Type")), strv_init("text/html")));
// Convert back to string
str_t res_out = http_res_to_str(&arena, &res);
ASSERT(!str_is_empty(res_out));
arena_cleanup(&arena);
}
// URL Encoding/Decoding Tests
UNIT_TEST(http_url_encoding) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t original = strv_init("hello world & special chars: ?=&/");
str_t encoded = http_make_url_safe(&arena, original);
str_t decoded = http_decode_url_safe(&arena, strv_init_str(encoded));
ASSERT(!str_is_empty(encoded));
ASSERT(str_equals(decoded, str_init(&arena, "hello world & special chars: ?=&/")));
arena_cleanup(&arena);
}
UNIT_TEST(http_url_splitting) {
strview_t url = strv_init("http://example.com/path?query=value");
http_url_t split = http_split_url(url);
ASSERT(strv_equals(split.host, strv_init("example.com")));
ASSERT(strv_equals(split.uri, strv_init("/path?query=value")));
}
// HTTP Request Tests
// Note: These tests would actually make network requests, so we should mock them
// for real unit tests. Here we'll just test the setup part.
UNIT_TEST(http_request_setup) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Prepare headers
http_header_t headers[2] = {
{ .key = strv_init("Content-Type"), .value = strv_init("application/json"), .next = &headers[1] },
{ .key = strv_init("User-Agent"), .value = strv_init("Colla Test"), .next = NULL }
};
// Setup request descriptor
http_request_desc_t desc = {
.arena = &arena,
.url = strv_init("http://example.com"),
.version = { .major = 1, .minor = 1 },
.request_type = HTTP_GET,
.headers = headers,
.header_count = 2,
.body = strv_init("")
};
// We don't actually make the request, just verify the setup is correct
ASSERT(desc.arena == &arena);
ASSERT(strv_equals(desc.url, strv_init("http://example.com")));
ASSERT(desc.request_type == HTTP_GET);
ASSERT(desc.headers == headers);
ASSERT(desc.header_count == 2);
arena_cleanup(&arena);
}
// Socket Tests
UNIT_TEST(socket_basic) {
net_init();
// Open a socket
socket_t sock = sk_open(SOCK_TCP);
ASSERT(sk_is_valid(sock));
// Close the socket
ASSERT(sk_close(sock));
net_cleanup();
}
// SHA1 Tests
UNIT_TEST(sha1_hash) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
sha1_t ctx = sha1_init();
const char *data = "Hello, World!";
str_t hash = sha1_str(&arena, &ctx, data, strlen(data));
// The SHA1 hash for "Hello, World!" is known
// But we'll just verify it's not empty and has the expected format (40 hex chars)
ASSERT(!str_is_empty(hash));
ASSERT(hash.len == 40);
arena_cleanup(&arena);
}
// Base64 Tests
UNIT_TEST(base64_encoding) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
const char *original = "Hello, World!";
buffer_t input = { (u8*)original, strlen(original) };
// Encode
buffer_t encoded = base64_encode(&arena, input);
ASSERT(encoded.data != NULL);
ASSERT(encoded.len > 0);
// Decode
buffer_t decoded = base64_decode(&arena, encoded);
ASSERT(decoded.data != NULL);
ASSERT(decoded.len == input.len);
ASSERT(memcmp(decoded.data, input.data, input.len) == 0);
arena_cleanup(&arena);
}
// WebSocket Tests
UNIT_TEST(websocket_encoding) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t message = strv_init("Hello, WebSocket!");
// Encode message to WebSocket format
buffer_t encoded = websocket_encode(&arena, message);
ASSERT(encoded.data != NULL);
ASSERT(encoded.len > 0);
// Decode WebSocket message
str_t decoded = websocket_decode(&arena, encoded);
ASSERT(!str_is_empty(decoded));
ASSERT(str_equals(decoded, str_init(&arena, "Hello, WebSocket!")));
arena_cleanup(&arena);
}

374
tests/os_tests.c Normal file
View file

@ -0,0 +1,374 @@
#include "runner.h"
#include "../os.h"
#include "../arena.h"
#include <stdio.h>
// Handle Tests
UNIT_TEST(os_handle) {
oshandle_t zero = os_handle_zero();
ASSERT(!os_handle_valid(zero));
// Create a handle (using file open)
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t test_file = strv_init("test_file.txt");
// Create test file
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
ASSERT(os_handle_valid(h_write));
os_file_puts(h_write, strv_init("test content"));
os_file_close(h_write);
// Open the file and test handle functions
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
ASSERT(os_handle_valid(h_read));
ASSERT(!os_handle_match(h_read, zero));
oshandle_t h_read2 = os_file_open(test_file, FILEMODE_READ);
ASSERT(os_handle_valid(h_read2));
ASSERT(!os_handle_match(h_read, h_read2));
os_file_close(h_read);
os_file_close(h_read2);
os_file_delete(test_file);
arena_cleanup(&arena);
}
// File Operations Tests
UNIT_TEST(os_file_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t test_file = strv_init("test_file.txt");
// Delete if exists
if (os_file_exists(test_file)) {
os_file_delete(test_file);
}
// Check existence
ASSERT(!os_file_exists(test_file));
// Create and write
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
ASSERT(os_handle_valid(h_write));
os_file_putc(h_write, 'H');
os_file_puts(h_write, strv_init("ello World"));
os_file_close(h_write);
// Check existence after creation
ASSERT(os_file_exists(test_file));
// Read back
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
ASSERT(os_handle_valid(h_read));
char buffer[12] = {0};
usize read = os_file_read(h_read, buffer, 11);
ASSERT(read == 11);
ASSERT(strcmp(buffer, "Hello World") == 0);
os_file_close(h_read);
// Clean up
os_file_delete(test_file);
ASSERT(!os_file_exists(test_file));
arena_cleanup(&arena);
}
UNIT_TEST(os_file_seek) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t test_file = strv_init("test_file.txt");
// Create and write
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
ASSERT(os_handle_valid(h_write));
os_file_puts(h_write, strv_init("ABCDEFGHIJ"));
os_file_close(h_write);
// Open for reading
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
ASSERT(os_handle_valid(h_read));
// Seek to position 5
ASSERT(os_file_seek(h_read, 5));
// Read from position 5
char buffer[6] = {0};
usize read = os_file_read(h_read, buffer, 5);
ASSERT(read == 5);
ASSERT(strcmp(buffer, "FGHIJ") == 0);
// Rewind and read from beginning
os_file_rewind(h_read);
char buffer2[6] = {0};
read = os_file_read(h_read, buffer2, 5);
ASSERT(read == 5);
ASSERT(strcmp(buffer2, "ABCDE") == 0);
// Test file position
ASSERT(os_file_tell(h_read) == 5);
// Test file size
ASSERT(os_file_size(h_read) == 10);
// Seek to end
ASSERT(os_file_seek_end(h_read));
ASSERT(os_file_is_finished(h_read));
os_file_close(h_read);
os_file_delete(test_file);
arena_cleanup(&arena);
}
UNIT_TEST(os_file_read_write_all) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t test_file = strv_init("test_file.txt");
// Write string
strview_t test_data = strv_init("This is test data for read/write all functions");
ASSERT(os_file_write_all_str(test_file, test_data));
// Read back as string
str_t read_data = os_file_read_all_str(&arena, test_file);
ASSERT(str_equals(read_data, str_init(&arena, "This is test data for read/write all functions")));
// Read as buffer
buffer_t buffer = os_file_read_all(&arena, test_file);
ASSERT(buffer.len == test_data.len);
ASSERT(memcmp(buffer.data, test_data.buf, test_data.len) == 0);
// Write buffer
const char *new_data = "New buffer data";
buffer_t write_buffer = {(u8*)new_data, strlen(new_data)};
ASSERT(os_file_write_all(test_file, write_buffer));
// Read back after buffer write
str_t read_new = os_file_read_all_str(&arena, test_file);
ASSERT(str_equals(read_new, str_init(&arena, "New buffer data")));
// Clean up
os_file_delete(test_file);
arena_cleanup(&arena);
}
UNIT_TEST(os_file_path) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Test path splitting
strview_t path = strv_init("/path/to/file.txt");
strview_t dir, name, ext;
os_file_split_path(path, &dir, &name, &ext);
ASSERT(strv_equals(dir, strv_init("/path/to")));
ASSERT(strv_equals(name, strv_init("file")));
ASSERT(strv_equals(ext, strv_init(".txt")));
// Test full path resolution
strview_t relative_path = strv_init("test_file.txt");
tstr_t full_path = os_file_fullpath(&arena, relative_path);
// Can't easily test the exact value, but can verify it's not empty
ASSERT(full_path.len > 0);
arena_cleanup(&arena);
}
// Directory Tests
UNIT_TEST(os_dir_operations) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t test_dir = strv_init("test_dir");
// Delete if exists
if (os_dir_exists(test_dir)) {
os_dir_delete(test_dir);
}
// Create directory
ASSERT(os_dir_create(test_dir));
ASSERT(os_dir_exists(test_dir));
// Create test file in directory
strview_t test_file_path = strv_init("test_dir/test_file.txt");
oshandle_t h_write = os_file_open(test_file_path, FILEMODE_WRITE);
ASSERT(os_handle_valid(h_write));
os_file_puts(h_write, strv_init("test content"));
os_file_close(h_write);
// Test directory listing
dir_t *dir = os_dir_open(&arena, test_dir);
ASSERT(os_dir_is_valid(dir));
bool found_file = false;
dir_foreach(&arena, entry, dir) {
if (str_equals(entry->name, str_init(&arena, "test_file.txt"))) {
found_file = true;
ASSERT(entry->type == DIRTYPE_FILE);
ASSERT(entry->file_size == 12); // "test content"
}
}
ASSERT(found_file);
// Clean up
os_file_delete(test_file_path);
os_dir_close(dir);
// Directory should now be empty, so we can delete it
os_dir_delete(test_dir);
ASSERT(!os_dir_exists(test_dir));
arena_cleanup(&arena);
}
// Environment Variable Tests
UNIT_TEST(os_env_vars) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
arena_t scratch = arena_make(ARENA_MALLOC, KB(1));
// Set environment variable
strview_t key = strv_init("COLLA_TEST_VAR");
strview_t value = strv_init("test_value");
os_set_env_var(scratch, key, value);
// Get environment variable
str_t read_value = os_get_env_var(&arena, key);
ASSERT(str_equals(read_value, str_init(&arena, "test_value")));
// Get all environment variables
os_env_t *env = os_get_env(&arena);
ASSERT(env != NULL);
arena_cleanup(&scratch);
arena_cleanup(&arena);
}
// Virtual Memory Tests
UNIT_TEST(os_virtual_memory) {
usize page_size;
void *memory = os_reserve(MB(1), &page_size);
ASSERT(memory != NULL);
ASSERT(page_size > 0);
// Commit a page
ASSERT(os_commit(memory, 1));
// Write to the committed memory
memset(memory, 0x42, os_get_system_info().page_size);
// Release the memory
ASSERT(os_release(memory, MB(1)));
}
// Thread Tests
static int thread_test_func(u64 thread_id, void *userdata) {
int *value = (int*)userdata;
(*value)++;
return 42;
}
UNIT_TEST(os_thread) {
// Create thread data
int value = 0;
// Launch thread
oshandle_t thread = os_thread_launch(thread_test_func, &value);
ASSERT(os_handle_valid(thread));
// Get thread ID
u64 thread_id = os_thread_get_id(thread);
ASSERT(thread_id != 0);
// Join thread
int exit_code;
ASSERT(os_thread_join(thread, &exit_code));
ASSERT(exit_code == 42);
ASSERT(value == 1);
}
int test_mutex_trylock(u64 id, void *userdata) {
oshandle_t mutex = *((oshandle_t*)userdata);
return os_mutex_try_lock(mutex);
}
// Mutex Tests
UNIT_TEST(os_mutex) {
oshandle_t mutex = os_mutex_create();
ASSERT(os_handle_valid(mutex));
oshandle_t thread = os_thread_launch(test_mutex_trylock, &mutex);
// Lock mutex
os_mutex_lock(mutex);
int locked = 0;
os_thread_join(thread, &locked);
ASSERT(locked == false);
// Unlock
os_mutex_unlock(mutex);
// Try lock should succeed now
ASSERT(os_mutex_try_lock(mutex));
// Unlock again
os_mutex_unlock(mutex);
// Free mutex
os_mutex_free(mutex);
}
#if !COLLA_NO_CONDITION_VARIABLE
// Condition Variable Tests
struct cond_test_data {
oshandle_t mutex;
oshandle_t cond;
int counter;
};
static int cond_test_thread(u64 thread_id, void *userdata) {
struct cond_test_data *data = (struct cond_test_data*)userdata;
os_mutex_lock(data->mutex);
data->counter++;
os_mutex_unlock(data->mutex);
// Signal the condition
os_cond_signal(data->cond);
return 0;
}
UNIT_TEST(os_condition_variable) {
struct cond_test_data data;
data.mutex = os_mutex_create();
data.cond = os_cond_create();
data.counter = 0;
// Lock mutex before launching thread
os_mutex_lock(data.mutex);
// Launch thread
oshandle_t thread = os_thread_launch(cond_test_thread, &data);
// Wait for condition with timeout
os_cond_wait(data.cond, data.mutex, 1000);
// We should have the lock again, and counter should be 1
ASSERT(data.counter == 1);
// Unlock and cleanup
os_mutex_unlock(data.mutex);
os_thread_join(thread, NULL);
os_mutex_free(data.mutex);
os_cond_free(data.cond);
}
#endif

370
tests/parsers_tests.c Normal file
View file

@ -0,0 +1,370 @@
#include "runner.h"
#include "../parsers.h"
#include "../arena.h"
#include <stdio.h>
// INI Parser Tests
UNIT_TEST(ini_parse_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t ini_content = strv_init(
"[section1]\n"
"key1=value1\n"
"key2=value2\n"
"\n"
"[section2]\n"
"key3=value3\n"
"key4=value4\n"
);
ini_t ini = ini_parse_str(&arena, ini_content, NULL);
ASSERT(ini_is_valid(&ini));
// Test section1
initable_t *section1 = ini_get_table(&ini, strv_init("section1"));
ASSERT(section1 != NULL);
ASSERT(strv_equals(section1->name, strv_init("section1")));
// Test section1 values
inivalue_t *key1 = ini_get(section1, strv_init("key1"));
ASSERT(key1 != NULL);
ASSERT(strv_equals(key1->key, strv_init("key1")));
ASSERT(strv_equals(key1->value, strv_init("value1")));
inivalue_t *key2 = ini_get(section1, strv_init("key2"));
ASSERT(key2 != NULL);
ASSERT(strv_equals(key2->key, strv_init("key2")));
ASSERT(strv_equals(key2->value, strv_init("value2")));
// Test section2
initable_t *section2 = ini_get_table(&ini, strv_init("section2"));
ASSERT(section2 != NULL);
ASSERT(strv_equals(section2->name, strv_init("section2")));
// Test section2 values
inivalue_t *key3 = ini_get(section2, strv_init("key3"));
ASSERT(key3 != NULL);
ASSERT(strv_equals(key3->key, strv_init("key3")));
ASSERT(strv_equals(key3->value, strv_init("value3")));
inivalue_t *key4 = ini_get(section2, strv_init("key4"));
ASSERT(key4 != NULL);
ASSERT(strv_equals(key4->key, strv_init("key4")));
ASSERT(strv_equals(key4->value, strv_init("value4")));
arena_cleanup(&arena);
}
UNIT_TEST(ini_parse_with_options) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t ini_content = strv_init(
"[section1]\n"
"key1:value1\n"
"# This is a comment\n"
"key2:value2\n"
"\n"
"[section1]\n" // Duplicate section
"key3:value3\n"
);
iniopt_t options = {
.merge_duplicate_tables = true,
.merge_duplicate_keys = false,
.key_value_divider = ':',
.comment_vals = strv_init("#")
};
ini_t ini = ini_parse_str(&arena, ini_content, &options);
ASSERT(ini_is_valid(&ini));
// Test section1 (should be merged)
initable_t *section1 = ini_get_table(&ini, strv_init("section1"));
ASSERT(section1 != NULL);
// Check all keys exist in merged section
inivalue_t *key1 = ini_get(section1, strv_init("key1"));
ASSERT(key1 != NULL);
ASSERT(strv_equals(key1->value, strv_init("value1")));
inivalue_t *key2 = ini_get(section1, strv_init("key2"));
ASSERT(key2 != NULL);
ASSERT(strv_equals(key2->value, strv_init("value2")));
inivalue_t *key3 = ini_get(section1, strv_init("key3"));
ASSERT(key3 != NULL);
ASSERT(strv_equals(key3->value, strv_init("value3")));
arena_cleanup(&arena);
}
UNIT_TEST(ini_value_conversion) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t ini_content = strv_init(
"[values]\n"
"uint=42\n"
"int=-42\n"
"float=3.14\n"
"bool_true=true\n"
"bool_false=false\n"
"array=item1,item2,item3\n"
);
ini_t ini = ini_parse_str(&arena, ini_content, NULL);
initable_t *values = ini_get_table(&ini, strv_init("values"));
ASSERT(values != NULL);
// Test uint conversion
inivalue_t *uint_val = ini_get(values, strv_init("uint"));
ASSERT(uint_val != NULL);
ASSERT(ini_as_uint(uint_val) == 42);
// Test int conversion
inivalue_t *int_val = ini_get(values, strv_init("int"));
ASSERT(int_val != NULL);
ASSERT(ini_as_int(int_val) == -42);
// Test float conversion
inivalue_t *float_val = ini_get(values, strv_init("float"));
ASSERT(float_val != NULL);
ASSERT(ini_as_num(float_val) > 3.13 && ini_as_num(float_val) < 3.15);
// Test bool conversion
inivalue_t *bool_true = ini_get(values, strv_init("bool_true"));
ASSERT(bool_true != NULL);
ASSERT(ini_as_bool(bool_true) == true);
inivalue_t *bool_false = ini_get(values, strv_init("bool_false"));
ASSERT(bool_false != NULL);
ASSERT(ini_as_bool(bool_false) == false);
// Test array conversion
inivalue_t *array_val = ini_get(values, strv_init("array"));
ASSERT(array_val != NULL);
iniarray_t array = ini_as_arr(&arena, array_val, ',');
ASSERT(array.count == 3);
ASSERT(strv_equals(array.values[0], strv_init("item1")));
ASSERT(strv_equals(array.values[1], strv_init("item2")));
ASSERT(strv_equals(array.values[2], strv_init("item3")));
arena_cleanup(&arena);
}
// JSON Parser Tests
UNIT_TEST(json_parse_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t json_content = strv_init(
"{\n"
" \"string\": \"value\",\n"
" \"number\": 42,\n"
" \"bool\": true,\n"
" \"null\": null,\n"
" \"array\": [1, 2, 3],\n"
" \"object\": {\n"
" \"nested\": \"nested_value\"\n"
" }\n"
"}"
);
json_t *root = json_parse_str(&arena, json_content, JSON_DEFAULT);
ASSERT(root != NULL);
ASSERT(root->type == JSON_OBJECT);
// Test string
json_t *string_node = json_get(root, strv_init("string"));
ASSERT(json_check(string_node, JSON_STRING));
ASSERT(strv_equals(string_node->string, strv_init("value")));
// Test number
json_t *number_node = json_get(root, strv_init("number"));
ASSERT(json_check(number_node, JSON_NUMBER));
ASSERT(number_node->number == 42);
// Test bool
json_t *bool_node = json_get(root, strv_init("bool"));
ASSERT(json_check(bool_node, JSON_BOOL));
ASSERT(bool_node->boolean == true);
// Test null
json_t *null_node = json_get(root, strv_init("null"));
ASSERT(json_check(null_node, JSON_NULL));
// Test array
json_t *array_node = json_get(root, strv_init("array"));
ASSERT(json_check(array_node, JSON_ARRAY));
// Test array contents
int count = 0;
int sum = 0;
json_for(item, array_node) {
ASSERT(json_check(item, JSON_NUMBER));
sum += (int)item->number;
count++;
}
ASSERT(count == 3);
ASSERT(sum == 6); // 1 + 2 + 3
// Test nested object
json_t *object_node = json_get(root, strv_init("object"));
ASSERT(json_check(object_node, JSON_OBJECT));
json_t *nested_node = json_get(object_node, strv_init("nested"));
ASSERT(json_check(nested_node, JSON_STRING));
ASSERT(strv_equals(nested_node->string, strv_init("nested_value")));
arena_cleanup(&arena);
}
UNIT_TEST(json_parse_with_options) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// JSON with comments and trailing commas
strview_t json_content = strv_init(
"{\n"
" \"key1\": \"value1\",\n"
" // This is a comment\n"
" \"key2\": \"value2\",\n"
" \"array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n" // Trailing comma
" ],\n" // Trailing comma
"}"
);
// Test with default flags (should allow comments and trailing commas)
json_t *root1 = json_parse_str(&arena, json_content, JSON_DEFAULT);
ASSERT(root1 != NULL);
ASSERT(json_get(root1, strv_init("key1")) != NULL);
ASSERT(json_get(root1, strv_init("key2")) != NULL);
// Test with NO_COMMENTS and NO_TRAILING_COMMAS flags
json_t *root2 = json_parse_str(&arena, json_content, JSON_NO_COMMENTS | JSON_NO_TRAILING_COMMAS);
// This should fail parsing due to the strict flags - but the behavior depends on implementation
// Some parsers might ignore the errors, others might return NULL
// We'll check both possibilities
if (root2 != NULL) {
// If parsing succeeded despite strict flags, ensure the content is correct
ASSERT(json_get(root2, strv_init("key1")) != NULL);
// key2 might be missing if comment handling failed
}
arena_cleanup(&arena);
}
// XML Parser Tests
UNIT_TEST(xml_parse_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t xml_content = strv_init(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<root>\n"
" <item id=\"1\" type=\"book\">\n"
" <title>Test Title</title>\n"
" <author>Test Author</author>\n"
" </item>\n"
" <item id=\"2\" type=\"magazine\">\n"
" <title>Another Title</title>\n"
" </item>\n"
"</root>"
);
xml_t xml = xml_parse_str(&arena, xml_content);
ASSERT(xml.root != NULL);
ASSERT(strv_equals(xml.root->key, strv_init("root")));
// Find item tags
xmltag_t *item = xml_get_tag(xml.root, strv_init("item"), false);
ASSERT(item != NULL);
// Check attributes
strview_t id = xml_get_attribute(item, strv_init("id"));
ASSERT(strv_equals(id, strv_init("1")));
strview_t type = xml_get_attribute(item, strv_init("type"));
ASSERT(strv_equals(type, strv_init("book")));
// Check nested tags
xmltag_t *title = xml_get_tag(item, strv_init("title"), false);
ASSERT(title != NULL);
ASSERT(strv_equals(title->content, strv_init("Test Title")));
xmltag_t *author = xml_get_tag(item, strv_init("author"), false);
ASSERT(author != NULL);
ASSERT(strv_equals(author->content, strv_init("Test Author")));
// Check recursive tag finding
xmltag_t *title_recursive = xml_get_tag(xml.root, strv_init("title"), true);
ASSERT(title_recursive != NULL);
ASSERT(strv_equals(title_recursive->content, strv_init("Test Title")));
arena_cleanup(&arena);
}
// HTML Parser Tests
UNIT_TEST(html_parse_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
strview_t html_content = strv_init(
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Test Page</title>\n"
"</head>\n"
"<body>\n"
" <h1>Hello World</h1>\n"
" <p class=\"intro\">This is a test.</p>\n"
" <div id=\"content\">\n"
" <p>More content here.</p>\n"
" </div>\n"
"</body>\n"
"</html>"
);
html_t html = html_parse_str(&arena, html_content);
ASSERT(html.root != NULL);
ASSERT(str_equals(html.root->key, str_init(&arena, "html")));
// Find head and body
htmltag_t *head = html_get_tag(html.root, strv_init("head"), false);
ASSERT(head != NULL);
htmltag_t *body = html_get_tag(html.root, strv_init("body"), false);
ASSERT(body != NULL);
// Find title in head
htmltag_t *title = html_get_tag(head, strv_init("title"), false);
ASSERT(title != NULL);
ASSERT(strv_equals(title->content, strv_init("Test Page")));
// Find elements in body
htmltag_t *h1 = html_get_tag(body, strv_init("h1"), false);
ASSERT(h1 != NULL);
ASSERT(strv_equals(h1->content, strv_init("Hello World")));
// Find paragraph with class
htmltag_t *p = html_get_tag(body, strv_init("p"), false);
ASSERT(p != NULL);
strview_t p_class = html_get_attribute(p, strv_init("class"));
ASSERT(strv_equals(p_class, strv_init("intro")));
ASSERT(strv_equals(p->content, strv_init("This is a test.")));
// Find div by id
htmltag_t *div = html_get_tag(body, strv_init("div"), false);
ASSERT(div != NULL);
strview_t div_id = html_get_attribute(div, strv_init("id"));
ASSERT(strv_equals(div_id, strv_init("content")));
// Find nested paragraph using recursive search
htmltag_t *nested_p = html_get_tag(div, strv_init("p"), false);
ASSERT(nested_p != NULL);
ASSERT(strv_equals(nested_p->content, strv_init("More content here.")));
arena_cleanup(&arena);
}

View file

@ -0,0 +1,34 @@
#include "runner.h"
#include "../pretty_print.h"
#include "../arena.h"
#include <stdio.h>
UNIT_TEST(pretty_print_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// The pretty_print function outputs to console, so we can't easily verify its exact output
// Instead, we'll just verify it doesn't crash
pretty_print(arena, "Hello, <red>World!</>");
// Test with formatting
pretty_print(arena, "Value: <blue>%d</>, String: <green>%s</>", 42, "test");
arena_cleanup(&arena);
}
// Helper function to test variadic function
void test_pretty_printv(arena_t arena, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
pretty_printv(arena, fmt, args);
va_end(args);
}
UNIT_TEST(pretty_printv) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
// Test the helper function
test_pretty_printv(arena, "Test <yellow>%d</> <cyan>%s</>", 42, "variadic");
arena_cleanup(&arena);
}

41
tests/runner.h Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include "../core.h"
#include "../str.h"
typedef struct unit_test_t unit_test_t;
struct unit_test_t {
strview_t fname;
strview_t name;
void (*fn)(void);
unit_test_t *next;
};
extern unit_test_t *test_head;
extern unit_test_t *test_tail;
extern const char *last_fail_reason;
extern bool last_failed;
void ut_register(const char *file, const char *name, void (*fn)(void));
// #pragma data_seg(".CRT$XCU")
#define INITIALIZER(f) \
static void f(void); \
__declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
__pragma(comment(linker,"/include:" #f "_")) \
static void f(void)
#define UNIT_TEST(name) \
void ut__test_##name(void); \
INITIALIZER(ut__register_##name) { ut_register(__FILE__, #name, ut__test_##name); } \
void ut__test_##name(void)
#define ASSERT(cond) \
if (!(cond)) { \
last_fail_reason = "assert(" COLLA_STRINGIFY(cond) ") at " COLLA_STRINGIFY(__LINE__); \
last_failed = true; \
return; \
}

457
tests/str_tests.c Normal file
View file

@ -0,0 +1,457 @@
#include "runner.h"
#include "../str.h"
#include "../arena.h"
#include <stdio.h>
// String (str_t) Tests
UNIT_TEST(str_init_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_init(&arena, "hello");
ASSERT(s.len == 5);
ASSERT(s.buf != NULL);
ASSERT(memcmp(s.buf, "hello", 5) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_init_len) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_init_len(&arena, "hello world", 5);
ASSERT(s.len == 5);
ASSERT(s.buf != NULL);
ASSERT(memcmp(s.buf, "hello", 5) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_fmt) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_fmt(&arena, "Number: %d, String: %s", 42, "test");
ASSERT(s.buf != NULL);
ASSERT(memcmp(s.buf, "Number: 42, String: test", s.len) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_equals) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "hello");
str_t s2 = str_init(&arena, "hello");
str_t s3 = str_init(&arena, "world");
ASSERT(str_equals(s1, s2) == true);
ASSERT(str_equals(s1, s3) == false);
arena_cleanup(&arena);
}
UNIT_TEST(str_compare) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "abc");
str_t s2 = str_init(&arena, "abc");
str_t s3 = str_init(&arena, "abd");
str_t s4 = str_init(&arena, "abb");
ASSERT(str_compare(s1, s2) == 0);
ASSERT(str_compare(s1, s3) < 0);
ASSERT(str_compare(s1, s4) > 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_dup) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "test");
str_t s2 = str_dup(&arena, s1);
ASSERT(s1.len == s2.len);
ASSERT(s1.buf != s2.buf); // Different memory locations
ASSERT(memcmp(s1.buf, s2.buf, s1.len) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_cat) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "hello ");
str_t s2 = str_init(&arena, "world");
str_t s3 = str_cat(&arena, s1, s2);
ASSERT(s3.len == s1.len + s2.len);
ASSERT(memcmp(s3.buf, "hello world", s3.len) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_is_empty) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "test");
str_t s2 = STR_EMPTY;
ASSERT(str_is_empty(s1) == false);
ASSERT(str_is_empty(s2) == true);
arena_cleanup(&arena);
}
UNIT_TEST(str_lower_upper) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s1 = str_init(&arena, "TeSt");
str_lower(&s1);
ASSERT(memcmp(s1.buf, "test", 4) == 0);
str_t s2 = str_init(&arena, "TeSt");
str_upper(&s2);
ASSERT(memcmp(s2.buf, "TEST", 4) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_replace) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_init(&arena, "hello");
str_replace(&s, 'l', 'x');
ASSERT(memcmp(s.buf, "hexxo", 5) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(str_sub) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_init(&arena, "hello world");
strview_t sv = str_sub(s, 6, 11);
ASSERT(sv.len == 5);
ASSERT(memcmp(sv.buf, "world", 5) == 0);
arena_cleanup(&arena);
}
// String View (strview_t) Tests
UNIT_TEST(strv_init) {
strview_t sv = strv_init("hello");
ASSERT(sv.len == 5);
ASSERT(sv.buf != NULL);
ASSERT(memcmp(sv.buf, "hello", 5) == 0);
}
UNIT_TEST(strv_init_len) {
strview_t sv = strv_init_len("hello world", 5);
ASSERT(sv.len == 5);
ASSERT(sv.buf != NULL);
ASSERT(memcmp(sv.buf, "hello", 5) == 0);
}
UNIT_TEST(strv_init_str) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
str_t s = str_init(&arena, "hello");
strview_t sv = strv_init_str(s);
ASSERT(sv.len == s.len);
ASSERT(sv.buf == s.buf);
arena_cleanup(&arena);
}
UNIT_TEST(strv_is_empty) {
strview_t sv1 = strv_init("test");
strview_t sv2 = STRV_EMPTY;
ASSERT(strv_is_empty(sv1) == false);
ASSERT(strv_is_empty(sv2) == true);
}
UNIT_TEST(strv_equals) {
strview_t sv1 = strv_init("hello");
strview_t sv2 = strv_init("hello");
strview_t sv3 = strv_init("world");
ASSERT(strv_equals(sv1, sv2) == true);
ASSERT(strv_equals(sv1, sv3) == false);
}
UNIT_TEST(strv_compare) {
strview_t sv1 = strv_init("abc");
strview_t sv2 = strv_init("abc");
strview_t sv3 = strv_init("abd");
strview_t sv4 = strv_init("abb");
ASSERT(strv_compare(sv1, sv2) == 0);
ASSERT(strv_compare(sv1, sv3) < 0);
ASSERT(strv_compare(sv1, sv4) > 0);
}
UNIT_TEST(strv_front_back) {
strview_t sv = strv_init("hello");
ASSERT(strv_front(sv) == 'h');
ASSERT(strv_back(sv) == 'o');
}
UNIT_TEST(strv_remove_prefix_suffix) {
strview_t sv = strv_init("hello");
strview_t prefix_removed = strv_remove_prefix(sv, 2);
ASSERT(prefix_removed.len == 3);
ASSERT(memcmp(prefix_removed.buf, "llo", 3) == 0);
strview_t suffix_removed = strv_remove_suffix(sv, 2);
ASSERT(suffix_removed.len == 3);
ASSERT(memcmp(suffix_removed.buf, "hel", 3) == 0);
}
UNIT_TEST(strv_trim) {
strview_t sv1 = strv_init(" hello ");
strview_t sv2 = strv_init(" hello");
strview_t sv3 = strv_init("hello ");
strview_t trimmed1 = strv_trim(sv1);
strview_t trimmed2 = strv_trim_left(sv2);
strview_t trimmed3 = strv_trim_right(sv3);
ASSERT(trimmed1.len == 5);
ASSERT(memcmp(trimmed1.buf, "hello", 5) == 0);
ASSERT(trimmed2.len == 5);
ASSERT(memcmp(trimmed2.buf, "hello", 5) == 0);
ASSERT(trimmed3.len == 5);
ASSERT(memcmp(trimmed3.buf, "hello", 5) == 0);
}
UNIT_TEST(strv_sub) {
strview_t sv = strv_init("hello world");
strview_t sub = strv_sub(sv, 6, 11);
ASSERT(sub.len == 5);
ASSERT(memcmp(sub.buf, "world", 5) == 0);
}
UNIT_TEST(strv_starts_ends_with) {
strview_t sv = strv_init("hello");
ASSERT(strv_starts_with(sv, 'h') == true);
ASSERT(strv_starts_with(sv, 'e') == false);
ASSERT(strv_ends_with(sv, 'o') == true);
ASSERT(strv_ends_with(sv, 'l') == false);
strview_t prefix = strv_init("hel");
strview_t suffix = strv_init("llo");
ASSERT(strv_starts_with_view(sv, prefix) == true);
ASSERT(strv_ends_with_view(sv, suffix) == true);
}
UNIT_TEST(strv_contains) {
strview_t sv = strv_init("hello world");
ASSERT(strv_contains(sv, 'e') == true);
ASSERT(strv_contains(sv, 'z') == false);
strview_t sub = strv_init("world");
ASSERT(strv_contains_view(sv, sub) == true);
strview_t chars = strv_init("xyz");
ASSERT(strv_contains_either(sv, chars) == false);
strview_t chars2 = strv_init("xyo");
ASSERT(strv_contains_either(sv, chars2) == true);
}
UNIT_TEST(strv_find) {
strview_t sv = strv_init("hello world");
ASSERT(strv_find(sv, 'o', 0) == 4);
ASSERT(strv_find(sv, 'o', 5) == 7);
ASSERT(strv_find(sv, 'z', 0) == STR_NONE);
strview_t sub = strv_init("world");
ASSERT(strv_find_view(sv, sub, 0) == 6);
strview_t chars = strv_init("xwo");
ASSERT(strv_find_either(sv, chars, 0) == 4); // 'w' at position 6
}
UNIT_TEST(strv_rfind) {
strview_t sv = strv_init("hello world");
ASSERT(strv_rfind(sv, 'o', 0) == 7);
ASSERT(strv_rfind(sv, 'o', 5) == 4);
ASSERT(strv_rfind(sv, 'z', 0) == STR_NONE);
strview_t sub = strv_init("world");
ASSERT(strv_rfind_view(sv, sub, 0) == 6);
}
// Character Functions Tests
UNIT_TEST(char_functions) {
ASSERT(char_is_space(' ') == true);
ASSERT(char_is_space('\t') == true);
ASSERT(char_is_space('a') == false);
ASSERT(char_is_alpha('a') == true);
ASSERT(char_is_alpha('Z') == true);
ASSERT(char_is_alpha('1') == false);
ASSERT(char_is_num('0') == true);
ASSERT(char_is_num('9') == true);
ASSERT(char_is_num('a') == false);
ASSERT(char_lower('A') == 'a');
ASSERT(char_lower('a') == 'a');
ASSERT(char_lower('1') == '1');
}
// Input Stream Tests
UNIT_TEST(instream_basic) {
strview_t sv = strv_init("hello world");
instream_t is = istr_init(sv);
ASSERT(istr_get(&is) == 'h');
ASSERT(istr_get(&is) == 'e');
ASSERT(istr_peek(&is) == 'l');
ASSERT(istr_peek_next(&is) == 'l');
ASSERT(istr_get(&is) == 'l');
ASSERT(istr_prev(&is) == 'l');
ASSERT(istr_prev_prev(&is) == 'e');
istr_skip(&is, 2);
ASSERT(istr_peek(&is) == ' ');
istr_skip_whitespace(&is);
ASSERT(istr_peek(&is) == 'w');
istr_rewind(&is);
ASSERT(istr_peek(&is) == 'h');
ASSERT(istr_tell(&is) == 0);
ASSERT(istr_remaining(&is) == 11);
ASSERT(istr_is_finished(&is) == false);
}
UNIT_TEST(instream_ignore) {
strview_t sv = strv_init("hello,world");
instream_t is = istr_init(sv);
istr_ignore(&is, ',');
ASSERT(istr_peek(&is) == ',');
istr_ignore_and_skip(&is, ',');
ASSERT(istr_peek(&is) == 'w');
}
UNIT_TEST(instream_get_values) {
strview_t sv = strv_init("true 42 3.14 hello");
instream_t is = istr_init(sv);
bool b;
ASSERT(istr_get_bool(&is, &b) == true);
ASSERT(b == true);
istr_skip_whitespace(&is);
u32 u;
ASSERT(istr_get_u32(&is, &u) == true);
ASSERT(u == 42);
istr_skip_whitespace(&is);
double d;
ASSERT(istr_get_num(&is, &d) == true);
ASSERT(d > 3.13 && d < 3.15);
istr_skip_whitespace(&is);
strview_t word = istr_get_view(&is, ' ');
ASSERT(word.len == 5);
ASSERT(memcmp(word.buf, "hello", 5) == 0);
}
// Output Stream Tests
UNIT_TEST(outstream_basic) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
outstream_t os = ostr_init(&arena);
ostr_putc(&os, 'h');
ostr_putc(&os, 'i');
ASSERT(ostr_tell(&os) == 2);
ASSERT(ostr_back(&os) == 'i');
ostr_print(&os, " %d", 42);
strview_t result = ostr_as_view(&os);
ASSERT(result.len == 5);
ASSERT(memcmp(result.buf, "hi 42", 5) == 0);
ostr_pop(&os, 3);
result = ostr_as_view(&os);
ASSERT(result.len == 2);
ASSERT(memcmp(result.buf, "hi", 2) == 0);
ostr_clear(&os);
ASSERT(ostr_tell(&os) == 0);
arena_cleanup(&arena);
}
UNIT_TEST(outstream_append) {
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
outstream_t os = ostr_init(&arena);
ostr_append_bool(&os, true);
ostr_putc(&os, ' ');
ostr_append_uint(&os, 42);
ostr_putc(&os, ' ');
ostr_append_int(&os, -10);
ostr_putc(&os, ' ');
ostr_append_num(&os, 3.14);
str_t result = ostr_to_str(&os);
ASSERT(result.len > 0);
arena_cleanup(&arena);
}
// Binary Input Stream Tests
UNIT_TEST(binary_stream) {
u8 data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
buffer_t buffer = {data, sizeof(data)};
ibstream_t bs = ibstr_init(buffer);
ASSERT(ibstr_remaining(&bs) == 8);
ASSERT(ibstr_tell(&bs) == 0);
ASSERT(ibstr_is_finished(&bs) == false);
u8 val8;
ASSERT(ibstr_get_u8(&bs, &val8) == true);
ASSERT(val8 == 0x01);
u16 val16;
ASSERT(ibstr_get_u16(&bs, &val16) == true);
ASSERT(val16 == 0x0302); // Assuming little-endian
ibstr_skip(&bs, 1);
u32 val32;
ASSERT(ibstr_get_u32(&bs, &val32) == true);
ASSERT(val32 == 0x08070605); // Assuming little-endian
ASSERT(ibstr_is_finished(&bs) == true);
}

12
tests/string_tests.c Normal file
View file

@ -0,0 +1,12 @@
#include "runner.h"
#include "../str.h"
#include "../arena.h"
UNIT_TEST(str_format) {
arena_t arena = arena_make(ARENA_MALLOC, KB(1));
str_t s = str_fmt(&arena, "%d %s", 42, "test");
str_t lit = str(&arena, "42 test");
ASSERT(str_equals(s, lit));
arena_cleanup(&arena);
}