.
This commit is contained in:
parent
95d74c2ef4
commit
a66e58193f
25 changed files with 2600 additions and 93 deletions
130
arena.c
130
arena.c
|
|
@ -1,8 +1,11 @@
|
|||
#include "arena.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#if COLLA_DEBUG
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "os.h"
|
||||
|
||||
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 out = {0};
|
||||
|
||||
#if COLLA_DEBUG
|
||||
out.file = desc->file;
|
||||
out.line = desc->line;
|
||||
#endif
|
||||
|
||||
if (desc) {
|
||||
switch (desc->type) {
|
||||
case ARENA_VIRTUAL: out = arena__make_virtual(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_MALLOC_ALWAYS: out = malloc_arena; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +85,10 @@ void *arena_alloc(const arena_alloc_desc_t *desc) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (!ptr && desc->flags & ALLOC_SOFT_FAIL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
usize total = desc->size * desc->count;
|
||||
|
||||
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
|
||||
|
|
@ -98,7 +111,7 @@ void arena_rewind(arena_t *arena, usize from_start) {
|
|||
return;
|
||||
}
|
||||
|
||||
assert(arena_tell(arena) >= from_start);
|
||||
colla_assert(arena_tell(arena) >= from_start);
|
||||
|
||||
arena->cur = arena->beg + from_start;
|
||||
}
|
||||
|
|
@ -137,17 +150,13 @@ static void arena__free_virtual(arena_t *arena) {
|
|||
return;
|
||||
}
|
||||
|
||||
bool success = os_release(arena->beg, arena_capacity(arena));
|
||||
assert(success && "Failed arena free");
|
||||
os_release(arena->beg, arena_capacity(arena));
|
||||
}
|
||||
// == MALLOC ARENA =====================================================================================================
|
||||
|
||||
extern void *malloc(usize size);
|
||||
extern void free(void *ptr);
|
||||
|
||||
static arena_t arena__make_malloc(usize size) {
|
||||
u8 *ptr = malloc(size);
|
||||
assert(ptr);
|
||||
u8 *ptr = os_alloc(size);
|
||||
colla_assert(ptr);
|
||||
return (arena_t) {
|
||||
.beg = ptr,
|
||||
.cur = ptr,
|
||||
|
|
@ -157,7 +166,7 @@ static arena_t arena__make_malloc(usize size) {
|
|||
}
|
||||
|
||||
static void arena__free_malloc(arena_t *arena) {
|
||||
free(arena->beg);
|
||||
os_free(arena->beg);
|
||||
}
|
||||
|
||||
// == ARENA ALLOC ======================================================================================================
|
||||
|
|
@ -171,7 +180,10 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
|
|||
|
||||
if (total > arena_remaining(arena)) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -187,10 +199,13 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
|
|||
// TODO is this really correct?
|
||||
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 (!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);
|
||||
}
|
||||
return NULL;
|
||||
|
|
@ -201,6 +216,14 @@ static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
|
|||
u8 *ptr = arena->cur;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -208,9 +231,9 @@ static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) {
|
|||
usize total = desc->size * desc->count;
|
||||
|
||||
// TODO: alignment?
|
||||
u8 *ptr = malloc(total);
|
||||
u8 *ptr = os_alloc(total);
|
||||
if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) {
|
||||
fatal("malloc call failed for %_$$$dB", total);
|
||||
fatal("alloc call failed for %_$$$dB", total);
|
||||
}
|
||||
|
||||
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
35
arena.h
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include "core.h"
|
||||
|
||||
#if COLLA_DEBUG
|
||||
#include "str.h"
|
||||
#endif
|
||||
|
||||
#if COLLA_WIN && !COLLA_TCC
|
||||
#define alignof __alignof
|
||||
#endif
|
||||
|
|
@ -21,16 +25,36 @@ typedef enum alloc_flags_e {
|
|||
ALLOC_SOFT_FAIL = 1 << 1,
|
||||
} 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;
|
||||
struct arena_t {
|
||||
u8 *beg;
|
||||
u8 *cur;
|
||||
u8 *end;
|
||||
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;
|
||||
struct arena_desc_t {
|
||||
#if COLLA_DEBUG
|
||||
strview_t file;
|
||||
int line;
|
||||
#endif
|
||||
arena_type_e type;
|
||||
usize size;
|
||||
u8 *static_buffer;
|
||||
|
|
@ -38,6 +62,9 @@ struct arena_desc_t {
|
|||
|
||||
typedef struct arena_alloc_desc_t arena_alloc_desc_t;
|
||||
struct arena_alloc_desc_t {
|
||||
#if COLLA_DEBUG
|
||||
strview_t type_name;
|
||||
#endif
|
||||
arena_t *arena;
|
||||
usize count;
|
||||
alloc_flags_e flags;
|
||||
|
|
@ -46,10 +73,18 @@ struct arena_alloc_desc_t {
|
|||
};
|
||||
|
||||
// arena_type_e type, usize allocation, [ byte *static_buffer ]
|
||||
#if !COLLA_DEBUG
|
||||
#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 ]
|
||||
#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__ })
|
||||
#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
|
||||
// malloc for some reason but want to still use the arena interface
|
||||
|
|
|
|||
42
core.h
42
core.h
|
|
@ -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 /////////////////////////////////
|
||||
|
||||
// singly linked list
|
||||
|
|
@ -73,7 +58,7 @@ void colla_cleanup(void);
|
|||
|
||||
// OS AND COMPILER MACROS ///////////////////////
|
||||
|
||||
#if defined(_DEBUG) || !defined(NDEBUG)
|
||||
#if defined(_DEBUG)
|
||||
#define COLLA_DEBUG 1
|
||||
#define COLLA_RELEASE 0
|
||||
#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 //////////////////////////////////
|
||||
|
||||
#if COLLA_WIN && COLLA_UNICODE
|
||||
|
|
|
|||
27
net.c
27
net.c
|
|
@ -1,6 +1,8 @@
|
|||
#include "net.h"
|
||||
#include "arena.h"
|
||||
|
||||
#include <stdio.h> // sscanf
|
||||
|
||||
#if COLLA_WIN
|
||||
#include "win/net_win32.c"
|
||||
#else
|
||||
|
|
@ -38,7 +40,7 @@ const char *http_get_status_string(int status) {
|
|||
case 404: return "NOT FOUND";
|
||||
case 407: return "RANGE NOT SATISFIABLE";
|
||||
|
||||
case 500: return "INTERNAL SERVER_ERROR";
|
||||
case 500: return "INTERNAL SERVER ERROR";
|
||||
case 501: return "NOT IMPLEMENTED";
|
||||
case 502: return "BAD GATEWAY";
|
||||
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)) {
|
||||
strview_t line = istr_get_line(in);
|
||||
|
||||
// end of headers
|
||||
if (strv_is_empty(line)) {
|
||||
break;
|
||||
}
|
||||
|
||||
usize pos = strv_find(line, ':', 0);
|
||||
if (pos != STR_NONE) {
|
||||
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};
|
||||
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"))) {
|
||||
err("response doesn't start with 'HTTP', instead with %v", http);
|
||||
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 = {
|
||||
.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;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return (buffer_t){
|
||||
.data = out,
|
||||
.len = outlen,
|
||||
.len = outlen - spaces_count,
|
||||
};
|
||||
}
|
||||
2
os.c
2
os.c
|
|
@ -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) {
|
||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
||||
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;
|
||||
}
|
||||
str_t out = os_file_read_all_str_fp(arena, fp);
|
||||
|
|
|
|||
6
os.h
6
os.h
|
|
@ -118,6 +118,7 @@ bool os_dir_create(strview_t folder);
|
|||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
bool os_commit(void *ptr, usize num_of_pages);
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ void json_pretty_print(json_t *root, const json_pretty_opts_t *options) {
|
|||
LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY,
|
||||
LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING,
|
||||
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_RED, // JSON_PRETTY_COLOUR_FALSE,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
#include "pretty_print.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "core.h"
|
||||
#include "os.h"
|
||||
#include "str.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
strview_t pretty__colour[LOG_COL__COUNT] = {
|
||||
[LOG_COL_BLACK] = cstrv("black"),
|
||||
|
|
@ -28,6 +26,10 @@ strview_t pretty__colour[LOG_COL__COUNT] = {
|
|||
[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, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#if 0
|
||||
|
||||
pretty_print(arena,
|
||||
"<red>error!</><blue>%s!</>", "wow");
|
||||
|
||||
#endif
|
||||
|
||||
#include "os.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_printv(arena_t scratch, const char *fmt, va_list args);
|
||||
|
||||
|
|
|
|||
12
str.c
12
str.c
|
|
@ -1,5 +1,7 @@
|
|||
#include "str.h"
|
||||
|
||||
#include "os.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -396,7 +398,11 @@ bool char_is_num(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 =================================================
|
||||
|
|
@ -657,9 +663,11 @@ char ostr_back(outstream_t *ctx) {
|
|||
str_t ostr_to_str(outstream_t *ctx) {
|
||||
ostr_putc(ctx, '\0');
|
||||
|
||||
usize len = ostr_tell(ctx);
|
||||
|
||||
str_t out = {
|
||||
.buf = ctx->beg,
|
||||
.len = ostr_tell(ctx) - 1,
|
||||
.len = len ? len - 1 : 0,
|
||||
};
|
||||
|
||||
memset(ctx, 0, sizeof(outstream_t));
|
||||
|
|
|
|||
3
str.h
3
str.h
|
|
@ -1,8 +1,6 @@
|
|||
#ifndef COLLA_STR_H
|
||||
#define COLLA_STR_H
|
||||
|
||||
#include <string.h> // strlen
|
||||
|
||||
#include "core.h"
|
||||
#include "darr.h"
|
||||
|
||||
|
|
@ -165,6 +163,7 @@ bool char_is_space(char c);
|
|||
bool char_is_alpha(char c);
|
||||
bool char_is_num(char c);
|
||||
char char_lower(char c);
|
||||
char char_upper(char c);
|
||||
|
||||
// == INPUT STREAM =================================================
|
||||
|
||||
|
|
|
|||
237
tests/arena_tests.c
Normal file
237
tests/arena_tests.c
Normal 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
229
tests/core_tests.c
Normal 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
179
tests/highlight_tests.c
Normal 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
238
tests/net_tests.c
Normal 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
374
tests/os_tests.c
Normal 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
370
tests/parsers_tests.c
Normal 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);
|
||||
}
|
||||
34
tests/pretty_print_tests.c
Normal file
34
tests/pretty_print_tests.c
Normal 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
41
tests/runner.h
Normal 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
457
tests/str_tests.c
Normal 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
12
tests/string_tests.c
Normal 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);
|
||||
}
|
||||
|
||||
13
tools/nob.c
13
tools/nob.c
|
|
@ -1,4 +1,5 @@
|
|||
#define COLLA_NO_CONDITION_VARIABLE 1
|
||||
#define COLLA_NO_NET 1
|
||||
|
||||
#include "../build.c"
|
||||
#include <windows.h>
|
||||
|
|
@ -296,7 +297,7 @@ int main(int argc, char **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");
|
||||
_mkdir("build");
|
||||
}
|
||||
|
|
@ -307,7 +308,14 @@ int main(int argc, char **argv) {
|
|||
arena_t scratch = arena;
|
||||
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");
|
||||
os_abort(1);
|
||||
}
|
||||
|
|
@ -366,6 +374,7 @@ int main(int argc, char **argv) {
|
|||
|
||||
if (opt.debug) {
|
||||
darr_push(&scratch, cmd, strv("/Zi"));
|
||||
darr_push(&scratch, cmd, strv("/D_DEBUG"));
|
||||
}
|
||||
|
||||
for_each (def, opt.defines) {
|
||||
|
|
|
|||
80
tools/unit_tests.c
Normal file
80
tools/unit_tests.c
Normal 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);
|
||||
}
|
||||
|
|
@ -48,6 +48,10 @@ iptr net_get_last_error(void) {
|
|||
}
|
||||
|
||||
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 request = NULL;
|
||||
BOOL result = FALSE;
|
||||
|
|
@ -135,7 +139,14 @@ http_res_t http_request(http_request_desc_t *req) {
|
|||
(DWORD)req->body.len
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +197,12 @@ http_res_t http_request(http_request_desc_t *req) {
|
|||
if (!read_success || read == 0) {
|
||||
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));
|
||||
|
|
|
|||
106
win/os_win32.c
106
win/os_win32.c
|
|
@ -1,6 +1,10 @@
|
|||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
|
||||
#if COLLA_DEBUG
|
||||
#include <stdlib.h> // abort
|
||||
#endif
|
||||
|
||||
#include "../os.h"
|
||||
#include "../net.h"
|
||||
|
||||
|
|
@ -37,6 +41,8 @@ struct {
|
|||
os_entity_t *entity_free;
|
||||
oshandle_t hstdout;
|
||||
oshandle_t hstdin;
|
||||
WORD default_fg;
|
||||
WORD default_bg;
|
||||
} w32_data = {0};
|
||||
|
||||
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});
|
||||
|
||||
HANDLE hstdout = CreateFile(TEXT("CONOUT$"), 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 hstdout = CreateFile(TEXT("CONOUT$"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 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$");
|
||||
else w32_data.hstdout.data = (uptr)hstdout;
|
||||
|
||||
if (hstdin == INVALID_HANDLE_VALUE) err("couldn't open CONIN$");
|
||||
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) {
|
||||
|
|
@ -95,6 +110,9 @@ void os_cleanup(void) {
|
|||
}
|
||||
|
||||
void os_abort(int code) {
|
||||
#if COLLA_DEBUG
|
||||
if (code != 0) abort();
|
||||
#endif
|
||||
ExitProcess(code);
|
||||
}
|
||||
|
||||
|
|
@ -111,19 +129,21 @@ str_t os_get_error_string(iptr error) {
|
|||
DWORD chars;
|
||||
chars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, msgbuf, arrlen(msgbuf), NULL);
|
||||
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;
|
||||
}
|
||||
|
||||
debug("error: %zi 0x%zX", error, error);
|
||||
|
||||
// remove \r\n at the end
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||
switch (colour) {
|
||||
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;
|
||||
WORD attribute = colour == LOG_COL_RESET ? w32_data.default_fg : (WORD)colour;
|
||||
SetConsoleTextAttribute((HANDLE)w32_data.hstdout.data, attribute);
|
||||
}
|
||||
|
||||
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
SetConsoleTextAttribute(hc, attribute | FOREGROUND_INTENSITY);
|
||||
void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background) {
|
||||
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) {
|
||||
|
|
@ -206,6 +222,26 @@ bool os_file_exists(strview_t path) {
|
|||
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) {
|
||||
OS_SMALL_SCRATCH();
|
||||
|
||||
|
|
@ -232,6 +268,12 @@ bool os_file_delete(strview_t path) {
|
|||
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) {
|
||||
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) {
|
||||
usize prev = arena_tell(arena);
|
||||
dir_t* ctx = alloc(arena, dir_t);
|
||||
|
||||
arena_t scratch = *arena;
|
||||
#if 0
|
||||
u8 tmpbuf[KB(1)] = {0};
|
||||
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
|
||||
#endif
|
||||
|
||||
tstr_t winpath = strv_to_tstr(&scratch, path);
|
||||
// get a little extra leeway
|
||||
|
|
@ -378,11 +426,10 @@ dir_t *os_dir_open(arena_t *arena, strview_t path) {
|
|||
fullpath[pathlen++] = '*';
|
||||
fullpath[pathlen++] = '\0';
|
||||
|
||||
dir_t *ctx = alloc(arena, dir_t);
|
||||
ctx->handle = FindFirstFile(fullpath, &ctx->find_data);
|
||||
|
||||
if (ctx->handle == INVALID_HANDLE_VALUE) {
|
||||
arena_pop(arena, sizeof(dir_t));
|
||||
arena_rewind(arena, prev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -551,8 +598,15 @@ bool os_process_wait(oshandle_t proc, uint time, int *out_exit) {
|
|||
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) {
|
||||
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) {
|
||||
if (!os_handle_valid(thread)) return false;
|
||||
os_entity_t *entity = (os_entity_t *)thread.data;
|
||||
int return_code = WaitForSingleObject(entity->thread.handle, INFINITE);
|
||||
if (code) *code = return_code;
|
||||
DWORD wait_result = WaitForSingleObject(entity->thread.handle, INFINITE);
|
||||
if (code) {
|
||||
DWORD exit_code = 0;
|
||||
GetExitCodeThread(entity->thread.handle, &exit_code);
|
||||
*code = exit_code;
|
||||
}
|
||||
BOOL result = CloseHandle(entity->thread.handle);
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue