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.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
This skill covers the Ceedling build orchestration - the
ceedling command-line tool, project.yml schema, and rake
tasks, per
throwtheswitch.org/ceedling.
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.
qemu-system-test-runner) - Ceedling handles the host pipeline natively.If the unit-under-test is C++ instead of C, prefer
googletest-embedded-arm;
Ceedling does not target C++.
Per the Ceedling README and the command-line reference at github.com/ThrowTheSwitch/Ceedling/.../getting-started/command-line.md:
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 + CExceptionA 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
- gcovThe 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.
ceedling module:create[ringbuffer]
# Creates src/ringbuffer.c, src/ringbuffer.h, test/test_ringbuffer.cThe generator emits the canonical skeleton - production header /
source with includes wired, test file with setUp / tearDown /
one test_ringbuffer_NeedToImplement placeholder.
ceedling test:all # Run every test_*.c
ceedling test:ringbuffer # Run only test_ringbuffer.c
ceedling gcov:all # Run under gcov instrumentation + generate reportceedling 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 reportThe full task surface (test:pattern, test:path,
--test-case, release, clean / clobber, environment,
dumpconfig) is in
references/task-reference.md.
-------------------
OVERALL TEST SUMMARY
-------------------
TESTED: 47
PASSED: 46
FAILED: 1
IGNORED: 0Failures are reported per assertion with file:line:test:reason:
test/test_ringbuffer.c:48:test_overflow_returns_minus_one:FAIL: Expected -1 Was 0With 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.
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.xmlFor 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-pattern | Why it fails | Fix |
|---|---|---|
Editing the generated *_Runner.c files | Regenerated on every build; edits lost | Edit the source test_*.c; the runner is derived |
:enforce_strict_ordering: FALSE to "make tests pass" | Mock-call-order bugs become invisible | Keep :enforce_strict_ordering: TRUE; fix the SUT's call shape |
Commit build/ artifacts | Repo bloat; merges conflict on generated files | .gitignore build/ always |
Skipping ceedling clobber in CI | Stale mocks survive header changes; ghost test failures | Always clobber before the CI run |
| Mixing test compile flags with release flags | --coverage in release build inflates the binary | Keep :flags: :test: separate from :flags: :release: |
Naming a test file _test.c instead of test_*.c | Default :test_file_prefix: test_ won't pick it up | Match the prefix or change the project.yml setting |
Using :use_mocks: FALSE then including a Mock*.h | CMock isn't invoked; link fails on the mock symbol | Either 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 rename | Read it from ceedling environment |
googletest-embedded-arm
with CMake.:flags schema's
glob keys are powerful but error-prone - dumpconfig is the
only reliable verifier.test_* runs
serially. Parallelise across multiple test:path[...] jobs in
CI if needed.:plugins: :load_paths: in project.yml. Ceedling's
error messages on missing plugins are terse.Cited inline. Foundational documents:
unity-test-framework-c,
embedded-coverage-strategy-reference,
qemu-system-test-runner,
googletest-embedded-arm.