Files
dusk/.claude/tests.md
T
2026-06-16 10:15:59 -05:00

112 lines
3.6 KiB
Markdown

# 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.