Testland
Browse all skills & agents

ceedling-build-runner

Author and run the Ceedling build system for C unit testing - the canonical build orchestration on top of Unity (assertions) + CMock (mocks) + CException (exceptions). Covers ceedling new scaffolding, the project.yml schema (:project / :paths / :files / :defines / :flags / :tools / :test_runner / :cmock / :unity / :cexception / :gcov / :plugins), the task surface (test:all, test:{name}, test:pattern, test:path, release, clean / clobber, gcov:all, module:create, environment, dumpconfig), JUnit XML via the report_tests_* plugins, gcov integration, host vs cross-build flow, and CI wiring. CMock semantics - the generated Expect / Ignore / IgnoreArg / ReturnThruPtr / AddCallback / Stub / ExpectAndThrow family, cmock.yml :plugins, mock naming, tearDown verification, strict-vs-ignore matching - are in references/cmock.md. Use when a C project wants the ThrowTheSwitch trio bundled by one build command, or when authoring / reading CMock mocks. For the Unity assertion API see unity-test-framework-c.

Install with skills.sh (any agent)

npx skills add testland/qa --skill ceedling-build-runner
View source

ceedling-build-runner

Overview

This skill covers the Ceedling build orchestration - the ceedling command-line tool, project.yml schema, and rake tasks, per throwtheswitch.org/ceedling (opens in new window). For the Unity assertion API see unity-test-framework-c; for CMock's generated mock API see references/cmock.md; for cross-target run see qemu-system-test-runner; for coverage see embedded-coverage-strategy-reference.

When to use

  • C unit-test project - Ceedling is the canonical setup for the ThrowTheSwitch stack.
  • Host-build-driven test loop (most embedded teams test on the host first, then cross-compile under qemu-system-test-runner) - Ceedling handles the host pipeline natively.
  • Coverage required - the bundled gcov plugin produces LCOV / HTML / Cobertura without external glue.
  • Mock-heavy module - CMock is integrated; no separate generator invocation needed.

If the unit-under-test is C++ instead of C, prefer googletest-embedded-arm; Ceedling does not target C++.

Authoring

Scaffolding a new project

Per the Ceedling README and the command-line reference at github.com/ThrowTheSwitch/Ceedling/.../getting-started/command-line.md (opens in new window):

gem install ceedling
ceedling new my-firmware --docs --local
cd my-firmware

--docs includes the documentation locally; --local vendors Unity / CMock / CException into vendor/ceedling/ so the build doesn't need network at compile time. The generated layout:

my-firmware/
  project.yml          # the schema covered below
  src/                 # production code under test
  test/                # one test_<module>.c per module
  test/support/        # shared test helpers
  build/               # generated; gitignore'd
  vendor/ceedling/     # bundled Unity + CMock + CException

project.yml

A minimal project.yml for a host test loop:

:project:
  :build_root: build/
  :use_mocks: TRUE
  :test_file_prefix: test_
:paths:
  :test:
    - test/**
  :source:
    - src/**
  :include:
    - inc/**
:plugins:
  :enabled:
    - report_tests_pretty_stdout
    - report_tests_junit_xml
    - gcov

The full schema (every top-level section, test vs release :flags, the :cmock / :cexception / :gcov blocks, per-section notes, and the plugin list) is in references/project-yml-schema.md.

Creating a module

ceedling module:create[ringbuffer]
# Creates src/ringbuffer.c, src/ringbuffer.h, test/test_ringbuffer.c

The generator emits the canonical skeleton - production header / source with includes wired, test file with setUp / tearDown / one test_ringbuffer_NeedToImplement placeholder.

Running

ceedling test:all         # Run every test_*.c
ceedling test:ringbuffer  # Run only test_ringbuffer.c
ceedling gcov:all         # Run under gcov instrumentation + generate report

ceedling test:all returns non-zero on any test failure; CI gates on the exit code. gcov:all writes build/artifacts/gcov/GcovCoverageResults.html (HtmlDetailed) or GcovCoverageCobertura.xml (Cobertura) - see embedded-coverage-strategy-reference.

The canonical CI invocation chains tasks on one command line:

ceedling clobber test:all release gcov:all
# Clean -> run tests -> build release -> produce coverage report

The full task surface (test:pattern, test:path, --test-case, release, clean / clobber, environment, dumpconfig) is in references/task-reference.md.

Parsing results

Pretty stdout output

-------------------
OVERALL TEST SUMMARY
-------------------
TESTED:  47
PASSED:  46
FAILED:   1
IGNORED:  0

Failures are reported per assertion with file:line:test:reason:

test/test_ringbuffer.c:48:test_overflow_returns_minus_one:FAIL: Expected -1 Was 0

JUnit XML

With report_tests_junit_xml enabled, ceedling test:all writes build/artifacts/test/report.xml in the canonical JUnit schema - the same one GoogleTest produces, so the same CI pipeline plugin (GitHub mikepenz/action-junit-report, GitLab JUnit, Jenkins JUnit) consumes both.

CI integration

jobs:
  ceedling-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Setup Ruby + Ceedling
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
      - run: gem install ceedling
      - name: Run tests + coverage
        run: ceedling clobber test:all gcov:all
      - name: Publish JUnit
        if: always()
        uses: mikepenz/action-junit-report@v4
        with:
          report_paths: 'build/artifacts/test/*.xml'
      - name: Publish coverage
        uses: codecov/codecov-action@v5
        with:
          files: build/artifacts/gcov/GcovCoverageCobertura.xml

For a cross-compile path (host build for CI, ARM build for hardware-in-loop) the recipe is: keep two project.yml files (project.yml for host, project-arm.yml for ARM) and pass --project=project-arm.yml for the ARM build. The ARM build's :tools: overrides the compiler to arm-none-eabi-gcc and the results route through qemu-system-test-runner.

Anti-patterns

Anti-patternWhy it failsFix
Editing the generated *_Runner.c filesRegenerated on every build; edits lostEdit the source test_*.c; the runner is derived
:enforce_strict_ordering: FALSE to "make tests pass"Mock-call-order bugs become invisibleKeep :enforce_strict_ordering: TRUE; fix the SUT's call shape
Commit build/ artifactsRepo bloat; merges conflict on generated files.gitignore build/ always
Skipping ceedling clobber in CIStale mocks survive header changes; ghost test failuresAlways clobber before the CI run
Mixing test compile flags with release flags--coverage in release build inflates the binaryKeep :flags: :test: separate from :flags: :release:
Naming a test file _test.c instead of test_*.cDefault :test_file_prefix: test_ won't pick it upMatch the prefix or change the project.yml setting
Using :use_mocks: FALSE then including a Mock*.hCMock isn't invoked; link fails on the mock symbolEither enable mocks globally or use Ceedling's --mocks runtime flag
Hardcoding the build directory in scripts:build_root is project.yml-driven; scripts break on renameRead it from ceedling environment

Limitations

  • Ruby dependency. Ceedling runs on Ruby; embedded teams without a Ruby toolchain have an adoption cost.
  • C only. No C++ support - for C++ pair googletest-embedded-arm with CMake.
  • Per-file flag overrides are verbose. The :flags schema's glob keys are powerful but error-prone - dumpconfig is the only reliable verifier.
  • No native parallel test execution. Each test_* runs serially. Parallelise across multiple test:path[...] jobs in CI if needed.
  • Plugin discovery is path-sensitive. Custom plugins must live under :plugins: :load_paths: in project.yml. Ceedling's error messages on missing plugins are terse.
  • gcov plugin reports are coupled to host-built coverage. For on-target coverage, write a custom plugin or feed gcovr manually after a QEMU run.

References

Cited inline. Foundational documents:

CMock semantics reference

View source (opens in new window)

CMock semantics reference

CMock is, per github.com/ThrowTheSwitch/CMock (opens in new window), "a mock and stub generator and runtime for unit testing C" that "automagically parses your C headers and creates useful and usable mock interfaces for unit testing". CMock only exists inside the Ceedling / Unity workflow this skill orchestrates - see SKILL.md (opens in new window) for the build tasks and project.yml wiring, and unity-test-framework-c for the assertion API.

This reference covers CMock's generated API surface and the cmock.yml plugin model: decoding which plugin produced which API when reading a mock-driven suite, choosing between strict expectations / ignored arguments / return-thru-pointer when authoring a new mock, and migrating a legacy fake-by-hand to a CMock-generated mock.

Mock / Stub / Spy / Fake in C

TermBehaviourCMock realisation
StubReturns canned values; doesn't fail the testfunc_IgnoreAndReturn(value) - no expectation, just a default return
Mock (strict)Verifies exact call + args; fails test if unmatchedfunc_Expect(args) / func_ExpectAndReturn(args, ret)
SpyRecords calls for later inspectionfunc_AddCallback(cb) - callback that records into test-scope state
FakeLightweight reimplementationfunc_Stub(impl) - replace the function with a custom C implementation

The English vocabulary is from Meszaros's xUnit Test Patterns (book, cite by ISBN 978-0131495050); CMock encodes all four shapes through one generated family.

What CMock generates from a header

Given parser.h:

int parse_message(const char *buf, size_t len, message_t *out);

CMock generates mock_parser.h and mock_parser.c. The generated API for parse_message is (per github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md (opens in new window)):

// Strict expectations
void parse_message_Expect(const char *buf, size_t len, message_t *out);
void parse_message_ExpectAndReturn(const char *buf, size_t len, message_t *out, int retval);

// Argument-flexibility (requires :expect_any_args plugin)
void parse_message_ExpectAnyArgs(void);
void parse_message_ExpectAnyArgsAndReturn(int retval);

// Array depth (requires :array plugin)
void parse_message_ExpectWithArray(const char *buf, int buf_depth, size_t len, message_t *out, int out_depth);
void parse_message_ExpectWithArrayAndReturn(const char *buf, int buf_depth, size_t len, message_t *out, int out_depth, int retval);

// Per-argument ignore (requires :ignore_arg plugin)
void parse_message_IgnoreArg_buf(void);
void parse_message_IgnoreArg_len(void);
void parse_message_IgnoreArg_out(void);

// Whole-function ignore (requires :ignore or :ignore_stateless plugin)
void parse_message_Ignore(void);
void parse_message_IgnoreAndReturn(int retval);
void parse_message_StopIgnore(void);

// Pointer-return injection (requires :return_thru_ptr plugin)
void parse_message_ReturnThruPtr_out(message_t *val_to_return);
void parse_message_ReturnArrayThruPtr_out(message_t *val_to_return, int len);
void parse_message_ReturnMemThruPtr_out(message_t *val_to_return, size_t size);

// Callback / stub (requires :callback plugin)
void parse_message_AddCallback(CMOCK_parse_message_CALLBACK callback);
void parse_message_Stub(CMOCK_parse_message_CALLBACK callback);

// Exception throwing (requires :cexception plugin)
void parse_message_ExpectAndThrow(const char *buf, size_t len, message_t *out, CEXCEPTION_T value_to_throw);

All of the above family names are cited from the CMock Summary doc above. The generated function names follow the rigid pattern <original_function_name>_<CMockVerb>[_<paramName>].

cmock.yml :plugins list

Each generated API family is gated on a plugin in cmock.yml (typically inlined into Ceedling's project.yml under :cmock: :plugins: - per the same CMock summary doc):

PluginEnablesWhen to enable
:ignore_Ignore, _IgnoreAndReturn, _StopIgnore (stateful)When you want unmatched calls to a function to pass after _Ignore is called
:ignore_statelessSame API as :ignore but no per-test stateFaster; use when ignored functions are uninteresting
:ignore_arg_IgnoreArg_<param>When a specific argument isn't part of the assertion
:expect_any_args_ExpectAnyArgs, _ExpectAnyArgsAndReturnWhen call count matters but args don't
:array_ExpectWithArray, _ReturnArrayThruPtr_<param>When arguments are pointer-to-array of known depth
:callback_AddCallback, _StubWhen you need to capture calls or substitute a fake
:cexception_ExpectAndThrowWhen the module under test propagates CException throws
:return_thru_ptr_ReturnThruPtr_<param>, _ReturnMemThruPtr_<param>When the mocked function writes through an out-pointer

A minimal cmock.yml for a typical embedded suite:

:cmock:
  :mock_prefix: Mock
  :mock_suffix: ""
  :plugins:
    - :ignore
    - :ignore_arg
    - :expect_any_args
    - :array
    - :callback
    - :return_thru_ptr

:cexception is added only if the project uses CException.

Mock-naming convention

Per the CMock summary doc, the mock module name is built from :mock_prefix + original module name + :mock_suffix. With the default :mock_prefix: Mock:

OriginalGenerated mock module
parser.hMockparser.h and Mockparser.c
i2c_driver.hMocki2c_driver.h and Mocki2c_driver.c

In a test (test_consumer.c), include the mock header:

#include "unity.h"
#include "Mockparser.h"      // CMock-generated
#include "consumer.h"        // under test

Ceedling automatically detects the Mock* include and generates the mock at test-build time.

Test lifecycle: setUp / tearDown / resetTest

CMock hooks into Unity's per-test lifecycle:

HookWhat CMock does
setUp()(Optional) Mockparser_Init() clears prior expectations - Ceedling generates this automatically when test_runner is generated
tearDown()Mockparser_Verify() asserts every queued expectation was matched; fails the test via Unity assertion if not
resetTest() (mid-test)Per the CMock summary doc, "Call it during a test to have CMock validate everything to this point and start over clean" - useful for staged interaction tests

Forgetting to register a mock causes link errors, not test failures - the mock is the only definition of the symbol.

Argument-matching modes

CMock's default mode is strict by argument: passed arguments must memcmp-equal the expected. The user softens with:

SoftenerEffect
_IgnoreArg_<param>This call's <param> is not checked
_ExpectAnyArgsNone of this call's args are checked
_Ignore / _IgnoreAndReturnAll subsequent calls to this function are ignored until _StopIgnore
Custom matcher via _AddCallbackInspect args programmatically and return a comparison

For pointer arguments to structs, the default is deep-equal by size - CMock memcompares the pointed-to memory. For string pointers, treat as strcmp only if the deep-equal of the buffer matches the string length CMock chose at generation; in practice, use _IgnoreArg_<param> + a _AddCallback for string-matching.

Worked example

A consumer module that calls a parser; the test verifies the expected call shape:

// consumer.h - under test
int consume(const char *raw);

// consumer.c
#include "parser.h"
int consume(const char *raw) {
    message_t m;
    if (parse_message(raw, strlen(raw), &m) != 0) return -1;
    return m.kind;
}

// test_consumer.c
#include "unity.h"
#include "Mockparser.h"
#include "consumer.h"

void test_consume_returns_kind_on_success(void) {
    message_t out_fixture = { .kind = 7 };

    parse_message_ExpectAndReturn("PING", 4, NULL, 0);
    parse_message_IgnoreArg_out();
    parse_message_ReturnThruPtr_out(&out_fixture);

    TEST_ASSERT_EQUAL_INT(7, consume("PING"));
}

void test_consume_returns_negative_one_on_parse_error(void) {
    parse_message_ExpectAnyArgsAndReturn(-1);
    TEST_ASSERT_EQUAL_INT(-1, consume("BADINPUT"));
}

The first test: strict on buf + len, lenient on out, then write the fixture through the out-pointer. The second: count matters, args don't.

CMock anti-patterns

Anti-patternWhy it failsFix
Mocking everythingTests become assertion-by-mock; refactors break dozens of mocksMock only the boundary (driver, peripheral, OS call); leave pure logic un-mocked
_ExpectAnyArgs everywhere"Test passes" but coverage of expected call shape is zeroUse strict _Expect for the interesting args; ignore only the noisy ones
_Ignore left on globallyReal defects in the SUT-mock interaction are hiddenPair _Ignore with a clear narrative comment; prefer _IgnoreAndReturn once-per-test
Mock-side state mutation through _StubTests rely on stub side-effects across calls; hard to reason aboutUse _AddCallback to record calls explicitly; assert on the record at the end
Forgetting to enable a plugin then using its APICompile fails on unknown functionAudit :cmock: :plugins: whenever a test uses a new _IgnoreArg_* or _ReturnThruPtr_*
Mocking standard-library functionsPulls libc into mock generation; coverage explodesWrap libc behind a project-owned header (e.g. time_provider.h) and mock that
Asserting Mockxxx_Init not calledInit is generated by Ceedling, not part of the API contractDon't assert on _Init / _Destroy; assert on the domain calls

CMock limitations

  • CMock parses C headers, not preprocessed source. Macros that hide function declarations are invisible - generators miss them.
  • Function pointers in structs need explicit handling. CMock generates a per-symbol mock; a function-pointer field in a struct is not a symbol and isn't directly mockable. Define a typed wrapper.
  • Vararg functions partially supported. Per the CMock summary doc, vararg mocking is limited; consider wrapping a vararg function before mocking.
  • Generated code grows with module size. A 200-function header produces ~200×8 generated functions; build time matters. Split the header.
  • Argument-deep-compare can be misleading on structs with pointer members. The pointed-to memory is not recursively compared - only the pointer value. Use _AddCallback for structures-with-pointers.
  • Not thread-safe. CMock's expectation queue is per-process global state; concurrent tests in the same process collide. Ceedling runs tests serially by default - keep it that way.

Ceedling project.yml schema

View source (opens in new window)

Ceedling project.yml schema

Per CeedlingPacket.md, the canonical top-level sections. Copy-paste template:

:project:
  :build_root: build/
  :release_build: TRUE
  :use_mocks: TRUE
  :use_exceptions: TRUE
  :use_test_preprocessor: :all
  :test_file_prefix: test_

:paths:
  :test:
    - test/**
  :source:
    - src/**
  :include:
    - inc/**
  :support: []
  :libraries: []

:files:
  # .c, .h, .o extension mappings - usually default values

:defines:
  :test:
    - UNITY_INCLUDE_DOUBLE
  :release:
    - NDEBUG

:flags:
  :release:
    :compile:
      '*':
        - -O2
        - -Wall
    :link:
      '*':
        - -Wl,--gc-sections
  :test:
    :compile:
      '*':
        - -O0
        - -g
        - --coverage
    :link:
      '*':
        - --coverage

:tools:
  # Override compiler / linker / preprocessor executables here

:test_runner:
  :includes:
    - "Mock*.h"

:unity:
  :defines:
    - UNITY_INCLUDE_DOUBLE

:cmock:
  :when_no_prototypes: :warn
  :enforce_strict_ordering: TRUE
  :plugins:
    - :ignore
    - :ignore_arg
    - :expect_any_args
    - :array
    - :callback
    - :return_thru_ptr

:cexception:
  :defines:
    - CEXCEPTION_T='signed char'

:gcov:
  :reports:
    - HtmlDetailed
    - Cobertura
  :gcovr:
    :report_root: src/

:plugins:
  :enabled:
    - report_tests_pretty_stdout
    - report_tests_junit_xml
    - gcov
    - module_generator

Section notes

  • :project holds the global on/off switches: :use_mocks, :use_exceptions, :test_file_prefix, :build_root.
  • :paths supports glob patterns; '*' under :flags matches all files.
  • :defines splits test-only vs release-only -D symbols (UNITY_INCLUDE_DOUBLE is a test-only define).
  • :tools is where a cross-build overrides the compiler to arm-none-eabi-gcc.
  • :unity sets Unity build-time defines, e.g. UNITY_INT_WIDTH=16 for 16-bit MCUs.
  • :cmock plugin list - see cmock.md (opens in new window).
  • :cexception type override; default is int, some MCUs prefer signed char.
  • :gcov report formats: HtmlDetailed, Cobertura, SonarQube.

Plugins

PluginEffect
report_tests_pretty_stdoutColoured terminal report
report_tests_junit_xmlJUnit XML at build/artifacts/test/report.xml - feeds CI dashboards
report_tests_log_factoryGeneric reporter - emit multiple formats at once
gcovCoverage via gcov + gcovr (see embedded-coverage-strategy-reference)
module_generatorPowers ceedling module:create[<name>]
command_hooksRun shell commands at pre/post lifecycle points

Ceedling task reference

View source (opens in new window)

Ceedling task reference

Per the CeedlingPacket task reference. The SKILL spine keeps ceedling test:all, ceedling gcov:all, and the compound CI invocation; the full surface follows.

Test tasks

ceedling test:all                          # Run every test_*.c
ceedling test:ringbuffer                   # Run only test_ringbuffer.c
ceedling test:pattern[ringbuffer]          # Regex match on test file basename
ceedling test:path[test/components]        # Tests under a path
ceedling test:ringbuffer --test-case=push  # Run only test cases matching 'push'

--test-case=<pattern> is the equivalent of GoogleTest's --gtest_filter.

Release build

ceedling release                # Build production binary
ceedling release:compile:foo.c  # Compile a single file

release is opt-in (:project: :release_build: TRUE in project.yml). Use it for the actual firmware build, not for tests.

Maintenance

ceedling clean        # Remove .o files
ceedling clobber      # Remove all generated files (build/, generated runners, mocks)
ceedling environment  # Print environment (CC, PATH, etc.)
ceedling dumpconfig   # Print the merged project.yml
ceedling help         # Task list
ceedling version      # Ceedling version

dumpconfig is the reliable way to debug mysterious flag behaviour - Ceedling merges several layers (defaults, project, plugin) and the final flags can surprise.

Compound tasks

Tasks chain on the command line:

ceedling clobber test:all release gcov:all
# Clean -> run tests -> build release -> produce coverage report

This is the canonical CI invocation.

Related skills

embedded-coverage-strategy-reference

Pure-reference catalog of code-coverage strategy for embedded C/C++: the criteria hierarchy (statement / branch / decision / condition / MC/DC), the gcov toolchain (.gcno/.gcda, --coverage), the LLVM source-based toolchain (llvm-profdata / llvm-cov), host-build vs QEMU-build instrumentation, MISRA-C:2012 and DO-178C structural-coverage expectations by safety level (DAL A maps to MC/DC), and report-format choices. Use when choosing what structural-coverage level to require and wiring gcov / llvm-cov into the build; physical .gcda retrieval from hardware is in hardware-in-loop-reference, QEMU machine flags in qemu-system-test-runner, and to author the embedded tests themselves use googletest-embedded-arm or unity-test-framework-c.

googletest-embedded-arm

Author and run GoogleTest 1.17+ for embedded C++ on ARM targets - TEST() / TEST_F() / TEST_P() / TYPED_TEST(), EXPECT_* vs ASSERT_* assertions, fixtures with SetUp() / TearDown(), value-parameterised tests, GoogleMock when paired, cross-compile with arm-none-eabi-g++, run on host or under QEMU via the qemu-system-test-runner skill, --gtest_filter / --gtest_output=xml:results.xml / --gtest_shuffle / --gtest_repeat command-line flags, and XML / JSON output parsing for CI. Use when the unit-under-test is C++ (modern C++17+) and the team wants the de-facto C++ test framework instead of the C-only Unity. For C use unity-test-framework-c; for pure C mocks see the CMock reference in ceedling-build-runner.

hardware-in-loop-reference

Pure-reference catalog of hardware-in-the-loop (HIL) testing for embedded systems. Defines the HIL pattern (ECU-under-test + real-time plant simulator + I/O cards emulating sensors / actuators / buses), the V-cycle progression (MIL → SIL → PIL → HIL), the canonical vendor stack (NI VeriStand + PXI / CompactRIO, dSPACE SCALEXIO / MicroAutoBox, Vector CANoe + VT System, Speedgoat real-time targets), bus emulation per protocol (CAN / CAN FD / LIN / FlexRay / Automotive Ethernet / SOME-IP), fault-injection patterns (short-to-ground, open-circuit, signal corruption), DO-178C / ISO 26262 / IEC 61508 alignment, and the test-evidence chain HIL produces. Use as the HIL terminology + vendor + standard reference when scoping an embedded test rig or interpreting an automotive / aerospace / industrial HIL test report.

qemu-system-test-runner

Author and run QEMU system emulation as an embedded-test target - qemu-system-arm / qemu-system-aarch64 / qemu-system-riscv32 launching cross-compiled ELF binaries on virtual MCUs and SoCs. Covers machine selection (-M virt / mps2-an385 / mps2-an386 / mps2-an500 / mps2-an511 / mps3-an524 / lm3s6965evb / raspi3b / xilinx-zynq-a9), CPU selection (-cpu cortex-m0 / cortex-m3 / cortex-m4 / cortex-m33 / cortex-a15 / cortex-a57 / max), -kernel ELF load, -nographic + -serial stdio, ARM semihosting via -semihosting-config enable=on,target=native (so cross-compiled GoogleTest / Unity binaries print to host stdio and exit with the test return code), GDB stub via -S -gdb tcp::1234, QMP monitor via -qmp tcp:host:port for automated test orchestration, and CI wiring. Use when host-only test runs are insufficient and the team wants arch-correct (endianness / alignment / interrupt-vector) behaviour on a virtual MCU without committing to physical hardware-in-loop.

unity-test-framework-c

Author and run ThrowTheSwitch Unity (the C unit-testing library) for bare-metal and RTOS C code. Distinct from the Unity game-engine Test Framework at docs.unity3d.com: this is the ThrowTheSwitch C testing library at throwtheswitch.org/unity, a single C file plus headers that runs on 8-bit MCUs through 64-bit hosts. Anchored on the Unity assertion API and configuration macros regardless of execution environment: the TEST_ASSERT_EQUAL_* / _FLOAT / _DOUBLE / _STRING / _MEMORY / _BITS assertion families, setUp/tearDown/RUN_TEST/UNITY_BEGIN/UNITY_END semantics and the exit-code contract, the generate_test_runner.rb generator, build-time config defines (UNITY_INCLUDE_DOUBLE, UNITY_OUTPUT_CHAR, UNITY_EXCLUDE_SETJMP), and CI integration via Ceedling JUnit XML; applies to host builds, cross-builds, and QEMU-run targets alike. For QEMU machine flags, semihosting, and exit-code capture, see qemu-system-test-runner. Use when the unit-under-test is pure C and the target ranges from 8-bit AVR to Cortex-M0 to Linux ARM.