dtunittest
Minimal C unit-test harness for suites, tests, assertions, and error/report plumbing. Provides a tiny, dependency-light runner used across the dtack portfolio.
Mini-guide
- Use
dtunittest_run_suite()to group relateddtunittest_run_test()calls into one reportable suite. - Tests return
NULLon success or adterr_t*on failure. The runner prints and disposes failures. - Every test body must define a
dterr_t* dterr = NULL;and acleanup:label. The assertion macros jump there on failure. - Optional hooks: set
suite_setup,suite_teardown,test_setup,test_teardownindtunittest_control_tfor fixture lifecycle. - Filtering: set
patternindtunittest_control_tto run only tests whose names contain that substring. - Resource auditing: if you provide a NULL-terminated array of
dtledger_t*inledgers, the runner clears and checks them around each test.
Example
Typical flow: define tests that use the assert macros, then run them inside a suite.
#include <dtcore/dtunittest.h>
#include <dtcore/dterr.h>
static dterr_t* T01_addition_holds(void)
{
dterr_t* dterr = NULL;
int got = 2 + 2;
int expected = 4;
DTUNITTEST_ASSERT_INT(got, ==, expected);
cleanup:
return dterr; /* NULL means pass */
}
static void math_suite(dtunittest_control_t* unittest_control)
{
DTUNITTEST_RUN_TEST(T01_addition_holds);
}
int main(void)
{
dtunittest_control_t uc = {0};
uc.should_print_tests = true;
uc.should_print_suites = true;
uc.should_print_errors = true;
dtunittest_run_suite(&uc, "math_suite", math_suite);
return (uc.total_fail_count == 0) ? 0 : 1;
}
Notes:
- DTUNITTEST_RUN_TEST(X) expands to dtunittest_run_test(unittest_control, "X", X);.
- If you prefer not to use the macro, call dtunittest_run_test directly.
- For suite functions, DTUNITTEST_SUITE_ARGS expands to dtunittest_control_t* unittest_control.
Functions
dtunittest_run_suite
void dtunittest_run_suite(dtunittest_control_t* unittest_control,
const char* test_name,
dtunittest_suitefunc_f suitefunc);
suite_setup and suite_teardown, and accumulates totals. unittest_control must be non-NULL.
dtunittest_run_test
void dtunittest_run_test(dtunittest_control_t* unittest_control,
const char* test_name,
dtunittest_testfunc_f testfunc);
unittest_control->pattern, invokes test_setup/test_teardown, audits ledgers if provided, prints errors if enabled, and disposes any returned dterr_t*.
dtunittest_each_error_callback
void dtunittest_each_error_callback(dterr_t* dterr, void* context);
dterr_each() to print one node in an error chain. The default impl ignores context.
Contracts and invariants
- Test contract: each test must define
dterr_t* dterrand acleanup:label; returnNULLon success or a livedterr_t*on failure. The runner owns disposal of the returned error. - Filtering: when
patternis non-NULL, only tests whose names contain the substring run. - Ledgers:
ledgersis a NULL-terminated array ofdtledger_t*. When set, the runner resets before a test and verifies after, treating leaks or negative low-water marks as failures. - Printing flags:
should_print_tests,should_print_suites, andshould_print_errorsgate console output but do not affect pass/fail semantics.
Assertion macros (overview)
Use the helpers to create readable failures and jump to cleanup:
- DTUNITTEST_ASSERT_TRUE(expr)
- DTUNITTEST_ASSERT_DTERR(err, code) (disposes on success)
- DTUNITTEST_ASSERT_INT(got, OP, expected) (signed int)
- DTUNITTEST_ASSERT_UINT8/UINT32/UINT64(got, OP, expected)
- DTUNITTEST_ASSERT_DOUBLE(got, OP, tol, expected)
- DTUNITTEST_ASSERT_PTR(got, OP, expected)
- DTUNITTEST_ASSERT_NULL(ptr) / DTUNITTEST_ASSERT_NOT_NULL(ptr)
- DTUNITTEST_ASSERT_EQUAL_STRING(got, expected)
- DTUNITTEST_ASSERT_EQUAL_BYTES(got, expected, len)
All macros set dterr = dterr_new(DTERR_ASSERTION, DTERR_LOC, ...) and goto cleanup; when the check fails.