Embedded C test runner with cross-compilation support
Project description
vyperling
Embedded C unit test runner with cross-compilation support.
A pip-installable replacement for Ceedling โ no Ruby dependencies, modern Python-first design.
Overview
vyperling (vpl for short) is a complete embedded C testing framework that:
- ๐ Auto-discovers
test_*.cfiles in your test directory - ๐ฏ Generates CMock-style mocks from C headers using pycparser + Jinja2
- ๐จ Cross-compiles for native (GCC) and embedded targets (ARM, MIPS, RISC-V, AVR) with parallel jobs
- ๐ฅ๏ธ Executes natively or under emulation (QEMU, simavr)
- ๐ Parses Unity test output with rich terminal reporting
- ๐ Generates coverage reports (gcov) for native targets
- ๐ Exports JUnit XML for CI/CD integration
Perfect for firmware development, embedded systems testing, and hardware validation workflows.
Quick Start
1. Install
pip install vyperling
# Alias 'vpl' is registered automatically
vpl --version
Development (editable install from source):
git clone https://github.com/ericsonjoseph/vyperling.git
cd vyperling
pip install -e .
2. Create a Project
vpl new myproject
cd myproject
This scaffolds:
forge.ymlโ project configurationsrc/โ source files under testtest/โ test files (test_*.c pattern)mocks/โ auto-generated mocks
3. Run Tests
# Test native target (default)
vpl test
# Cross-compile for MIPS32
vpl test --target mips32
# Run specific tests (pattern match)
vpl test -k uart
# Parallel compilation (4 jobs)
vpl test -j4
# Generate coverage report (native only)
vpl test --coverage
# Export JUnit XML for CI
vpl test --output junit
Commands Reference
All commands use vyperling or vpl interchangeably.
vpl test โ Discover, Compile, Run, Report
vpl test [OPTIONS]
OPTIONS:
--target TEXT Toolchain target (default: targets.default from forge.yml)
-k, --filter PATTERN Run only tests matching PATTERN
-j, --jobs N Parallel compile jobs (default: 1)
--coverage Enable gcov coverage (native target only)
--output FORMAT Export results: junit
-v, --verbose Print every compiler command
--no-mock Skip automatic mock generation
Example: Test UART module with coverage on 4 parallel jobs:
vpl test -k uart -j4 --coverage
vpl mock โ Generate Mocks from Headers
Generate CMock-style mocks from C header files:
vpl mock src/uart.h src/spi.h
# Or mock all headers in configured source dirs
vpl mock --all
# Use a specific toolchain's preprocessor
vpl mock --target arm-cortex-m4 src/uart.h
Output: mocks/mock_uart.{c,h} and mocks/mock_spi.{c,h}
vpl build โ Compile Only (No Execution)
vpl build [OPTIONS]
OPTIONS:
--target TEXT Toolchain target
-j, --jobs N Parallel compile jobs
-v, --verbose Print compiler commands
--no-mock Skip mock generation
Useful for checking compilation without running tests:
vpl build --target arm-cortex-m4 --verbose
vpl targets โ List Available Toolchains
vpl targets
Shows:
- Built-in targets (native, mips32, mips32el, arm-cortex-m4, riscv32, avr)
- Project-defined targets (from
forge.yml)
vpl clean โ Remove Build Artifacts
vpl clean # Remove all build directories
vpl clean --target mips32 # Remove only target's build dir
vpl --version / vpl --help
Show version or full command help.
Cross-Compilation Targets
| Target | Triplet | Emulator | Install (Debian/Ubuntu) |
|---|---|---|---|
native |
(native) | direct exec | (included with GCC) |
mips32 |
mips-linux-gnu |
QEMU | gcc-mips-linux-gnu qemu-user |
mips32el |
mipsel-linux-gnu |
QEMU | gcc-mipsel-linux-gnu qemu-user |
arm-cortex-m4 |
arm-none-eabi |
QEMU | gcc-arm-none-eabi qemu-user |
riscv32 |
riscv64-unknown-elf |
QEMU | gcc-riscv64-unknown-elf qemu-user |
avr |
avr |
simavr | gcc-avr avr-libc simavr |
Installation on Ubuntu/Debian:
# Native (GCC)
sudo apt install gcc build-essential
# MIPS cross-compile
sudo apt install gcc-mips-linux-gnu qemu-user
# ARM Cortex-M4 (bare-metal)
sudo apt install gcc-arm-none-eabi qemu-user
# RISC-V
sudo apt install gcc-riscv64-unknown-elf qemu-user
# AVR (Arduino)
sudo apt install gcc-avr avr-libc simavr
Custom Toolchains
Define custom targets in forge.yml:
project:
name: myproject
toolchains:
custom-arm:
description: "Custom ARM GCC 12.2"
cc: "arm-linux-gcc-12.2"
ar: "arm-linux-ar-12.2"
cflags: ["-mcpu=cortex-a7", "-mfloat-abi=hard"]
emulator: qemu-arm-static
sysroot: /path/to/sysroot
targets:
default: native
Configuration (forge.yml)
Minimal required:
project:
name: MyProject
Full example with all options:
project:
name: my-firmware
src_dirs:
- src
- lib/hal
test_dir: test
include_dirs:
- src
- lib/hal/include
build_dir: build
mock_dir: mocks
targets:
default: native
compiler:
extra_cflags:
- -Wall
- -Wextra
- -pedantic
defines:
- DEBUG=1
- VERSION=1.0.0
toolchains:
custom-mcu:
description: "STM32 Cross-Compile"
cc: arm-none-eabi-gcc
ar: arm-none-eabi-ar
cflags:
- -mcpu=cortex-m4
- -mthumb
emulator: qemu-arm-static
Project Layout
After vpl new myproject:
myproject/
โโโ forge.yml # Project configuration
โโโ src/ # Source files under test
โ โโโ uart.h
โ โโโ uart.c
โ โโโ spi.c
โโโ test/ # Unit tests
โ โโโ test_uart.c
โ โโโ test_spi.c
โโโ mocks/ # Auto-generated mocks (created by 'vpl mock')
โโโ mock_uart.h
โโโ mock_uart.c
โโโ mock_spi.h
โโโ mock_spi.c
Test File Pattern
Tests use the test_*.c pattern. Each test file:
- Includes
unity.h(provided by vyperling) - Includes mocks via
#include "mock_<dependency>.h" - Defines test cases with
void test_<name>(void)
Example: test/test_uart.c
#include "unity.h"
#include "uart.h"
#include "mock_gpio.h"
void setUp(void) {
// Called before each test
}
void tearDown(void) {
// Called after each test
}
void test_uart_init_should_configure_pins(void) {
gpio_init_Expect();
uart_init();
TEST_ASSERT_TRUE(1);
}
void test_uart_send_should_transmit_byte(void) {
uart_send(0x42);
TEST_ASSERT_EQUAL_INT(0x42, last_byte_sent);
}
Test Framework
vyperling uses:
- Unity โ lightweight C assertion framework (ThrowTheSwitch)
- CMock โ automated mocking for C functions (auto-generated via
vpl mock) - pycparser โ C header parser for mock generation
Assertion Macros
Unity provides rich assertions:
// Basic checks
TEST_ASSERT_TRUE(condition)
TEST_ASSERT_FALSE(condition)
TEST_ASSERT_NULL(ptr)
TEST_ASSERT_NOT_NULL(ptr)
// Equality
TEST_ASSERT_EQUAL_INT(expected, actual)
TEST_ASSERT_EQUAL_UINT(expected, actual)
TEST_ASSERT_EQUAL_HEX(expected, actual)
TEST_ASSERT_EQUAL_STRING(expected, actual)
// Arrays
TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, len)
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)
// Floating point
TEST_ASSERT_EQUAL_FLOAT(expected, actual, delta)
See Unity documentation for complete reference.
Architecture
vyperling's pipeline flows through 8 independent modules, connected by dataclasses:
TestUnit โโโ CompileResult โโโ RunResult
| Module | Responsibility |
|---|---|
| config.py | Load forge.yml, resolve paths, manage project settings |
| toolchains.py | Maintain toolchain registry (builtin + user-defined) |
| discoverer.py | Glob test_*.c, match source files, emit TestUnit list |
| mockgen.py | Parse headers with pycparser, generate mocks via Jinja2 |
| compiler.py | Invoke GCC with ThreadPoolExecutor, cache via .forge_deps.json |
| runner.py | Execute binaries natively or under QEMU/simavr, parse Unity output |
| reporter.py | Rich terminal tables + JUnit XML export |
| coverage.py | Generate gcov reports (native-only, best-effort) |
Key invariant: Each module emits structured output (dataclass) that the next consumes โ no hidden state.
Development
Install for Development
git clone https://github.com/ericsonjoseph/vyperling.git
cd vyperling
pip install -e .
Run Tests
# Full test suite (coverage on by default)
pytest
# Single test file
pytest tests/test_mockgen.py
# Single test by name
pytest -k test_cross_compile
# Disable coverage
pytest --no-cov
Project Structure
vyperling/โ main librarycli.pyโ Click entry point (commands: test, build, mock, clean, targets, new)config.pyโ forge.yml loader and config validationtoolchains.pyโ Toolchain dataclass + registrydiscoverer.pyโ test file discoverymockgen.pyโ C header parsing + mock generationcompiler.pyโ GCC invocation with caching and parallel jobsrunner.pyโ test execution and Unity output parsingreporter.pyโ rich terminal output + JUnit exportcoverage.pyโ gcov/gcovr pipelinescaffold.pyโ project template generationerrors.pyโ exception hierarchyunity.pyโ Unity C framework accessorc/โ vendored C assets (Unity v2.6.1 + forge_mock)templates/โ Jinja2 templates for mocks and scaffolding
tests/โ comprehensive Python test suiteDEVELOPMENT_PLAN.mdโ implementation checklist (all 17 steps โ )
Dependencies
Runtime:
click>=8.0โ CLI frameworkrich>=13.0โ terminal formattingpyyaml>=6.0โ YAML config parsingpycparser>=2.21โ C header parsingjinja2>=3.0โ template enginegcovr>=7.0โ coverage reporting
Development:
pytest>=7.0โ testingpytest-cov>=4.0โ coverage measurement
Known Limitations (v0.0.1)
- Mock generation: Skips variadic functions, function-pointer params, and incomplete struct-by-value params (warns during generation)
- Coverage: Native target only; requires GCC with
-fprofile-arcs -ftest-coveragesupport - Emulation: Timeout-based (default 30s per test binary)
Roadmap
| Phase | Items |
|---|---|
| v0.1 | โ Core pipeline (discover โ mock โ compile โ run โ report) |
| v0.2 | ๐ Enhanced mock generation (variadic support, callbacks) |
| v0.3 | ๐ CI/CD integration templates (GitHub Actions, GitLab CI) |
| v0.4 | ๐ IDE integration (VS Code extension) |
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Run the full test suite (
pytest) - Commit with conventional messages
- Push and open a pull request
For major changes, please open an issue first to discuss.
License
MIT โ see LICENSE file for details.
Credits
- vyperling by Ericson Joseph
- Unity testing framework by ThrowTheSwitch
- CMock concepts adapted from CMock
Support
- ๐ Architecture Documentation
- ๐ Issue Tracker
- ๐ฌ Discussions
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vyperling-0.0.1.tar.gz.
File metadata
- Download URL: vyperling-0.0.1.tar.gz
- Upload date:
- Size: 59.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1015-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e5553cea2598a302790c600d1571e6a7c3ec8c5dd36e37511a1fa0961bdebaf
|
|
| MD5 |
b0ab0e6c6848e48b50132a6517445741
|
|
| BLAKE2b-256 |
fbfc610f7f05bbd18e960e0b07d24a71a8c38776ed6bed3940960b2a0b48b32a
|
File details
Details for the file vyperling-0.0.1-py3-none-any.whl.
File metadata
- Download URL: vyperling-0.0.1-py3-none-any.whl
- Upload date:
- Size: 64.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1015-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5a9e73a16feb53423385699e3a8e5153bd66bcd577ea44ab6b08dcce64fcb77
|
|
| MD5 |
8043838800e2bc4e1a3998a9f9c79f0d
|
|
| BLAKE2b-256 |
6333c62359721afa1dd730b9e54c3d437969851b3b656e6b6fbe42045b033a4a
|