Devlog #6: Unit tests for Error Handling (Commit: 252eb6d937)
5 changed files with 1312 additions and 0 deletions
After implementing the error code system, diagnostic stack, and scope guard, I needed to make sure they actually work before I start building on top of them. So, my favorite activity, writing unit tests, was awaiting me with 64 more tests across 3 new suites.
New test macro: ZINN_EXPECT_EQ_STR
The diagnostic stack tests compare message strings. ZINN_EXPECT_EQ would compare pointer addresses if you passed strings, not contents, so I added a strcmp based variant:
ZINN_EXPECT_EQ_STR(frame->message, "42 hi");
On failure it prints actual vs expected strings, which makes debugging test failures a lot easier.
Error Code (16 tests)
The tests verify that each field lands in the correct position and that overflows are safely masked out:
-
“Round-trip”: pack
ZINN_DOMAIN_LOGGER | ZINN_SEVERITY_INFO | 0x42, then extract each field and verify -
Overflow tests: the masks (
0xFFFFF,0xFF,0xF) clip overflowed fields back to zero, tested with values one past each bit boundary - Max values: 0xFF domain, 0xF severity, 0xFFFFF code all survive the round trip
- All 6 predefined codes: decoded and checked against expected domain/severity/code down to the exact values
Diagnostic Stack (36 tests)
Raw push/pop/peek/clear
-
Push: basic field assignment, null file maps to
"unknown", null function maps to"unknown", null format maps to"(none)", empty format produces empty string, printf args ("%d %s",42,"hi") produce"42 hi", truncation at exactly 127 bytes + null terminator at 128 -
Push overflow: fill all 16 slots, the 17th push returns
false, depth stays pinned at 16, the last valid frame is untouched - Pop: once from a non-empty stack decrements depth, pop on an empty stack is a no-op, pop 3 of 5 leaves depth at 2, pop all 16 resets to 0
-
Peek: empty stack returns
nullptr, non-empty returns the top frame (LIFO: second push overwrites the peek target), after clear returnsnullptr - Clear: empty stack stays at 0, stack with 5 frames resets to 0
Convenience macros
Each macro has its own test helper and multiple assertions:
-
ZINN_DIAGNOSTICS_PUSH: verifies__FILE__,__func__,__LINE__are injected, with and without format args -
ZINN_RETURN_IF_ERR: passingZINN_OKskips the frame and returns OK; passing an error pushes a frame containing"Propagated error"and returns the error -
ZINN_RETURN_IF: condition true: pushes"Condition failed (true)"and returns the error; false: no frame, returns OK. -
ZINN_TRY: wraps a successful call (forwards the value), wraps a failing call (pushes"TRY macro captured failure", returns the error) -
ZINN_CHECK_ERROR: the only macro that doesn’t return. After pushing a frame, execution continues. Verified with acontinuedflag, format args, and a static message
Integration
Two isolation tests: one interleaves push/pop/push and verifies frame independence (frame 0 still holds "0" after pops, frame 1 holds "new"). The other pushes two frames with different error codes and verifies LIFO ordering and the peek return value at each step.
Scope Guard (12 tests)
Scope guards fire implicitly when a scope exits. Tested with global counters for basic fire, multiple guards, early return, null-safety, nested scopes, named guards, disarm, double-disarm, and direct execute() calls with null/disarmed/null-cleanup guards.
What’s next?
With 160 tests across 5 suites, I can take a short break from writing unit tests. Next up is the CLI argument parser (probably) the final core module before I can get to the fun ASCII stuff.
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.