# Tests and Assertions ## Test infrastructure Tests live in `test/` and mirror the `src/dusk/` directory structure. Enable with `-DDUSK_BUILD_TESTS=ON`. The test runner is **cmocka**. ### Entry point Every test file includes `dusktest.h`, which pulls in `dusk.h` and `assert/assert.h`. When `DUSK_TEST_ASSERT` is defined, `assert.h` includes `cmocka.h` and redirects assertion failures through `mock_assert()` instead of calling `abort()`. ### Test function signature ```c static void test_something(void **state) { // ... setup ... // ... exercise ... // ... assert ... assert_int_equal(memoryGetAllocatedCount(), 0); // leak check } ``` ### Registering and running tests ```c int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_errorThrow), cmocka_unit_test(test_errorOk), }; return cmocka_run_group_tests(tests, NULL, NULL); } ``` Use `cmocka_unit_test_setup_teardown()` when a test needs per-test setup or teardown callbacks. ### Assertion mix Tests use **two** sets of assertion macros: | Origin | When to use | |--------|-------------| | cmocka: `assert_int_equal()`, `assert_non_null()` etc. | Validate results inside test functions | | Dusk: `assertTrue()`, `assertNotNull()` etc. | Exercise the code under test (these may fire and need catching) | To assert that a Dusk assertion fires, use cmocka's mock system: ```c expect_assert_failure(assertTrueImpl(__FILE__, __LINE__, false, "msg")); ``` ### Memory leak discipline Every test function must end by asserting: ```c assert_int_equal(memoryGetAllocatedCount(), 0); ``` This ensures all allocations from the code under test were freed. --- ## Assertion system Source: `src/dusk/assert/` ### Runtime vs test mode | Mode | Trigger | Effect on failure | |------|---------|-------------------| | Runtime (default) | Release / non-test builds | Logs the message + backtrace, then calls `abort()` | | Test (`DUSK_TEST_ASSERT`) | `-DDUSK_TEST_ASSERT` build flag | Routes through cmocka `mock_assert()` for controlled catching | | Faked (`DUSK_ASSERTIONS_FAKED`) | Defined by platform or test | All macros become no-ops (`((void)0)`) | ### Available macros | Macro | Description | |-------|-------------| | `assertTrue(x, msg)` | Fails if `x` is false | | `assertFalse(x, msg)` | Fails if `x` is true | | `assertNotNull(ptr, msg)` | Fails if `ptr` is NULL | | `assertNull(ptr, msg)` | Fails if `ptr` is not NULL | | `assertUnreachable(msg)` | Unconditional failure; marks unreachable code | | `assertDeprecated(msg)` | Marks a code path as deprecated | | `assertStringEqual(a, b, msg)` | Fails if strings differ | | `assertStrLenMax(str, len, msg)` | Fails if `strlen(str) >= len` | | `assertStrLenMin(str, len, msg)` | Fails if `strlen(str) < len` | | `assertIsMainThread(msg)` | Fails if called from a non-main thread | | `assertNotMainThread(msg)` | Fails if called from the main thread | | `assertStructSize(type, size)` | Compile-time size check via `_Static_assert` | ### Thread tracking `assertInit()` records the main thread ID (pthreads). The main-thread assertions compare against this stored ID. Call `assertInit()` once at startup before spawning any threads. ### Usage guidelines - Prefer the specific macro over a bare `assertTrue` for clarity (e.g. use `assertNotNull` instead of `assertTrue(ptr != NULL, ...)`). - Use `assertUnreachable` in `default:` cases of exhaustive switches. - Use `assertStructSize` to guard struct layouts that must match a known binary format or a platform ABI. - Do not use asserts for expected error paths -- use `errorThrow` instead. Asserts are for programmer mistakes, not runtime errors.