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

136
arena.c
View file

@ -1,8 +1,11 @@
#include "arena.h" #include "arena.h"
#include <assert.h>
#include <string.h> #include <string.h>
#if COLLA_DEBUG
#include <stdlib.h>
#endif
#include "os.h" #include "os.h"
static uptr arena__align(uptr ptr, usize align) { static uptr arena__align(uptr ptr, usize align) {
@ -26,11 +29,17 @@ arena_t malloc_arena = {
arena_t arena_init(const arena_desc_t *desc) { arena_t arena_init(const arena_desc_t *desc) {
arena_t out = {0}; arena_t out = {0};
#if COLLA_DEBUG
out.file = desc->file;
out.line = desc->line;
#endif
if (desc) { if (desc) {
switch (desc->type) { switch (desc->type) {
case ARENA_VIRTUAL: out = arena__make_virtual(desc->size); break; case ARENA_VIRTUAL: out = arena__make_virtual(desc->size); break;
case ARENA_MALLOC: out = arena__make_malloc(desc->size); break; case ARENA_MALLOC: out = arena__make_malloc(desc->size); break;
case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->size); break; case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->size); break;
case ARENA_MALLOC_ALWAYS: out = malloc_arena; break;
default: break; default: break;
} }
} }
@ -76,6 +85,10 @@ void *arena_alloc(const arena_alloc_desc_t *desc) {
break; break;
} }
if (!ptr && desc->flags & ALLOC_SOFT_FAIL) {
return NULL;
}
usize total = desc->size * desc->count; usize total = desc->size * desc->count;
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total); return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
@ -98,7 +111,7 @@ void arena_rewind(arena_t *arena, usize from_start) {
return; return;
} }
assert(arena_tell(arena) >= from_start); colla_assert(arena_tell(arena) >= from_start);
arena->cur = arena->beg + from_start; arena->cur = arena->beg + from_start;
} }
@ -137,17 +150,13 @@ static void arena__free_virtual(arena_t *arena) {
return; return;
} }
bool success = os_release(arena->beg, arena_capacity(arena)); os_release(arena->beg, arena_capacity(arena));
assert(success && "Failed arena free");
} }
// == MALLOC ARENA ===================================================================================================== // == MALLOC ARENA =====================================================================================================
extern void *malloc(usize size);
extern void free(void *ptr);
static arena_t arena__make_malloc(usize size) { static arena_t arena__make_malloc(usize size) {
u8 *ptr = malloc(size); u8 *ptr = os_alloc(size);
assert(ptr); colla_assert(ptr);
return (arena_t) { return (arena_t) {
.beg = ptr, .beg = ptr,
.cur = ptr, .cur = ptr,
@ -157,7 +166,7 @@ static arena_t arena__make_malloc(usize size) {
} }
static void arena__free_malloc(arena_t *arena) { static void arena__free_malloc(arena_t *arena) {
free(arena->beg); os_free(arena->beg);
} }
// == ARENA ALLOC ====================================================================================================== // == ARENA ALLOC ======================================================================================================
@ -171,7 +180,10 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
if (total > arena_remaining(arena)) { if (total > arena_remaining(arena)) {
if (!soft_fail) { if (!soft_fail) {
fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB\n", total, arena_remaining(arena)); #if COLLA_DEBUG
arena__print_crash(arena);
#endif
fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB (total: %_$$$dB)\n", total, arena_remaining(arena), (usize)(arena->end - arena->beg));
} }
return NULL; return NULL;
} }
@ -187,10 +199,13 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
// TODO is this really correct? // TODO is this really correct?
usize num_of_pages = (extra_mem / page_size) + 1; usize num_of_pages = (extra_mem / page_size) + 1;
assert(num_of_pages > 0); colla_assert(num_of_pages > 0);
if (!os_commit(arena->cur, num_of_pages + 1)) { if (!os_commit(arena->cur, num_of_pages + 1)) {
if (!soft_fail) { if (!soft_fail) {
#if COLLA_DEBUG
arena__print_crash(arena);
#endif
fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages); fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
} }
return NULL; return NULL;
@ -201,6 +216,14 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
u8 *ptr = arena->cur; u8 *ptr = arena->cur;
arena->cur += total; arena->cur += total;
#if COLLA_DEBUG
alloc_header_t *header = calloc(1, sizeof(alloc_header_t));
header->size = desc->size;
header->count = desc->count;
memcpy(header->type_name, desc->type_name.buf, MIN(desc->type_name.len, sizeof(header->type_name)));
list_push(arena->head, header);
#endif
return ptr; return ptr;
} }
@ -208,9 +231,9 @@ static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) {
usize total = desc->size * desc->count; usize total = desc->size * desc->count;
// TODO: alignment? // TODO: alignment?
u8 *ptr = malloc(total); u8 *ptr = os_alloc(total);
if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) { if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) {
fatal("malloc call failed for %_$$$dB", total); fatal("alloc call failed for %_$$$dB", total);
} }
return ptr; return ptr;
@ -228,3 +251,82 @@ static arena_t arena__make_static(u8 *buf, usize len) {
}; };
} }
// == DEBUG ============================================================================================================
#if COLLA_DEBUG
#define ARENA_HMAP_SIZE (1024)
typedef struct arena_hnode_t arena_hnode_t;
struct arena_hnode_t {
u32 size;
u32 count;
};
typedef struct arena_hmap_t arena_hmap_t;
struct arena_hmap_t {
u64 hashes[ARENA_HMAP_SIZE];
char keys[ARENA_HMAP_SIZE][16];
arena_hnode_t values[ARENA_HMAP_SIZE];
};
u64 arena_hmap_hash(char key[16]) {
const u8 *data = (const u8 *)key;
u64 hash = 0;
for (usize i = 0; i < 16; ++i) {
hash = data[i] + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
arena_hnode_t *arena_hmap_find_or_add(arena_hmap_t *map, char key[16]) {
u64 hash = arena_hmap_hash(key);
usize index = hash & (ARENA_HMAP_SIZE - 1);
usize beg = index;
usize end = ARENA_HMAP_SIZE;
for (usize k = 0; k < 2; ++k) {
for (usize i = index; i < ARENA_HMAP_SIZE; ++i) {
if (map->hashes[i] == 0){
arena_hnode_t *node = &map->values[i];
map->hashes[i] = hash;
memcpy(map->keys[i], key, 16);
return node;
}
if (map->hashes[i] == hash && memcmp(map->keys[i], key, 16) == 0) {
return &map->values[i];
}
}
beg = 0;
end = index;
}
return NULL;
}
void arena__print_crash(arena_t *arena) {
arena_hmap_t hmap = {0};
debug("arena %v:%d", arena->file, arena->line);
for_each (header, arena->head) {
arena_hnode_t *node = arena_hmap_find_or_add(&hmap, header->type_name);
colla_assert(node->size == 0 || node->size == header->size);
node->size = header->size;
node->count += header->count;
}
print("type name | size\t| count\t| total\n");
for (usize i = 0; i < ARENA_HMAP_SIZE; ++i) {
if (hmap.hashes[i] == 0) continue;
arena_hnode_t n = hmap.values[i];
print("%16s| %_$$$dB\t| %d\t| %_$$$dB\n", hmap.keys[i], n.size, n.count, n.size * n.count);
}
}
#endif

35
arena.h
View file

@ -3,6 +3,10 @@
#include "core.h" #include "core.h"
#if COLLA_DEBUG
#include "str.h"
#endif
#if COLLA_WIN && !COLLA_TCC #if COLLA_WIN && !COLLA_TCC
#define alignof __alignof #define alignof __alignof
#endif #endif
@ -21,16 +25,36 @@ typedef enum alloc_flags_e {
ALLOC_SOFT_FAIL = 1 << 1, ALLOC_SOFT_FAIL = 1 << 1,
} alloc_flags_e; } alloc_flags_e;
#if COLLA_DEBUG
typedef struct alloc_header_t alloc_header_t;
struct alloc_header_t {
char type_name[16];
u32 size;
u32 count;
alloc_header_t *next;
};
void arena__print_crash(arena_t *arena);
#endif
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
struct arena_t { struct arena_t {
u8 *beg; u8 *beg;
u8 *cur; u8 *cur;
u8 *end; u8 *end;
arena_type_e type; arena_type_e type;
#if COLLA_DEBUG
strview_t file;
int line;
alloc_header_t *head;
#endif
}; };
typedef struct arena_desc_t arena_desc_t; typedef struct arena_desc_t arena_desc_t;
struct arena_desc_t { struct arena_desc_t {
#if COLLA_DEBUG
strview_t file;
int line;
#endif
arena_type_e type; arena_type_e type;
usize size; usize size;
u8 *static_buffer; u8 *static_buffer;
@ -38,6 +62,9 @@ struct arena_desc_t {
typedef struct arena_alloc_desc_t arena_alloc_desc_t; typedef struct arena_alloc_desc_t arena_alloc_desc_t;
struct arena_alloc_desc_t { struct arena_alloc_desc_t {
#if COLLA_DEBUG
strview_t type_name;
#endif
arena_t *arena; arena_t *arena;
usize count; usize count;
alloc_flags_e flags; alloc_flags_e flags;
@ -46,10 +73,18 @@ struct arena_alloc_desc_t {
}; };
// arena_type_e type, usize allocation, [ byte *static_buffer ] // arena_type_e type, usize allocation, [ byte *static_buffer ]
#if !COLLA_DEBUG
#define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ }) #define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ })
#else
#define arena_make(...) arena_init(&(arena_desc_t){ strv(__FILE__), __LINE__, __VA_ARGS__ })
#endif
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ] // arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ]
#if !COLLA_DEBUG
#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) #define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
#else
#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .type_name = strv(#type), .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
#endif
// simple arena that always calls malloc internally, this is useful if you need // simple arena that always calls malloc internally, this is useful if you need
// malloc for some reason but want to still use the arena interface // malloc for some reason but want to still use the arena interface

42
core.h
View file

@ -20,21 +20,6 @@ void colla_cleanup(void);
///////////////////////////////////////////////// /////////////////////////////////////////////////
// USEFUL MACROS ////////////////////////////////
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
#define COLLA_UNUSED(v) (void)(v)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define KB(n) (((u64)n) << 10)
#define MB(n) (((u64)n) << 20)
#define GB(n) (((u64)n) << 30)
#define TB(n) (((u64)n) << 40)
/////////////////////////////////////////////////
// LINKED LISTS ///////////////////////////////// // LINKED LISTS /////////////////////////////////
// singly linked list // singly linked list
@ -73,7 +58,7 @@ void colla_cleanup(void);
// OS AND COMPILER MACROS /////////////////////// // OS AND COMPILER MACROS ///////////////////////
#if defined(_DEBUG) || !defined(NDEBUG) #if defined(_DEBUG)
#define COLLA_DEBUG 1 #define COLLA_DEBUG 1
#define COLLA_RELEASE 0 #define COLLA_RELEASE 0
#else #else
@ -163,6 +148,31 @@ void colla_cleanup(void);
///////////////////////////////////////////////// /////////////////////////////////////////////////
// USEFUL MACROS ////////////////////////////////
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
#define COLLA_UNUSED(v) (void)(v)
#define COLLA__STRINGIFY(x) #x
#define COLLA_STRINGIFY(x) COLLA__STRINGIFY(x)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define KB(n) (((u64)n) << 10)
#define MB(n) (((u64)n) << 20)
#define GB(n) (((u64)n) << 30)
#define TB(n) (((u64)n) << 40)
#if COLLA_DEBUG
#define colla__assert(file, line, cond, ...) do { if (!(cond)) fatal(file ":" line " ASSERT FAILED: (" COLLA__STRINGIFY(cond) ") " __VA_ARGS__); } while (0)
#define colla_assert(...) colla__assert(__FILE__, COLLA__STRINGIFY(__LINE__), __VA_ARGS__)
#else
#define colla_assert(...) (void)0
#endif
/////////////////////////////////////////////////
// BASIC TYPES ////////////////////////////////// // BASIC TYPES //////////////////////////////////
#if COLLA_WIN && COLLA_UNICODE #if COLLA_WIN && COLLA_UNICODE

27
net.c
View file

@ -1,6 +1,8 @@
#include "net.h" #include "net.h"
#include "arena.h" #include "arena.h"
#include <stdio.h> // sscanf
#if COLLA_WIN #if COLLA_WIN
#include "win/net_win32.c" #include "win/net_win32.c"
#else #else
@ -38,7 +40,7 @@ const char *http_get_status_string(int status) {
case 404: return "NOT FOUND"; case 404: return "NOT FOUND";
case 407: return "RANGE NOT SATISFIABLE"; case 407: return "RANGE NOT SATISFIABLE";
case 500: return "INTERNAL SERVER_ERROR"; case 500: return "INTERNAL SERVER ERROR";
case 501: return "NOT IMPLEMENTED"; case 501: return "NOT IMPLEMENTED";
case 502: return "BAD GATEWAY"; case 502: return "BAD GATEWAY";
case 503: return "SERVICE NOT AVAILABLE"; case 503: return "SERVICE NOT AVAILABLE";
@ -55,6 +57,11 @@ http_header_t *http__parse_headers_instream(arena_t *arena, instream_t *in) {
while (!istr_is_finished(in)) { while (!istr_is_finished(in)) {
strview_t line = istr_get_line(in); strview_t line = istr_get_line(in);
// end of headers
if (strv_is_empty(line)) {
break;
}
usize pos = strv_find(line, ':', 0); usize pos = strv_find(line, ':', 0);
if (pos != STR_NONE) { if (pos != STR_NONE) {
http_header_t *new_head = alloc(arena, http_header_t); http_header_t *new_head = alloc(arena, http_header_t);
@ -112,7 +119,7 @@ http_res_t http_parse_res(arena_t *arena, strview_t response) {
http_res_t res = {0}; http_res_t res = {0};
instream_t in = istr_init(response); instream_t in = istr_init(response);
strview_t http = istr_get_view_len(&in, 5); strview_t http = istr_get_view_len(&in, 4);
if (!strv_equals(http, strv("HTTP"))) { if (!strv_equals(http, strv("HTTP"))) {
err("response doesn't start with 'HTTP', instead with %v", http); err("response doesn't start with 'HTTP', instead with %v", http);
return (http_res_t){0}; return (http_res_t){0};
@ -258,7 +265,7 @@ str_t http_decode_url_safe(arena_t *arena, strview_t string) {
} }
} }
assert(final_len <= string.len); colla_assert(final_len <= string.len);
str_t out = { str_t out = {
.buf = alloc(arena, char, final_len + 1), .buf = alloc(arena, char, final_len + 1),
@ -531,7 +538,7 @@ sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len) {
return result; return result;
} }
str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) { str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) {
sha1_result_t result = sha1(ctx, buf, len); sha1_result_t result = sha1(ctx, buf, len);
return str_fmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]); return str_fmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]);
} }
@ -623,10 +630,20 @@ buffer_t base64_decode(arena_t *arena, buffer_t buffer) {
bytes[2] = (triple >> 0) & 0xFF; bytes[2] = (triple >> 0) & 0xFF;
} }
usize spaces_count = 0;
for (isize i = buffer.len - 1; i >= 0; --i) {
if (buffer.data[i] == '=') {
spaces_count++;
}
else {
break;
}
}
usize outlen = arena_tell(arena) - start; usize outlen = arena_tell(arena) - start;
return (buffer_t){ return (buffer_t){
.data = out, .data = out,
.len = outlen, .len = outlen - spaces_count,
}; };
} }

2
os.c
View file

@ -149,7 +149,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) { 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, FILEMODE_READ);
if (!os_handle_valid(fp)) { if (!os_handle_valid(fp)) {
err("could not open file %v", path); err("could not open file %v: %v", path, os_get_error_string(os_get_last_error()));
return STR_EMPTY; return STR_EMPTY;
} }
str_t out = os_file_read_all_str_fp(arena, fp); str_t out = os_file_read_all_str_fp(arena, fp);

6
os.h
View file

@ -118,6 +118,7 @@ bool os_dir_create(strview_t folder);
tstr_t os_file_fullpath(arena_t *arena, strview_t filename); tstr_t os_file_fullpath(arena_t *arena, strview_t filename);
void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
bool os_file_delete(strview_t path); bool os_file_delete(strview_t path);
bool os_dir_delete(strview_t path);
oshandle_t os_file_open(strview_t path, filemode_e mode); oshandle_t os_file_open(strview_t path, filemode_e mode);
void os_file_close(oshandle_t handle); void os_file_close(oshandle_t handle);
@ -193,7 +194,10 @@ bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env);
oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env); oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env);
bool os_process_wait(oshandle_t proc, uint time, int *out_exit); bool os_process_wait(oshandle_t proc, uint time, int *out_exit);
// == VMEM ====================================== // == MEMORY ====================================
void *os_alloc(usize size);
void os_free(void *ptr);
void *os_reserve(usize size, usize *out_padded_size); void *os_reserve(usize size, usize *out_padded_size);
bool os_commit(void *ptr, usize num_of_pages); bool os_commit(void *ptr, usize num_of_pages);

View file

@ -374,12 +374,12 @@ void json_pretty_print(json_t *root, const json_pretty_opts_t *options) {
} }
if (!default_options.use_custom_colours) { if (!default_options.use_custom_colours) {
os_log_colour_e default_col[JSON_PRETTY_COLOUR__COUNT] = { os_log_colour_e default_col[JSON_PRETTY_COLOUR__COUNT] = {
LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY, LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY,
LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING, LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING,
LOG_COL_BLUE, // JSON_PRETTY_COLOUR_NUM, LOG_COL_BLUE, // JSON_PRETTY_COLOUR_NUM,
LOG_COL_BLACK, // JSON_PRETTY_COLOUR_NULL, LOG_COL_DARK_GREY, // JSON_PRETTY_COLOUR_NULL,
LOG_COL_GREEN, // JSON_PRETTY_COLOUR_TRUE, LOG_COL_GREEN, // JSON_PRETTY_COLOUR_TRUE,
LOG_COL_RED, // JSON_PRETTY_COLOUR_FALSE, LOG_COL_RED, // JSON_PRETTY_COLOUR_FALSE,
}; };
memmove(default_options.colours, default_col, sizeof(default_col)); memmove(default_options.colours, default_col, sizeof(default_col));
} }

View file

@ -1,10 +1,8 @@
#include "pretty_print.h" #include "pretty_print.h"
#include <stdarg.h> #include <stdarg.h>
#include "core.h"
#include "os.h"
#include "str.h"
#include "core.h"
strview_t pretty__colour[LOG_COL__COUNT] = { strview_t pretty__colour[LOG_COL__COUNT] = {
[LOG_COL_BLACK] = cstrv("black"), [LOG_COL_BLACK] = cstrv("black"),
@ -28,6 +26,10 @@ strview_t pretty__colour[LOG_COL__COUNT] = {
[LOG_COL_RESET] = cstrv("/"), [LOG_COL_RESET] = cstrv("/"),
}; };
strview_t pretty_log_to_colour(os_log_colour_e colour) {
return pretty__colour[colour];
}
void pretty_print(arena_t scratch, const char *fmt, ...) { void pretty_print(arena_t scratch, const char *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);

View file

@ -1,14 +1,10 @@
#pragma once #pragma once
#if 0 #include "os.h"
pretty_print(arena,
"<red>error!</><blue>%s!</>", "wow");
#endif
#include "arena.h" #include "arena.h"
#include "str.h"
strview_t pretty_log_to_colour(os_log_colour_e colour);
void pretty_print(arena_t scratch, const char *fmt, ...); void pretty_print(arena_t scratch, const char *fmt, ...);
void pretty_printv(arena_t scratch, const char *fmt, va_list args); void pretty_printv(arena_t scratch, const char *fmt, va_list args);

12
str.c
View file

@ -1,5 +1,7 @@
#include "str.h" #include "str.h"
#include "os.h"
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <stdlib.h> #include <stdlib.h>
@ -396,7 +398,11 @@ bool char_is_num(char c) {
} }
char char_lower(char c) { char char_lower(char c) {
return c >= 'A' && c <= 'Z' ? c - 32 : c; return c >= 'A' && c <= 'Z' ? c + 32 : c;
}
char char_upper(char c) {
return c <= 'a' && c >= 'z' ? c - 32 : c;
} }
// == INPUT STREAM ================================================= // == INPUT STREAM =================================================
@ -657,9 +663,11 @@ char ostr_back(outstream_t *ctx) {
str_t ostr_to_str(outstream_t *ctx) { str_t ostr_to_str(outstream_t *ctx) {
ostr_putc(ctx, '\0'); ostr_putc(ctx, '\0');
usize len = ostr_tell(ctx);
str_t out = { str_t out = {
.buf = ctx->beg, .buf = ctx->beg,
.len = ostr_tell(ctx) - 1, .len = len ? len - 1 : 0,
}; };
memset(ctx, 0, sizeof(outstream_t)); memset(ctx, 0, sizeof(outstream_t));

3
str.h
View file

@ -1,8 +1,6 @@
#ifndef COLLA_STR_H #ifndef COLLA_STR_H
#define COLLA_STR_H #define COLLA_STR_H
#include <string.h> // strlen
#include "core.h" #include "core.h"
#include "darr.h" #include "darr.h"
@ -165,6 +163,7 @@ bool char_is_space(char c);
bool char_is_alpha(char c); bool char_is_alpha(char c);
bool char_is_num(char c); bool char_is_num(char c);
char char_lower(char c); char char_lower(char c);
char char_upper(char c);
// == INPUT STREAM ================================================= // == INPUT STREAM =================================================

237
tests/arena_tests.c Normal file
View file

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

229
tests/core_tests.c Normal file
View file

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

179
tests/highlight_tests.c Normal file
View file

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

238
tests/net_tests.c Normal file
View file

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

374
tests/os_tests.c Normal file
View file

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

370
tests/parsers_tests.c Normal file
View file

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

View file

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

41
tests/runner.h Normal file
View file

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

457
tests/str_tests.c Normal file
View file

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

12
tests/string_tests.c Normal file
View file

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

View file

@ -1,4 +1,5 @@
#define COLLA_NO_CONDITION_VARIABLE 1 #define COLLA_NO_CONDITION_VARIABLE 1
#define COLLA_NO_NET 1
#include "../build.c" #include "../build.c"
#include <windows.h> #include <windows.h>
@ -296,7 +297,7 @@ int main(int argc, char **argv) {
options_t opt = parse_options(&arena, argc, argv); options_t opt = parse_options(&arena, argc, argv);
if (!os_file_exists(strv("build/"))) { if (!os_dir_exists(strv("build/"))) {
info("creating build folder"); info("creating build folder");
_mkdir("build"); _mkdir("build");
} }
@ -307,7 +308,14 @@ int main(int argc, char **argv) {
arena_t scratch = arena; arena_t scratch = arena;
str_t vcvars_path = find_vcvars_path(&scratch); str_t vcvars_path = find_vcvars_path(&scratch);
if (!os_run_cmd(scratch, os_make_cmd(strv(vcvars_path), strv("&&"), strv("set"), strv(">"), strv("build\\cache.ini")), NULL)) { 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"); fatal("failed to run vcvars64.bat");
os_abort(1); os_abort(1);
} }
@ -366,6 +374,7 @@ int main(int argc, char **argv) {
if (opt.debug) { if (opt.debug) {
darr_push(&scratch, cmd, strv("/Zi")); darr_push(&scratch, cmd, strv("/Zi"));
darr_push(&scratch, cmd, strv("/D_DEBUG"));
} }
for_each (def, opt.defines) { for_each (def, opt.defines) {

80
tools/unit_tests.c Normal file
View file

@ -0,0 +1,80 @@
#pragma section(".CRT$XCU", read)
#include "../build.c"
#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 failed = 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);
}

View file

@ -48,6 +48,10 @@ iptr net_get_last_error(void) {
} }
http_res_t http_request(http_request_desc_t *req) { http_res_t http_request(http_request_desc_t *req) {
return http_request_cb(req, NULL, NULL);
}
http_res_t http_request_cb(http_request_desc_t *req, http_request_callback_fn callback, void *userdata) {
HINTERNET connection = NULL; HINTERNET connection = NULL;
HINTERNET request = NULL; HINTERNET request = NULL;
BOOL result = FALSE; BOOL result = FALSE;
@ -135,7 +139,14 @@ http_res_t http_request(http_request_desc_t *req) {
(DWORD)req->body.len (DWORD)req->body.len
); );
if (!result) { if (!result) {
err("call to HttpSendRequest failed: %v", os_get_error_string(os_get_last_error())); iptr error = os_get_last_error();
if (error == ERROR_INTERNET_NAME_NOT_RESOLVED) {
err("invalid url: %v", req->url);
}
else {
err("call to HttpSendRequest failed: %lld", error);
// os_get_error_string(error));
}
goto failed; goto failed;
} }
@ -186,7 +197,12 @@ http_res_t http_request(http_request_desc_t *req) {
if (!read_success || read == 0) { if (!read_success || read == 0) {
break; break;
} }
ostr_puts(&body, strv(read_buffer, read));
strview_t chunk = strv(read_buffer, read);
if (callback) {
callback(chunk, userdata);
}
ostr_puts(&body, chunk);
} }
res.body = strv(ostr_to_str(&body)); res.body = strv(ostr_to_str(&body));

View file

@ -1,6 +1,10 @@
#include <windows.h> #include <windows.h>
#include <assert.h> #include <assert.h>
#if COLLA_DEBUG
#include <stdlib.h> // abort
#endif
#include "../os.h" #include "../os.h"
#include "../net.h" #include "../net.h"
@ -37,6 +41,8 @@ struct {
os_entity_t *entity_free; os_entity_t *entity_free;
oshandle_t hstdout; oshandle_t hstdout;
oshandle_t hstdin; oshandle_t hstdin;
WORD default_fg;
WORD default_bg;
} w32_data = {0}; } w32_data = {0};
os_entity_t *os__win_alloc_entity(os_entity_kind_e kind) { os_entity_t *os__win_alloc_entity(os_entity_kind_e kind) {
@ -77,14 +83,23 @@ void os_init(void) {
info->machine_name = str_from_tstr(&w32_data.arena, (tstr_t){ namebuf, namebuflen}); info->machine_name = str_from_tstr(&w32_data.arena, (tstr_t){ namebuf, namebuflen});
HANDLE hstdout = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); HANDLE hstdout = CreateFile(TEXT("CONOUT$"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hstdin = CreateFile(TEXT("CONIN$"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); HANDLE hstdin = CreateFile(TEXT("CONIN$"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hstdout == INVALID_HANDLE_VALUE) err("couldn't open CONOUT$"); if (hstdout == INVALID_HANDLE_VALUE) err("couldn't open CONOUT$");
else w32_data.hstdout.data = (uptr)hstdout; else w32_data.hstdout.data = (uptr)hstdout;
if (hstdin == INVALID_HANDLE_VALUE) err("couldn't open CONIN$"); if (hstdin == INVALID_HANDLE_VALUE) err("couldn't open CONIN$");
else w32_data.hstdin.data = (uptr)hstdin; else w32_data.hstdin.data = (uptr)hstdin;
CONSOLE_SCREEN_BUFFER_INFO console_info = {0};
if (GetConsoleScreenBufferInfo(hstdout, &console_info)) {
w32_data.default_fg = console_info.wAttributes & 0x0F;
w32_data.default_bg = (console_info.wAttributes & 0xF0) >> 4;
}
else {
err("couldn't get console screen buffer info: %v", os_get_error_string(os_get_last_error()));
}
} }
void os_cleanup(void) { void os_cleanup(void) {
@ -95,6 +110,9 @@ void os_cleanup(void) {
} }
void os_abort(int code) { void os_abort(int code) {
#if COLLA_DEBUG
if (code != 0) abort();
#endif
ExitProcess(code); ExitProcess(code);
} }
@ -111,19 +129,21 @@ str_t os_get_error_string(iptr error) {
DWORD chars; DWORD chars;
chars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, msgbuf, arrlen(msgbuf), NULL); chars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, msgbuf, arrlen(msgbuf), NULL);
if (chars == 0) { if (chars == 0) {
err("FormatMessageW: %ld", os_get_last_error()); iptr fmt_error = os_get_last_error();
if (fmt_error == ERROR_MR_MID_NOT_FOUND) {
return str_fmt(&arena, "(unknown error: 0x%04x)", fmt_error);
}
err("FormatMessageW: %ld", fmt_error);
return STR_EMPTY; return STR_EMPTY;
} }
debug("error: %zi 0x%zX", error, error);
// remove \r\n at the end // remove \r\n at the end
return str_from_str16(&arena, str16_init(msgbuf, chars - 2)); return str_from_str16(&arena, str16_init(msgbuf, chars - 2));
} }
os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds) { os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds) {
HANDLE win_handles[OS_MAX_WAITABLE_HANDLES] = {0}; HANDLE win_handles[OS_MAX_WAITABLE_HANDLES] = {0};
assert(count < MAXIMUM_WAIT_OBJECTS); colla_assert(count < MAXIMUM_WAIT_OBJECTS);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
win_handles[i] = (HANDLE)(handles[i].data); win_handles[i] = (HANDLE)(handles[i].data);
@ -156,20 +176,16 @@ os_system_info_t os_get_system_info(void) {
} }
void os_log_set_colour(os_log_colour_e colour) { void os_log_set_colour(os_log_colour_e colour) {
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; WORD attribute = colour == LOG_COL_RESET ? w32_data.default_fg : (WORD)colour;
switch (colour) { SetConsoleTextAttribute((HANDLE)w32_data.hstdout.data, attribute);
case LOG_COL_BLACK: attribute = 0; break; }
case LOG_COL_RED: attribute = FOREGROUND_RED; break;
case LOG_COL_GREEN: attribute = FOREGROUND_GREEN; break;
case LOG_COL_BLUE: attribute = FOREGROUND_BLUE; break;
case LOG_COL_MAGENTA: attribute = FOREGROUND_RED | FOREGROUND_BLUE; break;
case LOG_COL_YELLOW: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
case LOG_COL_CYAN: attribute = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
default: break;
}
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background) {
SetConsoleTextAttribute(hc, attribute | FOREGROUND_INTENSITY); WORD fg_attr = foreground == LOG_COL_RESET ? w32_data.default_fg : (WORD)foreground;
WORD bg_attr = (background == LOG_COL_RESET ? w32_data.default_bg : (WORD)background) << 4;
WORD attribute = fg_attr | bg_attr;
SetConsoleTextAttribute((HANDLE)w32_data.hstdout.data, attribute);
} }
oshandle_t os_stdout(void) { oshandle_t os_stdout(void) {
@ -206,6 +222,26 @@ bool os_file_exists(strview_t path) {
return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY);
} }
bool os_dir_exists(strview_t folder) {
OS_SMALL_SCRATCH();
tstr_t name = strv_to_tstr(&scratch, folder);
DWORD attributes = GetFileAttributes(name.buf);
return attributes != INVALID_FILE_ATTRIBUTES && attributes & FILE_ATTRIBUTE_DIRECTORY;
}
bool os_file_or_dir_exists(strview_t path) {
OS_SMALL_SCRATCH();
tstr_t name = strv_to_tstr(&scratch, path);
DWORD attributes = GetFileAttributes(name.buf);
return attributes != INVALID_FILE_ATTRIBUTES;
}
bool os_dir_create(strview_t folder) {
OS_SMALL_SCRATCH();
tstr_t name = strv_to_tstr(&scratch, folder);
return CreateDirectory(name.buf, NULL);
}
tstr_t os_file_fullpath(arena_t *arena, strview_t filename) { tstr_t os_file_fullpath(arena_t *arena, strview_t filename) {
OS_SMALL_SCRATCH(); OS_SMALL_SCRATCH();
@ -232,6 +268,12 @@ bool os_file_delete(strview_t path) {
return DeleteFile(fname.buf); return DeleteFile(fname.buf);
} }
bool os_dir_delete(strview_t path) {
OS_SMALL_SCRATCH();
tstr_t fname = strv_to_tstr(&scratch, path);
return RemoveDirectory(fname.buf);
}
oshandle_t os_file_open(strview_t path, filemode_e mode) { oshandle_t os_file_open(strview_t path, filemode_e mode) {
OS_SMALL_SCRATCH(); OS_SMALL_SCRATCH();
@ -364,8 +406,14 @@ dir_entry_t os__dir_entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) {
} }
dir_t *os_dir_open(arena_t *arena, strview_t path) { dir_t *os_dir_open(arena_t *arena, strview_t path) {
usize prev = arena_tell(arena);
dir_t* ctx = alloc(arena, dir_t);
arena_t scratch = *arena;
#if 0
u8 tmpbuf[KB(1)] = {0}; u8 tmpbuf[KB(1)] = {0};
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
#endif
tstr_t winpath = strv_to_tstr(&scratch, path); tstr_t winpath = strv_to_tstr(&scratch, path);
// get a little extra leeway // get a little extra leeway
@ -378,11 +426,10 @@ dir_t *os_dir_open(arena_t *arena, strview_t path) {
fullpath[pathlen++] = '*'; fullpath[pathlen++] = '*';
fullpath[pathlen++] = '\0'; fullpath[pathlen++] = '\0';
dir_t *ctx = alloc(arena, dir_t);
ctx->handle = FindFirstFile(fullpath, &ctx->find_data); ctx->handle = FindFirstFile(fullpath, &ctx->find_data);
if (ctx->handle == INVALID_HANDLE_VALUE) { if (ctx->handle == INVALID_HANDLE_VALUE) {
arena_pop(arena, sizeof(dir_t)); arena_rewind(arena, prev);
return NULL; return NULL;
} }
@ -551,8 +598,15 @@ bool os_process_wait(oshandle_t proc, uint time, int *out_exit) {
return exit_status == 0; return exit_status == 0;
} }
// == MEMORY ====================================
// == VMEM ====================================== void *os_alloc(usize size) {
return VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
}
void os_free(void *ptr) {
VirtualFree(ptr, 0, MEM_RELEASE);
}
void *os_reserve(usize size, usize *out_padded_size) { void *os_reserve(usize size, usize *out_padded_size) {
usize alloc_size = os_pad_to_page(size); usize alloc_size = os_pad_to_page(size);
@ -605,11 +659,15 @@ bool os_thread_detach(oshandle_t thread) {
bool os_thread_join(oshandle_t thread, int *code) { bool os_thread_join(oshandle_t thread, int *code) {
if (!os_handle_valid(thread)) return false; if (!os_handle_valid(thread)) return false;
os_entity_t *entity = (os_entity_t *)thread.data; os_entity_t *entity = (os_entity_t *)thread.data;
int return_code = WaitForSingleObject(entity->thread.handle, INFINITE); DWORD wait_result = WaitForSingleObject(entity->thread.handle, INFINITE);
if (code) *code = return_code; if (code) {
DWORD exit_code = 0;
GetExitCodeThread(entity->thread.handle, &exit_code);
*code = exit_code;
}
BOOL result = CloseHandle(entity->thread.handle); BOOL result = CloseHandle(entity->thread.handle);
os__win_free_entity(entity); os__win_free_entity(entity);
return return_code != WAIT_FAILED && result; return wait_result != WAIT_FAILED && result;
} }
u64 os_thread_get_id(oshandle_t thread) { u64 os_thread_get_id(oshandle_t thread) {