Skip to content

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 related dtunittest_run_test() calls into one reportable suite.
  • Tests return NULL on success or a dterr_t* on failure. The runner prints and disposes failures.
  • Every test body must define a dterr_t* dterr = NULL; and a cleanup: label. The assertion macros jump there on failure.
  • Optional hooks: set suite_setup, suite_teardown, test_setup, test_teardown in dtunittest_control_t for fixture lifecycle.
  • Filtering: set pattern in dtunittest_control_t to run only tests whose names contain that substring.
  • Resource auditing: if you provide a NULL-terminated array of dtledger_t* in ledgers, 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);
Runs a suite function and reports results. Resets per-suite counters, invokes optional 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);
Runs a single test and updates pass/fail counts. Applies name filtering via 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);
Callback suitable for 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* dterr and a cleanup: label; return NULL on success or a live dterr_t* on failure. The runner owns disposal of the returned error.
  • Filtering: when pattern is non-NULL, only tests whose names contain the substring run.
  • Ledgers: ledgers is a NULL-terminated array of dtledger_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, and should_print_errors gate 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.