.
This commit is contained in:
parent
524ec0d1ce
commit
61c1060a98
16 changed files with 5043 additions and 31 deletions
64
colla.c
64
colla.c
|
|
@ -34,13 +34,6 @@
|
|||
|
||||
colla_modules_e colla__initialised_modules = 0;
|
||||
|
||||
extern void os_init(void);
|
||||
extern void os_cleanup(void);
|
||||
#if !COLLA_NO_NET
|
||||
extern void net_init(void);
|
||||
extern void net_cleanup(void);
|
||||
#endif
|
||||
|
||||
static char *colla_fmt__stb_callback(const char *buf, void *ud, int len) {
|
||||
// TODO maybe use os_write?
|
||||
fflush(stdout);
|
||||
|
|
@ -154,9 +147,9 @@ tstr_t tstr_init(TCHAR *str, usize optional_len) {
|
|||
};
|
||||
}
|
||||
|
||||
str16_t str16_init(u16 *str, usize optional_len) {
|
||||
str16_t str16_init(char16_t *str, usize optional_len) {
|
||||
if (str && !optional_len) {
|
||||
optional_len = wcslen(str);
|
||||
optional_len = str16_len(str);
|
||||
}
|
||||
return (str16_t){
|
||||
.buf = str,
|
||||
|
|
@ -173,6 +166,19 @@ str_t str_from_str16(arena_t *arena, str16_t src) {
|
|||
return out;
|
||||
}
|
||||
|
||||
usize str16_len(char16_t *str) {
|
||||
#if COLLA_WIN
|
||||
return wcslen(str);
|
||||
#else
|
||||
usize len = 0;
|
||||
while (*str) {
|
||||
str++;
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
#endif
|
||||
}
|
||||
|
||||
str_t str_from_tstr(arena_t *arena, tstr_t src) {
|
||||
#if COLLA_UNICODE
|
||||
return str_from_str16(arena, src);
|
||||
|
|
@ -1057,14 +1063,13 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
|
|||
usize new_cur = allocated + total;
|
||||
|
||||
if (new_cur > page_end) {
|
||||
usize extra_mem = os_pad_to_page(new_cur - page_end);
|
||||
usize page_size = os_get_system_info().page_size;
|
||||
// TODO is this really correct?
|
||||
usize num_of_pages = (extra_mem / page_size) + 1;
|
||||
|
||||
usize prev_page = os_pad_to_page(allocated - page_size);
|
||||
usize next_page = os_pad_to_page(new_cur);
|
||||
usize num_of_pages = (next_page - prev_page) / page_size;
|
||||
colla_assert(num_of_pages > 0);
|
||||
|
||||
if (!os_commit(arena->cur, num_of_pages + 1)) {
|
||||
if (!os_commit(arena->beg + prev_page, num_of_pages)) {
|
||||
if (!soft_fail) {
|
||||
fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
|
||||
}
|
||||
|
|
@ -1214,7 +1219,7 @@ usize os_file_write_buf(oshandle_t handle, buffer_t buf) {
|
|||
}
|
||||
|
||||
buffer_t os_file_read_all(arena_t *arena, strview_t path) {
|
||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
||||
oshandle_t fp = os_file_open(path, OS_FILE_READ);
|
||||
if (!os_handle_valid(fp)) {
|
||||
err("could not open file: %v", path);
|
||||
return (buffer_t){0};
|
||||
|
|
@ -1242,7 +1247,7 @@ buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle) {
|
|||
}
|
||||
|
||||
str_t os_file_read_all_str(arena_t *arena, strview_t path) {
|
||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
||||
oshandle_t fp = os_file_open(path, OS_FILE_READ);
|
||||
if (!os_handle_valid(fp)) {
|
||||
err("could not open file %v: %v", path, os_get_error_string(os_get_last_error()));
|
||||
return STR_EMPTY;
|
||||
|
|
@ -1273,7 +1278,7 @@ str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle) {
|
|||
}
|
||||
|
||||
bool os_file_write_all(strview_t name, buffer_t buffer) {
|
||||
oshandle_t fp = os_file_open(name, FILEMODE_WRITE);
|
||||
oshandle_t fp = os_file_open(name, OS_FILE_WRITE);
|
||||
bool result = os_file_write_all_fp(fp, buffer);
|
||||
os_file_close(fp);
|
||||
return result;
|
||||
|
|
@ -1284,7 +1289,7 @@ bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer) {
|
|||
}
|
||||
|
||||
bool os_file_write_all_str(strview_t name, strview_t data) {
|
||||
oshandle_t fp = os_file_open(name, FILEMODE_WRITE);
|
||||
oshandle_t fp = os_file_open(name, OS_FILE_WRITE);
|
||||
bool result = os_file_write_all_str_fp(fp, data);
|
||||
os_file_close(fp);
|
||||
return result;
|
||||
|
|
@ -1295,7 +1300,7 @@ bool os_file_write_all_str_fp(oshandle_t handle, strview_t data) {
|
|||
}
|
||||
|
||||
u64 os_file_time(strview_t path) {
|
||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
||||
oshandle_t fp = os_file_open(path, OS_FILE_READ);
|
||||
u64 result = os_file_time_fp(fp);
|
||||
os_file_close(fp);
|
||||
return result;
|
||||
|
|
@ -1334,7 +1339,7 @@ usize os_pad_to_page(usize byte_count) {
|
|||
void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options);
|
||||
|
||||
ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt) {
|
||||
oshandle_t fp = os_file_open(filename, FILEMODE_READ);
|
||||
oshandle_t fp = os_file_open(filename, OS_FILE_READ);
|
||||
ini_t out = ini_parse_fp(arena, fp, opt);
|
||||
os_file_close(fp);
|
||||
return out;
|
||||
|
|
@ -2735,6 +2740,13 @@ http_url_t http_split_url(strview_t url) {
|
|||
}
|
||||
|
||||
#if !COLLA_NO_NET
|
||||
|
||||
// HTTP /////////////////////////////
|
||||
|
||||
http_res_t http_request(http_request_desc_t *req) {
|
||||
return http_request_cb(req, NULL, NULL);
|
||||
}
|
||||
|
||||
// WEBSOCKETS ///////////////////////
|
||||
|
||||
#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
|
@ -2772,17 +2784,17 @@ buffer_t websocket_encode(arena_t *arena, strview_t message) {
|
|||
if (message.len > UINT16_MAX) extra += sizeof(u64);
|
||||
else if (message.len > UINT8_MAX) extra += sizeof(u16);
|
||||
u8 *bytes = alloc(arena, u8, message.len + extra);
|
||||
bytes[0] = 0b10000001;
|
||||
bytes[1] = 0b10000000;
|
||||
bytes[0] = 0x81; // 0b10000001
|
||||
bytes[1] = 0x80; // 0b10000000;
|
||||
int offset = 2;
|
||||
if (message.len > UINT16_MAX) {
|
||||
bytes[1] |= 0b01111111;
|
||||
bytes[1] |= 0x7F; // 0b01111111;
|
||||
u64 len = htonll(message.len);
|
||||
memmove(bytes + 2, &len, sizeof(len));
|
||||
offset += sizeof(u64);
|
||||
}
|
||||
else if (message.len > UINT8_MAX) {
|
||||
bytes[1] |= 0b01111110;
|
||||
bytes[1] |= 0x7E; // 0b01111110;
|
||||
u16 len = htons((u16)message.len);
|
||||
memmove(bytes + 2, &len, sizeof(len));
|
||||
offset += sizeof(u16);
|
||||
|
|
@ -2803,9 +2815,9 @@ str_t websocket_decode(arena_t *arena, buffer_t message) {
|
|||
str_t out = STR_EMPTY;
|
||||
u8 *bytes = message.data;
|
||||
|
||||
bool mask = bytes[1] & 0b10000000;
|
||||
bool mask = bytes[1] & 0x80; // 0b10000000;
|
||||
int offset = 2;
|
||||
u64 msglen = bytes[1] & 0b01111111;
|
||||
u64 msglen = bytes[1] & 0x7F; // 0b01111111;
|
||||
|
||||
// 16bit msg len
|
||||
if (msglen == 126) {
|
||||
|
|
|
|||
15
colla.h
15
colla.h
|
|
@ -1,9 +1,13 @@
|
|||
#ifndef COLLA_HEADER
|
||||
#define COLLA_HEADER
|
||||
|
||||
#define _FILE_OFFSET_BITS 1
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <uchar.h>
|
||||
|
||||
// CORE MODULES /////////////////////////////////
|
||||
|
||||
|
|
@ -91,7 +95,7 @@ void colla_cleanup(void);
|
|||
#define COLLA_CMT_LIB 0
|
||||
#endif
|
||||
|
||||
#if COLLA_TCC
|
||||
#if COLLA_TCC || COLLA_CLANG
|
||||
#define alignof __alignof__
|
||||
#endif
|
||||
|
||||
|
|
@ -309,7 +313,7 @@ struct str_t {
|
|||
|
||||
typedef struct str16_t str16_t;
|
||||
struct str16_t {
|
||||
u16 *buf;
|
||||
char16_t *buf;
|
||||
usize len;
|
||||
};
|
||||
|
||||
|
|
@ -354,11 +358,12 @@ str_t str_fmt(arena_t *arena, const char *fmt, ...);
|
|||
str_t str_fmtv(arena_t *arena, const char *fmt, va_list args);
|
||||
|
||||
tstr_t tstr_init(TCHAR *str, usize optional_len);
|
||||
str16_t str16_init(u16 *str, usize optional_len);
|
||||
str16_t str16_init(char16_t *str, usize optional_len);
|
||||
|
||||
str_t str_from_str16(arena_t *arena, str16_t src);
|
||||
str_t str_from_tstr(arena_t *arena, tstr_t src);
|
||||
str16_t str16_from_str(arena_t *arena, str_t src);
|
||||
usize str16_len(char16_t *str);
|
||||
|
||||
bool str_equals(str_t a, str_t b);
|
||||
int str_compare(str_t a, str_t b);
|
||||
|
|
@ -743,8 +748,8 @@ oshandle_t os_stdin(void);
|
|||
// == FILE ======================================
|
||||
|
||||
typedef enum filemode_e {
|
||||
FILEMODE_READ = 1 << 0,
|
||||
FILEMODE_WRITE = 1 << 1,
|
||||
OS_FILE_READ = 1 << 0,
|
||||
OS_FILE_WRITE = 1 << 1,
|
||||
} filemode_e;
|
||||
|
||||
bool os_file_exists(strview_t filename);
|
||||
|
|
|
|||
1945
stb/stb_sprintf.h
Normal file
1945
stb/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load diff
235
tests/arena_tests.c
Normal file
235
tests/arena_tests.c
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#include "../colla.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, .flags = ALLOC_SOFT_FAIL);
|
||||
ASSERT(ptr != NULL);
|
||||
*ptr = i;
|
||||
}
|
||||
|
||||
// Allocate a large block
|
||||
void *large = alloc(&arena, char, .count = MB(5), .flags = ALLOC_SOFT_FAIL);
|
||||
ASSERT(large != NULL);
|
||||
|
||||
arena_cleanup(&arena);
|
||||
}
|
||||
231
tests/core_tests.c
Normal file
231
tests/core_tests.c
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.h"
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.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);
|
||||
}
|
||||
178
tests/highlight_tests.c
Normal file
178
tests/highlight_tests.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
237
tests/net_tests.c
Normal file
237
tests/net_tests.c
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
387
tests/os_tests.c
Normal file
387
tests/os_tests.c
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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, OS_FILE_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, OS_FILE_READ);
|
||||
ASSERT(os_handle_valid(h_read));
|
||||
ASSERT(!os_handle_match(h_read, zero));
|
||||
|
||||
oshandle_t h_read2 = os_file_open(test_file, OS_FILE_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, OS_FILE_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, OS_FILE_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, OS_FILE_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, OS_FILE_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");
|
||||
os_file_write_all_str(relative_path, strv("hello world"));
|
||||
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);
|
||||
|
||||
os_file_delete(relative_path);
|
||||
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_file_delete(strv("test_dir/test_file.txt"));
|
||||
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, OS_FILE_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);
|
||||
warn(">> %zu", entry->file_size);
|
||||
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
|
||||
typedef struct {
|
||||
u64 canary_beg;
|
||||
oshandle_t mutex;
|
||||
oshandle_t cond_mutex;
|
||||
oshandle_t cond;
|
||||
int counter;
|
||||
u64 canary_end;
|
||||
} cond_test_data;
|
||||
|
||||
static int cond_test_thread(u64 thread_id, void *userdata) {
|
||||
cond_test_data *data = (cond_test_data*)userdata;
|
||||
|
||||
info("%zu %zu", data->canary_beg, data->canary_end);
|
||||
|
||||
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) {
|
||||
cond_test_data data = {
|
||||
.mutex = os_mutex_create(),
|
||||
.cond_mutex = os_mutex_create(),
|
||||
.cond = os_cond_create(),
|
||||
.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_mutex_lock(data.cond_mutex);
|
||||
os_cond_wait(data.cond, data.cond_mutex, 1000);
|
||||
os_mutex_unlock(data.cond_mutex);
|
||||
|
||||
// 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_mutex_free(data.cond_mutex);
|
||||
os_cond_free(data.cond);
|
||||
}
|
||||
#endif
|
||||
369
tests/parsers_tests.c
Normal file
369
tests/parsers_tests.c
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
33
tests/pretty_print_tests.c
Normal file
33
tests/pretty_print_tests.c
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
45
tests/runner.h
Normal file
45
tests/runner.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include "../colla.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")
|
||||
|
||||
#if COLLA_WIN
|
||||
#define INITIALIZER(f) \
|
||||
static void f(void); \
|
||||
__declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
|
||||
__pragma(comment(linker,"/include:" #f "_")) \
|
||||
static void f(void)
|
||||
#else
|
||||
#define INITIALIZER(f) \
|
||||
__attribute__((constructor)) static void f(void)
|
||||
#endif
|
||||
|
||||
#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; \
|
||||
}
|
||||
|
||||
456
tests/str_tests.c
Normal file
456
tests/str_tests.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
11
tests/string_tests.c
Normal file
11
tests/string_tests.c
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include "runner.h"
|
||||
#include "../colla.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);
|
||||
}
|
||||
|
||||
478
tools/nob.c
Normal file
478
tools/nob.c
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
#define COLLA_NO_CONDITION_VARIABLE 1
|
||||
#define COLLA_NO_NET 1
|
||||
|
||||
#include "../colla.c"
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
|
||||
#if COLLA_TCC
|
||||
|
||||
WINBASEAPI LPCH WINAPI GetEnvironmentStringsj(VOID);
|
||||
WINBASEAPI LPWCH WINAPI GetEnvironmentStringsW(VOID);
|
||||
WINBASEAPI BOOL WINAPI FreeEnvironmentStringsA(LPCH penv);
|
||||
WINBASEAPI BOOL WINAPI FreeEnvironmentStringsW(LPWCH penv);
|
||||
#ifdef UNICODE
|
||||
#define GetEnvironmentStrings GetEnvironmentStringsW
|
||||
#define FreeEnvironmentStrings FreeEnvironmentStringsW
|
||||
#else
|
||||
#define GetEnvironmentStrings GetEnvironmentStringsA
|
||||
#define FreeEnvironmentStrings FreeEnvironmentStringsA
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
int strv_to_int(strview_t strv) {
|
||||
instream_t in = istr_init(strv);
|
||||
i32 value = 0;
|
||||
if (!istr_get_i32(&in, &value)) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
str_t find_vcvars_path(arena_t *arena) {
|
||||
strview_t base_path = strv("C:/Program Files/Microsoft Visual Studio");
|
||||
// find year
|
||||
int year = 0;
|
||||
{
|
||||
arena_t tmp = *arena;
|
||||
|
||||
dir_t *dir = os_dir_open(&tmp, base_path);
|
||||
if (!os_dir_is_valid(dir)) {
|
||||
err("couldn't open directory (%v)", base_path);
|
||||
return STR_EMPTY;
|
||||
}
|
||||
|
||||
dir_foreach(&tmp, entry, dir) {
|
||||
if (entry->type != DIRTYPE_DIR) continue;
|
||||
|
||||
int number = strv_to_int(strv(entry->name));
|
||||
if (number > year) year = number;
|
||||
}
|
||||
}
|
||||
|
||||
if (year == 0) {
|
||||
err("couldn't find visual studio year version");
|
||||
return STR_EMPTY;
|
||||
}
|
||||
|
||||
str_t path_with_year = str_fmt(arena, "%v/%d", base_path, year);
|
||||
|
||||
// find edition
|
||||
const char *editions[] = {
|
||||
"Enterprise",
|
||||
"Professional",
|
||||
"Community",
|
||||
};
|
||||
|
||||
int edition = 0;
|
||||
|
||||
for (; edition < arrlen(editions); ++edition) {
|
||||
arena_t tmp = *arena;
|
||||
str_t path = str_fmt(&tmp, "%v/%s", path_with_year, editions[edition]);
|
||||
if (os_dir_exists(strv(path))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (edition >= arrlen(editions)) {
|
||||
err("couldn't find visual studio edition");
|
||||
return STR_EMPTY;
|
||||
}
|
||||
|
||||
str_t vcvars = str_fmt(arena, "%v/%s/VC/Auxiliary/Build/vcvars64.bat", path_with_year, editions[edition]);
|
||||
|
||||
return vcvars;
|
||||
}
|
||||
|
||||
bool load_cache(arena_t *arena) {
|
||||
if (!os_file_exists(strv("build/cache.ini"))) {
|
||||
err("build/cache.ini doesn't exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
arena_t scratch = *arena;
|
||||
|
||||
ini_t ini = ini_parse(&scratch, strv("build/cache.ini"), &(iniopt_t){ .comment_vals = strv("#") });
|
||||
initable_t *root = ini_get_table(&ini, INI_ROOT);
|
||||
if (!root) fatal("fail");
|
||||
|
||||
for_each (val, root->values) {
|
||||
os_set_env_var(scratch, val->key, val->value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef enum optimise_level_e {
|
||||
OPTIMISE_NONE,
|
||||
OPTIMISE_FAST,
|
||||
OPTIMISE_SMALL,
|
||||
OPTIMISE__COUNT,
|
||||
} optimise_level_e;
|
||||
|
||||
typedef enum warning_level_e {
|
||||
WARNING_NONE,
|
||||
WARNING_DEFAULT,
|
||||
WARNING_ALL,
|
||||
WARNING__COUNT,
|
||||
} warning_level_e;
|
||||
|
||||
typedef enum sanitiser_e {
|
||||
SANITISER_NONE,
|
||||
SANITISER_ADDRESS,
|
||||
SANITISER__COUNT,
|
||||
} sanitiser_e;
|
||||
|
||||
typedef enum cversion_e {
|
||||
CVERSION_LATEST,
|
||||
CVERSION_17,
|
||||
CVERSION_11,
|
||||
CVERSION__COUNT
|
||||
} cversion_e;
|
||||
|
||||
typedef struct options_t options_t;
|
||||
struct options_t {
|
||||
strview_t input_fname;
|
||||
strview_t out_fname;
|
||||
optimise_level_e optimisation;
|
||||
warning_level_e warnings;
|
||||
bool warnings_as_error;
|
||||
sanitiser_e sanitiser;
|
||||
bool fast_math;
|
||||
bool debug;
|
||||
strv_list_t *defines;
|
||||
cversion_e cstd;
|
||||
bool run;
|
||||
strv_list_t *run_args;
|
||||
bool is_cpp;
|
||||
};
|
||||
|
||||
void print_help_message(void) {
|
||||
puts("usage:");
|
||||
puts(" -r / -run [input.c] [args...] compiles and runs <input.c>, forwards <args...>");
|
||||
puts(" -h / -help print this message");
|
||||
puts(" -o / -out [filename] output filename (default: build/<file>.exe)");
|
||||
puts(" -O / -optimise [fast,small] optimisation level");
|
||||
puts(" -w / -warning [default,all] warning level");
|
||||
puts(" -werror treat warnings as errors");
|
||||
puts(" -fsanitize [address] turn on sanitiser");
|
||||
puts(" -fastmath turn on fast math");
|
||||
puts(" -g / -debug generate debug information");
|
||||
puts(" -D / -define [key=value,key] add a preprocessor define ");
|
||||
puts(" -std [c11,c17,clatest] select c standard (default: clatest)");
|
||||
puts(" -cpp compile c++ instead of c");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
optimise_level_e get_optimisation_level(strview_t arg) {
|
||||
if (strv_equals(arg, strv("fast"))) {
|
||||
return OPTIMISE_FAST;
|
||||
}
|
||||
else if (strv_equals(arg, strv("small"))) {
|
||||
return OPTIMISE_SMALL;
|
||||
}
|
||||
warn("unrecognised optimisation level: (%v)", arg);
|
||||
return OPTIMISE_NONE;
|
||||
}
|
||||
|
||||
warning_level_e get_warning_level(strview_t arg) {
|
||||
if (strv_equals(arg, strv("default"))) {
|
||||
return WARNING_DEFAULT;
|
||||
}
|
||||
else if (strv_equals(arg, strv("all"))) {
|
||||
return WARNING_ALL;
|
||||
}
|
||||
warn("unrecognised warning level: (%v)", arg);
|
||||
return WARNING_NONE;
|
||||
}
|
||||
|
||||
sanitiser_e get_sanitiser(strview_t arg) {
|
||||
if (strv_equals(arg, strv("address"))) {
|
||||
return SANITISER_ADDRESS;
|
||||
}
|
||||
warn("unrecognised sanitiser: (%v)", arg);
|
||||
return SANITISER_NONE;
|
||||
}
|
||||
|
||||
cversion_e get_cversion(strview_t arg) {
|
||||
if (strv_equals(arg, strv("clatest"))) {
|
||||
return CVERSION_LATEST;
|
||||
}
|
||||
else if (strv_equals(arg, strv("c17"))) {
|
||||
return CVERSION_17;
|
||||
}
|
||||
else if (strv_equals(arg, strv("c11"))) {
|
||||
return CVERSION_11;
|
||||
}
|
||||
warn("unrecognised c std version: (%v)", arg);
|
||||
return CVERSION_LATEST;
|
||||
}
|
||||
|
||||
options_t parse_options(arena_t *arena, int argc, char **argv) {
|
||||
options_t out = {0};
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
strview_t arg = strv(argv[i]);
|
||||
|
||||
#define CHECK_OPT_BEG() if (false) {}
|
||||
#define CHECK_OPT1(opt) else if (strv_equals(arg, strv("-" opt)))
|
||||
#define CHECK_OPT2(small, big) else if (strv_equals(arg, strv("-" small)) || strv_equals(arg, strv("-" big)))
|
||||
|
||||
#define GET_NEXT_ARG() (i + 1) < argc ? strv(argv[++i]) : STRV_EMPTY
|
||||
|
||||
CHECK_OPT_BEG()
|
||||
CHECK_OPT2("h", "help") {
|
||||
print_help_message();
|
||||
}
|
||||
CHECK_OPT2("o", "out") {
|
||||
strview_t out_fname = GET_NEXT_ARG();
|
||||
str_t out_fname_str = str_fmt(arena, "build/%v", out_fname);
|
||||
out.out_fname = strv(out_fname_str);
|
||||
}
|
||||
CHECK_OPT2("O", "optimise") {
|
||||
out.optimisation = get_optimisation_level(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT2("w", "warning") {
|
||||
out.warnings = get_warning_level(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("werror") {
|
||||
out.warnings_as_error = true;
|
||||
}
|
||||
CHECK_OPT1("fsanitize") {
|
||||
out.sanitiser = get_sanitiser(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("fastmath") {
|
||||
out.fast_math = true;
|
||||
}
|
||||
CHECK_OPT2("g", "debug") {
|
||||
out.debug = true;
|
||||
}
|
||||
CHECK_OPT2("D", "define") {
|
||||
darr_push(arena, out.defines, GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("std") {
|
||||
out.cstd = get_cversion(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("cpp") {
|
||||
out.is_cpp = true;
|
||||
}
|
||||
CHECK_OPT2("r", "run") {
|
||||
out.run = true;
|
||||
out.input_fname = GET_NEXT_ARG();
|
||||
for (i += 1; i < argc; ++i) {
|
||||
darr_push(arena, out.run_args, strv(argv[i]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.input_fname = arg;
|
||||
}
|
||||
}
|
||||
|
||||
#undef CHECK_OPT_BEG
|
||||
#undef CHECK_OPT1
|
||||
#undef CHECK_OPT2
|
||||
#undef GET_NEXT_ARG
|
||||
|
||||
if (strv_is_empty(out.out_fname)) {
|
||||
strview_t name;
|
||||
os_file_split_path(out.input_fname, NULL, &name, NULL);
|
||||
str_t out_fname = str_fmt(arena, "build\\%v", name);
|
||||
out.out_fname = strv(out_fname);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void cl_print_line(arena_t scratch, strview_t line, strview_t substr, strview_t col) {
|
||||
usize sub_beg = strv_find_view(line, substr, 0);
|
||||
|
||||
strview_t file_part = strv_trim(strv_sub(line, 0, sub_beg));
|
||||
strview_t sub_part = strv_sub(line, sub_beg, STR_END);
|
||||
|
||||
usize last_beg = strv_find(sub_part, ':', 0) + 1;
|
||||
|
||||
strview_t last_part = strv_trim(strv_sub(sub_part, last_beg, STR_END));
|
||||
sub_part = strv_sub(sub_part, 0, last_beg);
|
||||
|
||||
pretty_print(scratch,
|
||||
"<%v>%v</>\n"
|
||||
" %v %v\n",
|
||||
col,
|
||||
sub_part,
|
||||
file_part,
|
||||
last_part
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
os_init();
|
||||
|
||||
arena_t arena = arena_make(ARENA_VIRTUAL, GB(1));
|
||||
|
||||
if (argc < 2) {
|
||||
print_help_message();
|
||||
}
|
||||
|
||||
options_t opt = parse_options(&arena, argc, argv);
|
||||
|
||||
if (!os_dir_exists(strv("build/"))) {
|
||||
info("creating build folder");
|
||||
_mkdir("build");
|
||||
}
|
||||
|
||||
if (!os_file_exists(strv("build/cache.ini"))) {
|
||||
info("couldn't find cache.ini, creating it now");
|
||||
|
||||
arena_t scratch = arena;
|
||||
str_t vcvars_path = find_vcvars_path(&scratch);
|
||||
|
||||
os_cmd_t *cmd = NULL;
|
||||
darr_push(&scratch, cmd, strv(vcvars_path));
|
||||
darr_push(&scratch, cmd, strv("&&"));
|
||||
darr_push(&scratch, cmd, strv("set"));
|
||||
darr_push(&scratch, cmd, strv(">"));
|
||||
darr_push(&scratch, cmd, strv("build\\cache.ini"));
|
||||
|
||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
||||
fatal("failed to run vcvars64.bat");
|
||||
os_abort(1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
arena_t scratch = arena;
|
||||
|
||||
if (!load_cache(&scratch)) {
|
||||
os_abort(1);
|
||||
}
|
||||
|
||||
os_cmd_t *cmd = NULL;
|
||||
|
||||
darr_push(&scratch, cmd, strv("cl"));
|
||||
darr_push(&scratch, cmd, strv("/nologo"));
|
||||
darr_push(&scratch, cmd, strv("/utf-8"));
|
||||
if (!opt.is_cpp) {
|
||||
darr_push(&scratch, cmd, strv("/TC"));
|
||||
}
|
||||
|
||||
str_t output = str_fmt(&scratch, "/Fe:%v.exe", opt.out_fname);
|
||||
str_t object = str_fmt(&scratch, "/Fo:%v.obj", opt.out_fname);
|
||||
darr_push(&scratch, cmd, strv(output));
|
||||
darr_push(&scratch, cmd, strv(object));
|
||||
|
||||
strview_t optimisations[OPTIMISE__COUNT] = {
|
||||
strv("/Od"), // disabled
|
||||
strv("/O2"), // fast code
|
||||
strv("/O1"), // small code
|
||||
};
|
||||
darr_push(&scratch, cmd, optimisations[opt.optimisation]);
|
||||
|
||||
strview_t warnings[WARNING__COUNT] = {
|
||||
strv("/W0"),
|
||||
strv("/W3"),
|
||||
strv("/W4"),
|
||||
};
|
||||
darr_push(&scratch, cmd, warnings[opt.warnings]);
|
||||
|
||||
if (opt.warnings_as_error) {
|
||||
darr_push(&scratch, cmd, strv("/WX"));
|
||||
}
|
||||
|
||||
if (opt.sanitiser) {
|
||||
strview_t sanitisers[SANITISER__COUNT] = {
|
||||
strv(""),
|
||||
strv("/fsanitize=address"),
|
||||
};
|
||||
darr_push(&scratch, cmd, sanitisers[opt.sanitiser]);
|
||||
}
|
||||
|
||||
if (opt.fast_math) {
|
||||
darr_push(&scratch, cmd, strv("/fp:fast"));
|
||||
}
|
||||
|
||||
if (opt.debug) {
|
||||
darr_push(&scratch, cmd, strv("/Zi"));
|
||||
darr_push(&scratch, cmd, strv("/D_DEBUG"));
|
||||
}
|
||||
|
||||
for_each (def, opt.defines) {
|
||||
for (int i = 0; i < def->count; ++i) {
|
||||
str_t define = str_fmt(&scratch, "/D%v", def->items[i]);
|
||||
darr_push(&scratch, cmd, strv(define));
|
||||
}
|
||||
}
|
||||
|
||||
strview_t cversion[CVERSION__COUNT] = {
|
||||
strv("clatest"),
|
||||
strv("c17"),
|
||||
strv("c11"),
|
||||
};
|
||||
|
||||
str_t cstd = str_fmt(&scratch, "/std:%v", cversion[opt.cstd]);
|
||||
darr_push(&scratch, cmd, strv(cstd));
|
||||
|
||||
darr_push(&scratch, cmd, opt.input_fname);
|
||||
|
||||
// /LD -> create dynamic lib
|
||||
// /LDd -> create debug dynamic lib
|
||||
// /link
|
||||
|
||||
oshandle_t hout = os_handle_zero();
|
||||
|
||||
bool compilation_result = os_run_cmd(
|
||||
scratch,
|
||||
cmd,
|
||||
&(os_cmd_options_t){ .out = &hout, }
|
||||
);
|
||||
|
||||
str_t result = os_file_read_all_str_fp(&scratch, hout);
|
||||
|
||||
instream_t in = istr_init(strv(result));
|
||||
|
||||
while (!istr_is_finished(&in)) {
|
||||
strview_t line = istr_get_line(&in);
|
||||
if (strv_contains_view(line, strv("error"))) {
|
||||
cl_print_line(scratch, line, strv("error"), strv("red"));
|
||||
}
|
||||
else if (strv_contains_view(line, strv("warning"))) {
|
||||
cl_print_line(scratch, line, strv("warning"), strv("yellow"));
|
||||
}
|
||||
else {
|
||||
if (compilation_result) {
|
||||
pretty_print(scratch, "<green>compiled:</> %v\n", line);
|
||||
}
|
||||
else {
|
||||
pretty_print(scratch, "<blue>while compiling:</> %v\n", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!compilation_result) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.run) {
|
||||
arena_t scratch = arena;
|
||||
os_cmd_t *cmd = NULL;
|
||||
|
||||
darr_push(&scratch, cmd, opt.out_fname);
|
||||
|
||||
for_each (arg, opt.run_args) {
|
||||
for (int i = 0; i < arg->count; ++i) {
|
||||
darr_push(&scratch, cmd, arg->items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
arena_cleanup(&arena);
|
||||
|
||||
os_cleanup();
|
||||
}
|
||||
308
tools/noblin.c
Normal file
308
tools/noblin.c
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#define COLLA_NO_CONDITION_VARIABLE 1
|
||||
#define COLLA_NO_NET 1
|
||||
|
||||
#include "../colla.c"
|
||||
|
||||
typedef enum optimise_level_e {
|
||||
OPTIMISE_NONE,
|
||||
OPTIMISE_FAST,
|
||||
OPTIMISE_SMALL,
|
||||
OPTIMISE__COUNT,
|
||||
} optimise_level_e;
|
||||
|
||||
typedef enum warning_level_e {
|
||||
WARNING_NONE,
|
||||
WARNING_DEFAULT,
|
||||
WARNING_ALL,
|
||||
WARNING__COUNT,
|
||||
} warning_level_e;
|
||||
|
||||
typedef enum sanitiser_e {
|
||||
SANITISER_NONE,
|
||||
SANITISER_ADDRESS,
|
||||
SANITISER__COUNT,
|
||||
} sanitiser_e;
|
||||
|
||||
typedef enum cversion_e {
|
||||
CVERSION_LATEST,
|
||||
CVERSION_17,
|
||||
CVERSION_11,
|
||||
CVERSION__COUNT
|
||||
} cversion_e;
|
||||
|
||||
typedef struct options_t options_t;
|
||||
struct options_t {
|
||||
strview_t input_fname;
|
||||
strview_t out_fname;
|
||||
optimise_level_e optimisation;
|
||||
warning_level_e warnings;
|
||||
bool warnings_as_error;
|
||||
sanitiser_e sanitiser;
|
||||
bool fast_math;
|
||||
bool debug;
|
||||
strv_list_t *defines;
|
||||
cversion_e cstd;
|
||||
bool run;
|
||||
strv_list_t *run_args;
|
||||
bool is_cpp;
|
||||
};
|
||||
|
||||
void print_help_message(void) {
|
||||
puts("usage:");
|
||||
puts(" -r / -run [input.c] [args...] compiles and runs <input.c>, forwards <args...>");
|
||||
puts(" -h / -help print this message");
|
||||
puts(" -o / -out [filename] output filename (default: build/<file>.exe)");
|
||||
puts(" -O / -optimise [fast,small] optimisation level");
|
||||
puts(" -w / -warning [default,all] warning level");
|
||||
puts(" -werror treat warnings as errors");
|
||||
puts(" -fsanitize [address] turn on sanitiser");
|
||||
puts(" -fastmath turn on fast math");
|
||||
puts(" -g / -debug generate debug information");
|
||||
puts(" -D / -define [key=value,key] add a preprocessor define ");
|
||||
puts(" -std [c11,c17,clatest] select c standard (default: clatest)");
|
||||
puts(" -cpp compile c++ instead of c");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
optimise_level_e get_optimisation_level(strview_t arg) {
|
||||
if (strv_equals(arg, strv("fast"))) {
|
||||
return OPTIMISE_FAST;
|
||||
}
|
||||
else if (strv_equals(arg, strv("small"))) {
|
||||
return OPTIMISE_SMALL;
|
||||
}
|
||||
warn("unrecognised optimisation level: (%v)", arg);
|
||||
return OPTIMISE_NONE;
|
||||
}
|
||||
|
||||
warning_level_e get_warning_level(strview_t arg) {
|
||||
if (strv_equals(arg, strv("default"))) {
|
||||
return WARNING_DEFAULT;
|
||||
}
|
||||
else if (strv_equals(arg, strv("all"))) {
|
||||
return WARNING_ALL;
|
||||
}
|
||||
warn("unrecognised warning level: (%v)", arg);
|
||||
return WARNING_NONE;
|
||||
}
|
||||
|
||||
sanitiser_e get_sanitiser(strview_t arg) {
|
||||
if (strv_equals(arg, strv("address"))) {
|
||||
return SANITISER_ADDRESS;
|
||||
}
|
||||
warn("unrecognised sanitiser: (%v)", arg);
|
||||
return SANITISER_NONE;
|
||||
}
|
||||
|
||||
cversion_e get_cversion(strview_t arg) {
|
||||
if (strv_equals(arg, strv("clatest"))) {
|
||||
return CVERSION_LATEST;
|
||||
}
|
||||
else if (strv_equals(arg, strv("c17"))) {
|
||||
return CVERSION_17;
|
||||
}
|
||||
else if (strv_equals(arg, strv("c11"))) {
|
||||
return CVERSION_11;
|
||||
}
|
||||
warn("unrecognised c std version: (%v)", arg);
|
||||
return CVERSION_LATEST;
|
||||
}
|
||||
|
||||
options_t parse_options(arena_t *arena, int argc, char **argv) {
|
||||
options_t out = {0};
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
strview_t arg = strv(argv[i]);
|
||||
|
||||
#define CHECK_OPT_BEG() if (false) {}
|
||||
#define CHECK_OPT1(opt) else if (strv_equals(arg, strv("-" opt)))
|
||||
#define CHECK_OPT2(small, big) else if (strv_equals(arg, strv("-" small)) || strv_equals(arg, strv("-" big)))
|
||||
|
||||
#define GET_NEXT_ARG() (i + 1) < argc ? strv(argv[++i]) : STRV_EMPTY
|
||||
|
||||
CHECK_OPT_BEG()
|
||||
CHECK_OPT2("h", "help") {
|
||||
print_help_message();
|
||||
}
|
||||
CHECK_OPT2("o", "out") {
|
||||
strview_t out_fname = GET_NEXT_ARG();
|
||||
str_t out_fname_str = str_fmt(arena, "build/%v", out_fname);
|
||||
out.out_fname = strv(out_fname_str);
|
||||
}
|
||||
CHECK_OPT2("O", "optimise") {
|
||||
out.optimisation = get_optimisation_level(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT2("w", "warning") {
|
||||
out.warnings = get_warning_level(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("werror") {
|
||||
out.warnings_as_error = true;
|
||||
}
|
||||
CHECK_OPT1("fsanitize") {
|
||||
out.sanitiser = get_sanitiser(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("fastmath") {
|
||||
out.fast_math = true;
|
||||
}
|
||||
CHECK_OPT2("g", "debug") {
|
||||
out.debug = true;
|
||||
}
|
||||
CHECK_OPT2("D", "define") {
|
||||
darr_push(arena, out.defines, GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("std") {
|
||||
out.cstd = get_cversion(GET_NEXT_ARG());
|
||||
}
|
||||
CHECK_OPT1("cpp") {
|
||||
out.is_cpp = true;
|
||||
}
|
||||
CHECK_OPT2("r", "run") {
|
||||
out.run = true;
|
||||
out.input_fname = GET_NEXT_ARG();
|
||||
for (i += 1; i < argc; ++i) {
|
||||
darr_push(arena, out.run_args, strv(argv[i]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.input_fname = arg;
|
||||
}
|
||||
}
|
||||
|
||||
#undef CHECK_OPT_BEG
|
||||
#undef CHECK_OPT1
|
||||
#undef CHECK_OPT2
|
||||
#undef GET_NEXT_ARG
|
||||
|
||||
if (strv_is_empty(out.out_fname)) {
|
||||
strview_t name;
|
||||
os_file_split_path(out.input_fname, NULL, &name, NULL);
|
||||
str_t out_fname = str_fmt(arena, "build\\%v", name);
|
||||
out.out_fname = strv(out_fname);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
os_init();
|
||||
|
||||
arena_t arena = arena_make(ARENA_VIRTUAL, GB(1));
|
||||
|
||||
if (argc < 2) {
|
||||
print_help_message();
|
||||
}
|
||||
|
||||
options_t opt = parse_options(&arena, argc, argv);
|
||||
|
||||
if (!os_dir_exists(strv("build/"))) {
|
||||
info("creating build folder");
|
||||
mkdir("build", 755);
|
||||
}
|
||||
|
||||
{
|
||||
arena_t scratch = arena;
|
||||
os_cmd_t *cmd = NULL;
|
||||
|
||||
if (opt.is_cpp) {
|
||||
darr_push(&scratch, cmd, strv("c++"));
|
||||
}
|
||||
else {
|
||||
darr_push(&scratch, cmd, strv("cc"));
|
||||
}
|
||||
|
||||
str_t output = str_fmt(&scratch, "-o %v", opt.out_fname);
|
||||
darr_push(&scratch, cmd, strv(output));
|
||||
|
||||
strview_t optimisations[OPTIMISE__COUNT] = {
|
||||
strv("-O0"), // disabled
|
||||
strv("-O3"), // fast code
|
||||
strv("-Os"), // small code
|
||||
};
|
||||
darr_push(&scratch, cmd, optimisations[opt.optimisation]);
|
||||
|
||||
strview_t warnings[WARNING__COUNT] = {
|
||||
strv("-w"),
|
||||
strv("-Weverything"),
|
||||
strv("-Wpedantic"),
|
||||
};
|
||||
darr_push(&scratch, cmd, warnings[opt.warnings]);
|
||||
|
||||
if (opt.warnings_as_error) {
|
||||
darr_push(&scratch, cmd, strv("-Werror"));
|
||||
}
|
||||
|
||||
if (opt.sanitiser) {
|
||||
strview_t sanitisers[SANITISER__COUNT] = {
|
||||
strv(""),
|
||||
strv("-fsanitize=address"),
|
||||
};
|
||||
darr_push(&scratch, cmd, sanitisers[opt.sanitiser]);
|
||||
}
|
||||
|
||||
if (opt.fast_math) {
|
||||
darr_push(&scratch, cmd, strv("-ffast-math"));
|
||||
}
|
||||
|
||||
if (opt.debug) {
|
||||
darr_push(&scratch, cmd, strv("-g"));
|
||||
darr_push(&scratch, cmd, strv("-D_DEBUG"));
|
||||
}
|
||||
|
||||
for_each (def, opt.defines) {
|
||||
for (int i = 0; i < def->count; ++i) {
|
||||
str_t define = str_fmt(&scratch, "-D%v", def->items[i]);
|
||||
darr_push(&scratch, cmd, strv(define));
|
||||
}
|
||||
}
|
||||
|
||||
strview_t cversion[CVERSION__COUNT] = {
|
||||
strv("c23"),
|
||||
strv("c17"),
|
||||
strv("c11"),
|
||||
};
|
||||
|
||||
str_t cstd = str_fmt(&scratch, "-std %v", cversion[opt.cstd]);
|
||||
darr_push(&scratch, cmd, strv(cstd));
|
||||
|
||||
darr_push(&scratch, cmd, opt.input_fname);
|
||||
|
||||
oshandle_t hout = os_handle_zero();
|
||||
|
||||
bool compilation_result = os_run_cmd(
|
||||
scratch,
|
||||
cmd,
|
||||
&(os_cmd_options_t){ .out = &hout, }
|
||||
);
|
||||
|
||||
str_t result = os_file_read_all_str_fp(&scratch, hout);
|
||||
|
||||
info("result:\n%v\n", result);
|
||||
|
||||
if (!compilation_result) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.run) {
|
||||
arena_t scratch = arena;
|
||||
os_cmd_t *cmd = NULL;
|
||||
|
||||
darr_push(&scratch, cmd, opt.out_fname);
|
||||
|
||||
for_each (arg, opt.run_args) {
|
||||
for (int i = 0; i < arg->count; ++i) {
|
||||
darr_push(&scratch, cmd, arg->items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
arena_cleanup(&arena);
|
||||
|
||||
os_cleanup();
|
||||
|
||||
}
|
||||
82
tools/unit_tests.c
Normal file
82
tools/unit_tests.c
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "../colla.c"
|
||||
|
||||
#if COLLA_WIN
|
||||
#pragma section(".CRT$XCU", read)
|
||||
#endif
|
||||
|
||||
#include "../tests/runner.h"
|
||||
|
||||
#include "../tests/arena_tests.c"
|
||||
#include "../tests/core_tests.c"
|
||||
#include "../tests/net_tests.c"
|
||||
#include "../tests/os_tests.c"
|
||||
// #include "../tests/parsers_tests.c"
|
||||
// #include "../tests/pretty_print_tests.c"
|
||||
#include "../tests/str_tests.c"
|
||||
|
||||
unit_test_t *test_head = NULL;
|
||||
unit_test_t *test_tail = NULL;
|
||||
const char *last_fail_reason = NULL;
|
||||
bool last_failed = false;
|
||||
|
||||
void ut_register(const char *file, const char *name, void (*fn)(void)) {
|
||||
strview_t fname;
|
||||
os_file_split_path(strv(file), NULL, &fname, NULL);
|
||||
|
||||
fname = strv_remove_suffix(fname, arrlen("_tests") - 1);
|
||||
|
||||
unit_test_t *test = calloc(1, sizeof(unit_test_t));
|
||||
test->name = strv(name);
|
||||
test->fn = fn;
|
||||
test->fname = fname;
|
||||
|
||||
olist_push(test_head, test_tail, test);
|
||||
}
|
||||
|
||||
int main() {
|
||||
colla_init(COLLA_ALL);
|
||||
arena_t arena = arena_make(ARENA_VIRTUAL, GB(1));
|
||||
|
||||
strview_t last_file = STRV_EMPTY;
|
||||
int success = 0;
|
||||
int total = 0;
|
||||
|
||||
unit_test_t *test = test_head;
|
||||
while (test) {
|
||||
if (!strv_equals(test->fname, last_file)) {
|
||||
last_file = test->fname;
|
||||
pretty_print(arena, "<blue>> %v</>\n", test->fname);
|
||||
}
|
||||
|
||||
test->fn();
|
||||
|
||||
total++;
|
||||
|
||||
if (last_failed) {
|
||||
pretty_print(arena, "%4s<red>[X]</> %v: %s\n", "", test->name, last_fail_reason);
|
||||
}
|
||||
else {
|
||||
pretty_print(arena, "%4s<green>[V]</> %v\n", "", test->name);
|
||||
success++;
|
||||
}
|
||||
|
||||
last_failed = false;
|
||||
|
||||
test = test->next;
|
||||
}
|
||||
|
||||
print("\n");
|
||||
|
||||
strview_t colors[] = {
|
||||
cstrv("red"),
|
||||
cstrv("light_red"),
|
||||
cstrv("yellow"),
|
||||
cstrv("light_yellow"),
|
||||
cstrv("light_green"),
|
||||
cstrv("green"),
|
||||
};
|
||||
|
||||
usize col = success * (arrlen(colors) - 1) / total;
|
||||
|
||||
pretty_print(arena, "<%v>%d</>/<blue>%d</> tests passed\n", colors[col], success, total);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue