You are browsing as a guest. Sign up (or log in) to start making projects!

1h 52m 22s logged

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 returns nullptr
  • 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: passing ZINN_OK skips 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 a continued flag, 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.

0
7

Comments 0

No comments yet. Be the first!