Compare commits
8 Commits
286c51b2e7
...
4269f65942
| Author | SHA1 | Date | |
|---|---|---|---|
| 4269f65942 | |||
| 657d154560 | |||
| 3ce42cc8db | |||
| 2d25ad3a10 | |||
| 2ba1d2f7b6 | |||
| 457b6651be | |||
| 95ec83a0fd | |||
| 79d3b92511 |
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Pi Skills
|
||||||
|
|
||||||
|
Project-level skills loaded by Pi on-demand.
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
| Skill | Description |
|
||||||
|
| ------------------------------ | ---------------------------------------------------- |
|
||||||
|
| [tdd](skills/tdd/SKILL.md) | TDD workflow with Unity and CMock |
|
||||||
|
| [cmake](skills/cmake/SKILL.md) | Build configuration, adding modules and dependencies |
|
||||||
|
| [git](skills/git/SKILL.md) | Commit messages, tagging, and branching workflow |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Skills are auto-discovered by Pi from `.pi/skills/`. No extra setup
|
||||||
|
required.
|
||||||
|
|
||||||
|
To install this project's skills into another project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pi install -l git:github.com/portersky/helloctdd
|
||||||
|
```
|
||||||
|
|
||||||
|
Or as a temporary package for the current session:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pi -e git:github.com/portersky/helloctdd
|
||||||
|
```
|
||||||
|
|
||||||
|
Pi auto-discovers `skills/` directories from installed packages.
|
||||||
|
|
||||||
|
## Adding a New Skill
|
||||||
|
|
||||||
|
Place a `SKILL.md` inside a directory under `.pi/skills/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
.pi/skills/
|
||||||
|
my-skill/
|
||||||
|
SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Pi validates the frontmatter and registers `/skill:my-skill` in the
|
||||||
|
TUI.
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
name: cmake
|
||||||
|
description: CMake build configuration for projects scaffolded from the ctdd
|
||||||
|
template. Use when adding modules, adding dependencies, configuring coverage
|
||||||
|
or sanitizers, or understanding the build system.
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMake Skill
|
||||||
|
|
||||||
|
> Replace `<src>/` with the project source directory (e.g. `ctdd/`, `src/`).
|
||||||
|
> Replace `<module>` with the module name (e.g. `counter`, `timer`).
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
| Action | Command |
|
||||||
|
| --------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| Configure | `cmake -S . -B build -G Ninja` |
|
||||||
|
| Build | `ninja -C build` |
|
||||||
|
| Run tests | `ninja -C build check` |
|
||||||
|
| Coverage | `cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON` then `ninja -C build-cov coverage` |
|
||||||
|
| ASan | `cmake -S . -B build-asan -G Ninja -DENABLE_ASAN=ON` then `ninja -C build-asan` |
|
||||||
|
|
||||||
|
> ASan and coverage cannot be used together.
|
||||||
|
|
||||||
|
## Adding a Module
|
||||||
|
|
||||||
|
Modules live in `<src>/` as static libraries. Register them in
|
||||||
|
`<src>/CMakeLists.txt`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
add_library(<src>_<module> <module>.c)
|
||||||
|
target_include_directories(<src>_<module> PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_compile_features(<src>_<module> PRIVATE c_std_23)
|
||||||
|
```
|
||||||
|
|
||||||
|
Link into `main` in the root `CMakeLists.txt`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_link_libraries(main PRIVATE ... <src>_<module>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a Dependency
|
||||||
|
|
||||||
|
Dependencies are fetched via custom `Find<name>.cmake` scripts in `deps/`.
|
||||||
|
`deps/` is on `CMAKE_MODULE_PATH` so `find_package()` resolves to these
|
||||||
|
scripts first.
|
||||||
|
|
||||||
|
### 1. Create `deps/Find<name>.cmake`
|
||||||
|
|
||||||
|
Pattern (follow `deps/FindUnity.cmake` or `deps/FindCMock.cmake`):
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
if (DEFINED _FIND<NAME>_INCLUDED)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
set(_FIND<NAME>_INCLUDED TRUE)
|
||||||
|
|
||||||
|
set(<NAME>_VERSION "x.y.z")
|
||||||
|
message(STATUS "Fetching <name> ${<NAME>_VERSION}")
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
<name>
|
||||||
|
URL https://github.com/org/<name>/archive/refs/tags/v${<NAME>_VERSION}.zip
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(<name>)
|
||||||
|
|
||||||
|
if (NOT TARGET <Name>::<Name>)
|
||||||
|
add_library(<Name>::<Name> ALIAS <name>)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(<Name>_FOUND TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
For libraries without CMakeLists.txt (like CMock), use
|
||||||
|
`FetchContent_Populate` and compile manually. See `FindCMock.cmake`
|
||||||
|
for the pattern.
|
||||||
|
|
||||||
|
### 2. Add `find_package(<Name> REQUIRED)` to `CMakeLists.txt`
|
||||||
|
|
||||||
|
Place it after `include(Platform)` and `include(Flags)`.
|
||||||
|
|
||||||
|
### 3. Link with `<Name>::<Name>`
|
||||||
|
|
||||||
|
In `<src>/CMakeLists.txt` or `tests/CMakeLists.txt` as needed:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_link_libraries(<src>_<module> PRIVATE <Name>::<Name>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform Flags
|
||||||
|
|
||||||
|
`deps/Platform.cmake` sets these variables for conditional logic:
|
||||||
|
|
||||||
|
| Variable | Meaning |
|
||||||
|
| ----------------- | ------------------------------------------------ |
|
||||||
|
| `IS_CLANG_OR_GCC` | Clang or GCC compiler |
|
||||||
|
| `IS_MSVC` | MSVC compiler |
|
||||||
|
| `IS_WINDOWS` | Windows target |
|
||||||
|
| `IS_LINUX` | Linux target |
|
||||||
|
| `IS_MACOS` | macOS target |
|
||||||
|
| `IS_IOS` | iOS target |
|
||||||
|
| `IS_ANDROID` | Android target |
|
||||||
|
| `IS_EMSCRIPTEN` | Emscripten/WASM target |
|
||||||
|
| `ARCH` | Normalized arch (`amd64`, `arm64`, `wasm`, etc.) |
|
||||||
|
| `IS_AMD64` | x86_64 |
|
||||||
|
| `IS_ARM64` | arm64/aarch64 |
|
||||||
|
|
||||||
|
## Compiler Flags
|
||||||
|
|
||||||
|
`deps/Flags.cmake` defines `BASE_OPTIONS` (warning flags) and
|
||||||
|
`BASE_DEFINITIONS`. Apply per-target to avoid polluting fetched
|
||||||
|
dependencies:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_compile_options(<target> PRIVATE ${BASE_OPTIONS})
|
||||||
|
target_compile_definitions(<target> PRIVATE ${BASE_DEFINITIONS})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CMock Mock Generation
|
||||||
|
|
||||||
|
`tests/CMakeLists.txt` provides a `cmock_generate_mock` helper function:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
cmock_generate_mock(<target> "<absolute-path-to-header>")
|
||||||
|
```
|
||||||
|
|
||||||
|
With optional config file:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
cmock_generate_mock(<target> "<header>" "cmock_config.yml")
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated mocks go into `build/mocks/` as `Mock<name>.h/.c`.
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
Enabled via `-DENABLE_COVERAGE=ON`. Requires GCC or Clang + `gcovr`.
|
||||||
|
Report at `build-cov/coverage/index.html`. Only `<src>/` sources are
|
||||||
|
measured; Unity, CMock, and mocks are excluded.
|
||||||
|
|
||||||
|
## Sanitizers
|
||||||
|
|
||||||
|
`deps/Sanitizers.cmake` provides AddressSanitizer via `-DENABLE_ASAN=ON`.
|
||||||
|
Incompatible with coverage.
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
name: git
|
||||||
|
description: Git workflow for the ctdd template project. Use when committing
|
||||||
|
changes, creating tags for releases, or following the branching and commit
|
||||||
|
message conventions.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Skill
|
||||||
|
|
||||||
|
## Commit Messages
|
||||||
|
|
||||||
|
Follow [Conventional Commits](https://www.conventionalcommits.org/) with the
|
||||||
|
50/72 rule.
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>: <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Subject:** max 50 characters, imperative mood, no period
|
||||||
|
- **Body:** wrapped at 72 characters, optional
|
||||||
|
- **Blank line** between subject and body
|
||||||
|
- **No co-author trailers** (`Co-Authored-By:` is forbidden)
|
||||||
|
- **No em dashes** (`—`). Use a colon or rewrite the sentence
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
| Type | Use |
|
||||||
|
| ---------- | ---------------------------------- |
|
||||||
|
| `feat` | New feature or module |
|
||||||
|
| `fix` | Bug fix |
|
||||||
|
| `docs` | Documentation only |
|
||||||
|
| `chore` | Build, deps, config, housekeeping |
|
||||||
|
| `ci` | CI/CD changes |
|
||||||
|
| `refactor` | Code change without feature or fix |
|
||||||
|
| `test` | Adding or changing tests |
|
||||||
|
| `style` | Formatting, whitespace, semicolons |
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add stopwatch timer
|
||||||
|
|
||||||
|
Replace Hello World with a live stopwatch that prints elapsed time
|
||||||
|
in HH:MM:SS.mmm format, updating every 10ms with color output.
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
fix: clear Unity INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
|
||||||
|
|
||||||
|
CMake rejects the path inside the build tree on newer versions.
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
chore: bump CMock to v2.6.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
added a new thing # no type, lowercase, vague
|
||||||
|
feat: add stopwatch # no body for a significant change
|
||||||
|
feat: add stopwatch timer. # trailing period
|
||||||
|
feat: add timer — it's fast # em dash not allowed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Committing
|
||||||
|
|
||||||
|
Stage changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add -A
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git commit -m "type: subject"
|
||||||
|
```
|
||||||
|
|
||||||
|
For multi-line messages:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git commit -m "type: subject
|
||||||
|
|
||||||
|
body line 1
|
||||||
|
body line 2"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tagging
|
||||||
|
|
||||||
|
This project uses semantic versioning for template releases.
|
||||||
|
|
||||||
|
### Creating a tag
|
||||||
|
|
||||||
|
Create the tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git tag -a v0.1.0 -m "Release v0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Push the tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git push origin v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag messages follow the same rules as commit messages: no em dashes,
|
||||||
|
wrapped at 72 characters.
|
||||||
|
|
||||||
|
### Tag naming
|
||||||
|
|
||||||
|
| Version | Meaning |
|
||||||
|
| -------- | ------------------------------------ |
|
||||||
|
| `v0.x.0` | Minor changes, breaking template API |
|
||||||
|
| `v0.x.y` | Patch changes, backward-compatible |
|
||||||
|
| `v1.x.0` | Major release |
|
||||||
|
|
||||||
|
### When to tag
|
||||||
|
|
||||||
|
- After merging a feature branch that changes the template structure
|
||||||
|
- Before sharing the template with others
|
||||||
|
- When CMakeLists.txt, deps/, or build system changes could affect
|
||||||
|
projects using this template
|
||||||
|
|
||||||
|
### Listing tags
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git tag -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting a tag
|
||||||
|
|
||||||
|
Delete the local tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git tag -d v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the remote tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git push origin --delete v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Branching
|
||||||
|
|
||||||
|
For local development:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git checkout -b feat/add-counter
|
||||||
|
```
|
||||||
|
|
||||||
|
Branch naming:
|
||||||
|
|
||||||
|
| Prefix | Use |
|
||||||
|
| -------- | ------------- |
|
||||||
|
| `feat/` | New feature |
|
||||||
|
| `fix/` | Bug fix |
|
||||||
|
| `chore/` | Housekeeping |
|
||||||
|
| `docs/` | Documentation |
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
| Command | Use |
|
||||||
|
| ----------------------- | --------------------------------- |
|
||||||
|
| `git log --oneline -10` | Recent history |
|
||||||
|
| `git diff --staged` | Check staged changes |
|
||||||
|
| `git status` | Current state |
|
||||||
|
| `git reset HEAD~1` | Undo last commit (keep changes) |
|
||||||
|
| `git rebase -i HEAD~3` | Interactive rebase last 3 commits |
|
||||||
|
|
||||||
|
## Checklist Before Committing
|
||||||
|
|
||||||
|
- [ ] Tests pass: `ninja -C build check`
|
||||||
|
- [ ] Commit message follows 50/72 rule
|
||||||
|
- [ ] Conventional commit type used
|
||||||
|
- [ ] No co-author trailers
|
||||||
|
- [ ] No em dashes
|
||||||
|
- [ ] No large unchanged regions reformatted
|
||||||
|
- [ ] No speculative code or unused imports
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
name: tdd
|
||||||
|
description: Test-driven development with Unity and CMock. Use when adding new
|
||||||
|
modules, writing tests, mocking dependencies, or following Red-Green-Refactor
|
||||||
|
in projects scaffolded from the ctdd template.
|
||||||
|
---
|
||||||
|
|
||||||
|
# TDD Skill
|
||||||
|
|
||||||
|
> Replace `<src>/` with the project source directory (e.g. `ctdd/`, `src/`).
|
||||||
|
> Replace `<module>` with the module name (e.g. `counter`, `timer`).
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Action | Command |
|
||||||
|
| ----------------------- | ------------------------------------------------------- |
|
||||||
|
| Configure (default) | `cmake -S . -B build -G Ninja` |
|
||||||
|
| Configure (coverage) | `cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON` |
|
||||||
|
| Build | `ninja -C build` |
|
||||||
|
| Run tests (full output) | `ninja -C build check` |
|
||||||
|
| Run tests (summary) | `ninja -C build test` |
|
||||||
|
| Run tests + coverage | `ninja -C build-cov coverage` |
|
||||||
|
|
||||||
|
## Red-Green-Refactor
|
||||||
|
|
||||||
|
Every change to `<src>/` follows this cycle:
|
||||||
|
|
||||||
|
1. **RED** : Write a failing test. Confirm it fails.
|
||||||
|
2. **GREEN** : Write the minimum code to pass. Confirm it passes.
|
||||||
|
3. **REFACTOR** : Clean up without changing behavior. Tests still pass.
|
||||||
|
|
||||||
|
Never skip the RED step. If the test passes immediately, you wrote the test
|
||||||
|
before the implementation was missing.
|
||||||
|
|
||||||
|
## Adding a New Module
|
||||||
|
|
||||||
|
### 1. Write the header
|
||||||
|
|
||||||
|
Create `<src>/<module>.h` with the public prototype(s).
|
||||||
|
|
||||||
|
C:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma once
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
int fn_name(int arg);
|
||||||
|
```
|
||||||
|
|
||||||
|
C++:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
auto fn_name(int arg) -> int;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Write the test
|
||||||
|
|
||||||
|
Create `tests/test_<module>.c`. Two patterns exist:
|
||||||
|
|
||||||
|
**State-based** (pure functions, no mocks):
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "unity.h"
|
||||||
|
#include "<src>/<module>.h"
|
||||||
|
|
||||||
|
void setUp(void) {}
|
||||||
|
void tearDown(void) {}
|
||||||
|
|
||||||
|
void test_fn_name_returns_expected(void) {
|
||||||
|
TEST_ASSERT_EQUAL_INT(42, fn_name(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_fn_name_returns_expected);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interaction-based** (mocking a dependency):
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "unity.h"
|
||||||
|
#include "<src>/<module>.h"
|
||||||
|
#include "Mock<dep>.h"
|
||||||
|
|
||||||
|
void setUp(void) { Mock<dep>_Init(); }
|
||||||
|
void tearDown(void) { Mock<dep>_Verify(); Mock<dep>_Destroy(); }
|
||||||
|
|
||||||
|
void test_fn_calls_dep(void) {
|
||||||
|
dep_fn_ExpectAndReturn(arg, expected);
|
||||||
|
TEST_ASSERT_TRUE(fn_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_fn_calls_dep);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Register the test in `tests/CMakeLists.txt`
|
||||||
|
|
||||||
|
Add after existing test targets, before the `check` target:
|
||||||
|
|
||||||
|
**State-based (no mock):**
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
add_executable(test_<module> test_<module>.c)
|
||||||
|
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_link_libraries(test_<module> PRIVATE <src>_<module> Unity::Unity)
|
||||||
|
target_compile_features(test_<module> PRIVATE c_std_23)
|
||||||
|
add_test(NAME test_<module> COMMAND test_<module>)
|
||||||
|
list(APPEND TEST_TARGETS test_<module>)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interaction-based (with mock):**
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
add_executable(test_<module> test_<module>.c)
|
||||||
|
target_include_directories(test_<module> PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_link_libraries(test_<module> PRIVATE <src>_<module> Unity::Unity CMock::CMock)
|
||||||
|
target_compile_features(test_<module> PRIVATE c_std_23)
|
||||||
|
cmock_generate_mock(test_<module> "${CMAKE_SOURCE_DIR}/<src>/<dep>.h")
|
||||||
|
add_test(NAME test_<module> COMMAND test_<module>)
|
||||||
|
list(APPEND TEST_TARGETS test_<module>)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Stub the implementation
|
||||||
|
|
||||||
|
Create `<src>/<module>.c` with a dummy return:
|
||||||
|
|
||||||
|
C:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "<src>/<module>.h"
|
||||||
|
|
||||||
|
int fn_name(int arg) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
C++:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "<src>/<module>.h"
|
||||||
|
|
||||||
|
auto fn_name(int arg) -> int {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Register the library in `<src>/CMakeLists.txt`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
add_library(<src>_<module> <module>.c)
|
||||||
|
target_include_directories(<src>_<module> PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
target_compile_features(<src>_<module> PRIVATE c_std_23)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Confirm RED
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ninja -C build check
|
||||||
|
```
|
||||||
|
|
||||||
|
The test must fail. If it does not, something is wrong.
|
||||||
|
|
||||||
|
### 6. Implement and confirm GREEN
|
||||||
|
|
||||||
|
Write the real implementation. Run tests again:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ninja -C build check
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests must pass.
|
||||||
|
|
||||||
|
## CMock Patterns
|
||||||
|
|
||||||
|
### Expecting a call with arguments
|
||||||
|
|
||||||
|
```c
|
||||||
|
dep_fn_Expect(arg1, arg2);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expecting a call and returning a value
|
||||||
|
|
||||||
|
```c
|
||||||
|
dep_fn_ExpectAndReturn(arg, expected_value);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verifying a call was NOT made
|
||||||
|
|
||||||
|
Do not set any `_Expect`. If the function is called, CMock fails
|
||||||
|
automatically in `tearDown`.
|
||||||
|
|
||||||
|
### Multiple calls
|
||||||
|
|
||||||
|
```c
|
||||||
|
dep_fn_ExpectAndReturn(first_call_arg, first_return);
|
||||||
|
dep_fn_ExpectAndReturn(second_call_arg, second_return);
|
||||||
|
```
|
||||||
|
|
||||||
|
Calls are matched in order.
|
||||||
|
|
||||||
|
## Test Naming
|
||||||
|
|
||||||
|
- `test_<function>_<scenario>`
|
||||||
|
- Use present tense: `test_upper_converts_lowercase`
|
||||||
|
- Cover: happy path, edge cases, boundaries, and error conditions
|
||||||
|
|
||||||
|
## Unity Assertions
|
||||||
|
|
||||||
|
| Assertion | Use |
|
||||||
|
| -------------------------------- | ---------------- |
|
||||||
|
| `TEST_ASSERT_TRUE(x)` | Boolean true |
|
||||||
|
| `TEST_ASSERT_FALSE(x)` | Boolean false |
|
||||||
|
| `TEST_ASSERT_EQUAL_INT(a, b)` | Integer equality |
|
||||||
|
| `TEST_ASSERT_EQUAL_STRING(a, b)` | String equality |
|
||||||
|
| `TEST_ASSERT_NULL(p)` | Null pointer |
|
||||||
|
| `TEST_ASSERT_NOT_NULL(p)` | Non-null pointer |
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
To check coverage after tests:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ninja -C build-cov coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `build-cov/coverage/index.html` in a browser.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Before considering a task done:
|
||||||
|
|
||||||
|
- [ ] Header declares the public API
|
||||||
|
- [ ] Test covers at least one happy path, one edge case
|
||||||
|
- [ ] Test registered in `tests/CMakeLists.txt`
|
||||||
|
- [ ] Library registered in `<src>/CMakeLists.txt` (if new module)
|
||||||
|
- [ ] `ninja -C build check` passes with zero failures
|
||||||
|
- [ ] No semicolons after closing braces
|
||||||
|
- [ ] Trailing return type on all functions
|
||||||
|
- [ ] East const (`char const*`)
|
||||||
|
- [ ] `snake_case` naming
|
||||||
@@ -2,8 +2,18 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
`ctdd` is a C23 project wired for test-driven development using
|
`ctdd` is a C23 template project wired for test-driven development
|
||||||
Unity and CMock.
|
using Unity and CMock. Use it as the core template when scaffolding
|
||||||
|
new projects.
|
||||||
|
|
||||||
|
For TDD workflows (adding modules, writing tests, mocking), load the
|
||||||
|
`tdd` skill with `/skill:tdd`.
|
||||||
|
|
||||||
|
For build configuration (dependencies, coverage, sanitizers), load the
|
||||||
|
`cmake` skill with `/skill:cmake`.
|
||||||
|
|
||||||
|
For committing, tagging, and branching, load the `git` skill with
|
||||||
|
`/skill:git`.
|
||||||
|
|
||||||
## Build System
|
## Build System
|
||||||
|
|
||||||
@@ -11,92 +21,47 @@ Unity and CMock.
|
|||||||
- **CMake minimum:** 3.21
|
- **CMake minimum:** 3.21
|
||||||
- **C standard:** C23
|
- **C standard:** C23
|
||||||
|
|
||||||
### Commands
|
|
||||||
|
|
||||||
Configure (default):
|
|
||||||
```sh
|
|
||||||
cmake -S . -B build -G Ninja
|
|
||||||
```
|
|
||||||
|
|
||||||
Configure with coverage:
|
|
||||||
```sh
|
|
||||||
cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON
|
|
||||||
```
|
|
||||||
|
|
||||||
Build:
|
|
||||||
```sh
|
|
||||||
ninja -C build
|
|
||||||
```
|
|
||||||
|
|
||||||
Run tests (full Unity output, colored):
|
|
||||||
```sh
|
|
||||||
ninja -C build check
|
|
||||||
```
|
|
||||||
|
|
||||||
Run tests (CTest summary only):
|
|
||||||
```sh
|
|
||||||
ninja -C build test
|
|
||||||
```
|
|
||||||
|
|
||||||
Run tests and generate coverage HTML:
|
|
||||||
```sh
|
|
||||||
ninja -C build-cov coverage
|
|
||||||
```
|
|
||||||
|
|
||||||
Run (Linux/macOS):
|
|
||||||
```sh
|
|
||||||
./build/main
|
|
||||||
```
|
|
||||||
|
|
||||||
Run (Windows):
|
|
||||||
```sh
|
|
||||||
./build/main.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
Dependencies are managed via custom `Find*.cmake` scripts in `deps/`.
|
Dependencies are managed via custom `Find*.cmake` scripts in `deps/`.
|
||||||
These scripts use `FetchContent` under the hood to download and build
|
See `/skill:cmake` for adding new dependencies.
|
||||||
libraries automatically.
|
|
||||||
|
|
||||||
To add a new dependency:
|
|
||||||
|
|
||||||
1. Add the corresponding `Find<name>.cmake` to `deps/`
|
|
||||||
2. Add `find_package(<name> REQUIRED)` to `CMakeLists.txt`
|
|
||||||
3. Link with `<name>::<name>` in `target_link_libraries()`
|
|
||||||
|
|
||||||
### CMake Module Path
|
|
||||||
|
|
||||||
`deps/` is added to `CMAKE_MODULE_PATH` so `find_package()` resolves
|
|
||||||
to the custom scripts instead of system-installed packages.
|
|
||||||
|
|
||||||
## Coding Conventions
|
## Coding Conventions
|
||||||
|
|
||||||
|
### C
|
||||||
|
|
||||||
- **Language:** C23
|
- **Language:** C23
|
||||||
- **Trailing return type** for function signatures (e.g. `auto fn() -> void`)
|
|
||||||
- **4-space indentation**
|
|
||||||
- **No semicolons after closing braces** for namespaces/classes
|
|
||||||
- `auto` for obvious types (e.g. `auto main(...) -> int`)
|
|
||||||
- **East const** (e.g. `char const*` not `const char*`)
|
- **East const** (e.g. `char const*` not `const char*`)
|
||||||
|
- **4-space indentation**
|
||||||
|
- `<>` includes for system headers (C stdlib, OS, etc.)
|
||||||
|
- `""` includes for third-party and local headers
|
||||||
|
- **Naming:** `snake_case` for variables, functions, and structs
|
||||||
|
- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants
|
||||||
|
|
||||||
|
### C++
|
||||||
|
|
||||||
|
- **Trailing return type** for function signatures
|
||||||
|
(e.g. `auto fn() -> void`)
|
||||||
|
- `auto` for obvious types (e.g. `auto main(...) -> int`)
|
||||||
|
- **No semicolons after closing braces** for namespaces/classes
|
||||||
- **Trailing return type** for all function definitions, including
|
- **Trailing return type** for all function definitions, including
|
||||||
operators (e.g. `auto operator=(T&&) noexcept -> T&`)
|
operators (e.g. `auto operator=(T&&) noexcept -> T&`)
|
||||||
- **Public members first** in class declarations, private members at the
|
- **Public members first** in class declarations, private members at
|
||||||
bottom
|
the bottom
|
||||||
- `<>` includes only for system headers (std, OS, etc.)
|
|
||||||
- `""` includes for third-party dependencies (e.g. `fmt`,
|
### Include Order
|
||||||
`nlohmann/json`)
|
|
||||||
- **Naming:** `snake_case` for variables, functions, and classes
|
Both C and C++ follow the same include order:
|
||||||
- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants
|
|
||||||
- Include order:
|
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
|
||||||
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
|
2. *(blank line)*
|
||||||
2. *(blank line)*
|
3. C standard library headers (`<stdlib.h>`, `<string.h>`, etc.)
|
||||||
3. C standard library headers (`<stdlib.h>`, `<string.h>`, etc.)
|
4. *(blank line)*
|
||||||
4. *(blank line)*
|
5. OS-specific headers (Windows API, POSIX, etc.)
|
||||||
5. OS-specific headers (Windows API, POSIX, etc.)
|
6. *(blank line)*
|
||||||
6. *(blank line)*
|
7. Third-party dependencies (`"fmt/core.h"`, etc.)
|
||||||
7. Third-party dependencies (`"fmt/core.h"`, etc.)
|
8. *(blank line)*
|
||||||
8. *(blank line)*
|
9. Local/project headers
|
||||||
9. Local/project headers
|
|
||||||
|
|
||||||
## Shell Scripts
|
## Shell Scripts
|
||||||
|
|
||||||
@@ -108,23 +73,7 @@ to the custom scripts instead of system-installed packages.
|
|||||||
|
|
||||||
## Commit Messages
|
## Commit Messages
|
||||||
|
|
||||||
- Follow the 50/72 rule:
|
See `/skill:git` for commit message conventions, types, and examples.
|
||||||
- Subject line: max 50 characters
|
|
||||||
- Body lines: wrapped at 72 characters
|
|
||||||
- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`,
|
|
||||||
etc.)
|
|
||||||
- Separate subject from body with a blank line
|
|
||||||
- Do **not** add yourself as a co-author (`Co-Authored-By:` trailers are
|
|
||||||
forbidden)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
feat: add stopwatch timer
|
|
||||||
|
|
||||||
Replace Hello World with a live stopwatch that prints elapsed time
|
|
||||||
in HH:MM:SS.mmm format, updating every 10ms with color output.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation (Markdown)
|
## Documentation (Markdown)
|
||||||
|
|
||||||
@@ -150,67 +99,22 @@ in HH:MM:SS.mmm format, updating every 10ms with color output.
|
|||||||
|
|
||||||
## Source Layout
|
## Source Layout
|
||||||
|
|
||||||
```text
|
```
|
||||||
ctdd/
|
ctdd/
|
||||||
str.h / str.c Pure string utilities (no dependencies)
|
str.h / str.c Pure string utilities (no dependencies)
|
||||||
report.h / report.c Formats a value and calls log_message()
|
report.h / report.c Formats a value and calls log_info()
|
||||||
logger.h / logger.c Real log_message via printf to stdout
|
logger.h / logger.c Log levels, emits via log_write()
|
||||||
main.c Entry point
|
log_write.h / log_write.c Real log_write via printf to stdout
|
||||||
|
main.c Entry point (template placeholder)
|
||||||
tests/
|
tests/
|
||||||
test_str.c Unity state-based tests for ctdd/str
|
test_str.c Unity state-based tests for ctdd/str
|
||||||
test_report.c Interaction-based tests using CMock
|
test_report.c Interaction-based tests using CMock
|
||||||
|
test_logger.c Interaction-based tests using CMock
|
||||||
deps/
|
deps/
|
||||||
FindUnity.cmake Fetches Unity v2.6.1 via ZIP
|
FindUnity.cmake Fetches Unity v2.6.1 via ZIP
|
||||||
FindCMock.cmake Fetches CMock v2.6.0 via ZIP
|
FindCMock.cmake Fetches CMock v2.6.0 via ZIP
|
||||||
```
|
```
|
||||||
|
|
||||||
## TDD Workflow
|
|
||||||
|
|
||||||
This project follows Red-Green-Refactor. All changes to testable source
|
|
||||||
files under `ctdd/` should be test-driven: write a failing test first,
|
|
||||||
then implement.
|
|
||||||
|
|
||||||
### Adding a new module
|
|
||||||
|
|
||||||
1. Create `ctdd/<module>.h` with the public prototype.
|
|
||||||
2. Create `tests/test_<module>.c`. Set CMock expectations for any
|
|
||||||
dependency calls, then assert the result.
|
|
||||||
3. Register the test in `tests/CMakeLists.txt`:
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
add_executable(test_module test_module.c)
|
|
||||||
target_include_directories(test_module PRIVATE "${CMAKE_SOURCE_DIR}")
|
|
||||||
target_link_libraries(test_module PRIVATE ctdd_module Unity::Unity CMock::CMock)
|
|
||||||
target_compile_features(test_module PRIVATE c_std_23)
|
|
||||||
cmock_generate_mock(test_module "${CMAKE_SOURCE_DIR}/ctdd/dep.h")
|
|
||||||
add_test(NAME test_module COMMAND test_module)
|
|
||||||
list(APPEND TEST_TARGETS test_module)
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Stub `ctdd/<module>.c` with a dummy return, confirm RED, implement,
|
|
||||||
confirm GREEN:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ninja -C build check
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mocking a dependency
|
|
||||||
|
|
||||||
Use `cmock_generate_mock` in the test target to generate a mock from a
|
|
||||||
header. Include `Mock<name>.h` in the test and use the generated API:
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "Mockdep.h"
|
|
||||||
|
|
||||||
void setUp(void) { Mockdep_Init(); }
|
|
||||||
void tearDown(void) { Mockdep_Verify(); Mockdep_Destroy(); }
|
|
||||||
|
|
||||||
void test_something(void) {
|
|
||||||
dep_fn_ExpectAndReturn(arg, expected);
|
|
||||||
TEST_ASSERT_TRUE(module_do_thing());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behavioral Guidelines
|
## Behavioral Guidelines
|
||||||
|
|
||||||
Reduce common LLM coding mistakes. Bias toward caution over speed.
|
Reduce common LLM coding mistakes. Bias toward caution over speed.
|
||||||
@@ -247,8 +151,3 @@ Transform tasks into verifiable goals:
|
|||||||
- "Add validation" means: write tests for invalid inputs, then pass.
|
- "Add validation" means: write tests for invalid inputs, then pass.
|
||||||
- "Fix the bug" means: write a test that reproduces it, then pass.
|
- "Fix the bug" means: write a test that reproduces it, then pass.
|
||||||
- "Refactor X" means: ensure tests pass before and after.
|
- "Refactor X" means: ensure tests pass before and after.
|
||||||
|
|
||||||
## Platform Support
|
|
||||||
|
|
||||||
The project supports Windows, Linux, macOS, Emscripten, and Android via
|
|
||||||
`Platform.cmake` and `Flags.cmake` in `deps/`.
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# ctdd
|
# ctdd
|
||||||
|
|
||||||
A C23 project wired for test-driven development using
|
A C23 template project wired for test-driven development using
|
||||||
[Unity](https://github.com/ThrowTheSwitch/Unity) and
|
[Unity](https://github.com/ThrowTheSwitch/Unity) and
|
||||||
[CMock](https://github.com/ThrowTheSwitch/CMock).
|
[CMock](https://github.com/ThrowTheSwitch/CMock). Use it as the core
|
||||||
|
template when scaffolding new projects.
|
||||||
|
|
||||||
All dependencies are fetched automatically via CMake `FetchContent` with no
|
All dependencies are fetched automatically via CMake `FetchContent` with no
|
||||||
manual installation required beyond the tools listed below.
|
manual installation required beyond the tools listed below.
|
||||||
@@ -87,7 +88,21 @@ Linux / macOS:
|
|||||||
./build/main
|
./build/main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding a new module (TDD workflow)
|
## Renaming the Project
|
||||||
|
|
||||||
|
To use this template for a new project, rename `ctdd` to your project
|
||||||
|
name:
|
||||||
|
|
||||||
|
1. Rename the `ctdd/` directory to `<src>/`.
|
||||||
|
2. Replace `ctdd` with `<src>` in:
|
||||||
|
- `CMakeLists.txt` (project name, `target_link_libraries`)
|
||||||
|
- `ctdd/CMakeLists.txt` (library names)
|
||||||
|
- All source `#include` directives
|
||||||
|
- `tests/CMakeLists.txt` (library references)
|
||||||
|
3. Update the `project()` call in `CMakeLists.txt`.
|
||||||
|
4. Update `main.c` to include your new headers.
|
||||||
|
|
||||||
|
Example: `ctdd/` becomes `mylib/`, `ctdd_str` becomes `mylib_str`.
|
||||||
|
|
||||||
### 1. Write the header
|
### 1. Write the header
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
#include "ctdd/log_write.h"
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "ctdd/log_write.h"
|
||||||
|
|
||||||
void log_write(char const* msg) {
|
void log_write(char const* msg) {
|
||||||
printf("%s\n", msg);
|
printf("%s\n", msg);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "ctdd/logger.h"
|
#include "ctdd/logger.h"
|
||||||
#include "ctdd/log_write.h"
|
#include "ctdd/log_write.h"
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
static log_level s_level = LOG_DEBUG;
|
static log_level s_level = LOG_DEBUG;
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "ctdd/report.h"
|
#include "ctdd/report.h"
|
||||||
#include "ctdd/logger.h"
|
#include "ctdd/logger.h"
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
void report_value(char const* label, int value) {
|
void report_value(char const* label, int value) {
|
||||||
char buf[256];
|
char buf[256];
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
#include "ctdd/str.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ctdd/str.h"
|
||||||
|
|
||||||
int str_starts_with(char const* s, char const* prefix) {
|
int str_starts_with(char const* s, char const* prefix) {
|
||||||
return strncmp(s, prefix, strlen(prefix)) == 0;
|
return strncmp(s, prefix, strlen(prefix)) == 0;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Template placeholder: replace with your own entry point.
|
||||||
|
// Remove or rename this file when scaffolding a new project.
|
||||||
|
|
||||||
#include "ctdd/str.h"
|
#include "ctdd/str.h"
|
||||||
#include "ctdd/report.h"
|
#include "ctdd/report.h"
|
||||||
#include "ctdd/logger.h"
|
#include "ctdd/logger.h"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
|
|
||||||
#include "ctdd/logger.h"
|
#include "ctdd/logger.h"
|
||||||
#include "Mocklog_write.h"
|
#include "Mocklog_write.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
|
|
||||||
#include "ctdd/report.h"
|
#include "ctdd/report.h"
|
||||||
#include "Mocklogger.h"
|
#include "Mocklogger.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
|
|
||||||
#include "ctdd/str.h"
|
#include "ctdd/str.h"
|
||||||
|
|
||||||
void setUp(void) {}
|
void setUp(void) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user