diff --git a/Makefile b/Makefile index bb82a59..2051944 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PROJECT=naka DESCRIPTION="Find a file within another file" AUTHOR=Avril (Flanchan) -SRC_C = $(wildcard src/*.c) +SRC_C = $(wildcard src/*.c) $(wildcard src/tests/*.c) SRC_CXX = $(wildcard src/*.cpp) INCLUDE=include @@ -50,7 +50,7 @@ debug: | dirs $(PROJECT)-debug # Targets dirs: - @mkdir -p obj/c{,xx}/src + @mkdir -p obj/c{,xx}/src{,/tests} obj/c/%.o: %.c $(CC) -c $< $(CFLAGS) -o $@ $(LDFLAGS) diff --git a/include/macros.h b/include/macros.h index cdd13ee..a1860f1 100644 --- a/include/macros.h +++ b/include/macros.h @@ -9,6 +9,7 @@ #include #include #include +#include // Attribute macros @@ -84,6 +85,8 @@ _mixin void _drain_val(void* x, ...) { IGNORE(x); } // Assertions +#define assert_msg(expr, ...) ( (expr) ? 1 : FATAL(__VA_ARGS__)) + #ifdef DEBUG #define debug_assert(x) assert((x)) #elif defined(_EVAL_DEBUG_ONLY_STMTS) @@ -136,8 +139,10 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)"); #ifdef DEBUG #define TRACE(msg, ...) TRACEx("trace", msg, ## __VA_ARGS__) +#define dprintf(msg, ...) TRACEx("debug", msg, ## __VA_ARGS__) #elif defined(_EVAL_DEBUG_ONLY_STMTS) #define TRACE(msg, ...) _drain(msg, ## __VA_ARGS__) +#define dprintf(msg, ...) _drain(msg, ## __VA_ARGS__) #else #define TRACE(msg, ...) _no_op #endif @@ -147,6 +152,19 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)"); #define FATAL(msg, ...) (TRACEx("FATAL", msg, ## __VA_ARGS__), abort()) +// Debug testing + +#define TEST_OK 1 +#define TEST_FAILED 0 + +#define TESTFUN(name) _test__ ## name +#define CALLTEST(name) (assert_msg(TESTFUN(name)() == TEST_OK, "Test `" #name "` failed"), TRACE("Test `" #name "` passed!")) +#define DEFTEST(name) int _test__ ## name(void) + +#define TEST_ASSERT(x) do { if (!(x)) {ERROR("Test `%s' assertion failure: `" #x "`", __func__ + 7); return TEST_FAILED;} } while(0) +#define TEST_ASSERT_EQ(x,y) TEST_ASSERT( (x) == (y) ) +#define TEST_ASSERT_NE(x,y) TEST_ASSERT( (x) != (y) ) + // Version macros #define _AS_u32(x) ((uint32_t)(x)) diff --git a/include/tests.h b/include/tests.h new file mode 100644 index 0000000..a3cfdda --- /dev/null +++ b/include/tests.h @@ -0,0 +1,22 @@ +#ifndef _TESTH_H +#define _TESTS_H + +#include +#include "macros.h" + +/// Run a test before `main()` +#define RUNTEST(name) __attribute__((constructor)) void _runtest__ ## name () { \ + fprintf(stderr, "[!] <--- running test " #name " --- \n"); \ + CALLTEST(name); \ + fprintf(stderr, "[!] --- " #name " ok --->\n"); \ + } + +#ifdef DEBUG +#define RUNTEST_DEBUG(name) RUNTEST(name) +#else +#define RUNTEST_DEBUG(name) +#endif + +DEFTEST(version_str); + +#endif /* _TESTS_H */ diff --git a/include/version.h b/include/version.h index 020d121..f00f972 100644 --- a/include/version.h +++ b/include/version.h @@ -43,16 +43,13 @@ _mixin uint32_t v_ctoraw(version_t ver) /// Print a version to a string buffer `out`. Return the number of bytes written. /// -/// Has the same semantics as `snprintf`; to calculate the length of the string needed to allocate, invoke like: `size_t sz = v_ctosn(vers, 0, NULL)`. The buffer should be allocated as `sz + 1`, and then the next call to `v_ctosn` should be like: `v_ctosn(vers, sz+1, buffer); -//XXX: Specifying `str[static ...` tells the compiler this function expects a non-null pointer. When in reality, a `NULL` pointer is used here as a sentinel value to not print... The mixin below is a hack around this, but this might introduce invalid optimisations. We should either: split the size getting function into its own thing (`v_ctosn_sz` would become a real function) that does the exact same thing as `v_ctosn(_,0,NULL)`, and not pass `NULL` to str here ever. OR, change str to regular `char *pOUT str`. The latter would be easier, but we might potentially lose out on some obscure non-NULL snprintf optimisation? For now, just use the `v_ctosn_sz` hack below. If we go with the latter, we can #define it to be just `v_ctosn(v, 0, NULL)`, otherwise move it to version.c and implement it with `snprintf(NULL, 0, ...)`. +/// Has the same semantics as `snprintf`, however, the compiler assumes `str` to be non-NULL. To get the buffer size required, use `v_ctosn_sz()` instead. usize v_ctosn(version_t ver, usize max, char str[static pOUT max]); -_mixin usize v_ctosn_sz(version_t ver) -{ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnonnull" - return v_ctosn(ver, 0, NULL); -#pragma GCC diagnostic pop -} + +/// Get the buffer size required for `v_ctosn().` +/// +/// Same as calling `v_ctosn(ver, 0, NULL)` but won't warn about non-NULL parameter `str`. +usize v_ctosn_sz(version_t ver); /// Print a version to a static string buffer. The string buffer is thread local. The next call to this function on the same thread will overwrite the returned buffer. const char* v_ctoss(version_t ver); diff --git a/src/main.c b/src/main.c index d5c4bca..0496362 100644 --- a/src/main.c +++ b/src/main.c @@ -8,27 +8,20 @@ #include #include +#include + + int main(int argc, char** argv) { IGNORE(argc); IGNORE(argv); - const u32 vraw = VERSION(100,200,101,255); - version_t vers = v_rawtoc(vraw); + INFO("main start"); - usize sz = v_ctosn_sz(vers); - char verstr[sz+1]; - v_ctosn(vers, sz+1, verstr); - - char* verstr_h; - INFO("Version: 0x%x, raw: 0x%x", vers.raw, vraw); - INFO("Output is: (%lu chars)\t%s", sz, verstr); - INFO("Output is (static):\t%s", v_ctoss(vers)); - INFO("Output is (leak):\t\t%s", (verstr_h = v_ctos(vers))); - - free(verstr_h); //INFO("Hello world %lu %lu %lu!", i, bswap(i), bswap(bswap(i))); //INFO("Version: 0x%x (0x%x)", vers, bswap(vers)); // Check which version endian was preferrable, we'll stick with the default system endian (little, for this one.) + + INFO("main end"); return 0; } diff --git a/src/tests/version.c b/src/tests/version.c new file mode 100644 index 0000000..de1528c --- /dev/null +++ b/src/tests/version.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +DEFTEST(version_str) +{ + const u32 vraw = VERSION(100,200,101,255); + version_t vers = v_rawtoc(vraw); + + usize sz = v_ctosn_sz(vers); + TEST_ASSERT(sz > 0); + char verstr[sz+1]; + TEST_ASSERT_EQ(v_ctosn(vers, sz+1, verstr), sz); + + + + char* verstr_h; + INFO("Version: 0x%x, raw: 0x%x", vers.raw, vraw); + INFO("Output is: (%lu chars)\t%s", sz, verstr); + INFO("Output is (static):\t%s", v_ctoss(vers)); + INFO("Output is (leak):\t\t%s", (verstr_h = v_ctos(vers))); + + TEST_ASSERT(verstr_h); + + free(verstr_h); + + return TEST_OK; +} +RUNTEST_DEBUG(version_str) diff --git a/src/version.c b/src/version.c index 44a4ce7..28dea88 100644 --- a/src/version.c +++ b/src/version.c @@ -4,11 +4,22 @@ #include -usize v_ctosn(version_t ver, usize max, char out[static pOUT max]) +#define VS_FMT(comp) (comp).revision ? "%hhu.%hhu.%hhur%hhu" : "%hhu.%hhu.%hhu" + +usize v_ctosn_sz(version_t ver) { struct v_comp comp = ver.comp; - const char* const fmt = comp.revision ? "%hhu.%hhu.%hhur%hhu" : "%hhu.%hhu.%hhu"; + return snprintf(NULL, 0, VS_FMT(comp), + comp.major, + comp.minor, + comp.bugfix, + comp.revision); +} +usize v_ctosn(version_t ver, usize max, char out[static pOUT max]) +{ + struct v_comp comp = ver.comp; + const char* const fmt = VS_FMT(comp); return snprintf(out, max, fmt, comp.major,