Skip to content

Commit 327dae2

Browse files
FredM67claude
andauthored
Add CI with embedded tests via Wokwi simulation (#138)
* chore: implement two-branch workflow (main + dev) (#134) - Create dev branch for active development - Update pre-commit hooks to protect both main and dev branches - Document branching strategy in CONTRIBUTING.md - Align with simplified workflow for small team 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * docs: add detailed daily and release workflow instructions (#135) * fix: remove broken image links from CONTRIBUTING.md (#136) Remove references to non-existent assets/images directory: - table-of-contents.png - contribution_cta.png These were leftover from the GitHub docs template. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * docs: add CLAUDE.md for Claude Code guidance Add configuration file for Claude Code AI assistant with: - Build commands for PlatformIO - Architecture overview and key files - Real-time constraints and memory limitations - Code style and safety requirements - Git workflow and test structure Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * test: add relay engine edge case tests Add comprehensive tests for RelayEngine behavior: - settle_change: verify 60s delay at startup, reset after state change, blocks both turn-ON and turn-OFF operations - relay_ordering: verify ascending order for turn-ON (surplus), descending order for turn-OFF (import) with 3 relays - duration_overflow: verify counter saturates at UINT16_MAX without wrapping, which would incorrectly block relay changes Each test suite uses separate EWMA instances (different D parameters) to ensure test isolation. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: convert override and relay tests to native, add CI - Extract config-dependent helpers to utils_override_helpers.h using if constexpr for compile-time branch verification - Remove config.h dependency from utils_override.h to fix circular deps - Convert embedded relay tests to native tests (no Arduino required) - Add native tests for override utilities (PinList, OverridePins, etc.) - Add native test for RELAY_DIVERSION=false scenario - Add native test step to CI workflow with proper ignore patterns Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * chore: simplify CI test command Remove redundant --ignore="*embedded*" flag since test_ignore in platformio.ini already handles embedded test exclusion. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: correct EWMA benchmark test assertion The test incorrectly assumed TEMA has better cloud immunity than fast EMA. TEMA reduces lag (for responsiveness) but can overshoot, causing more threshold crossings. The correct assertion is that slow EMA (high α) has fewer relay toggles than fast EMA - this is the actual cloud immunity property. Also remove --ignore flag from CI since the test now passes. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: add dev branch to CI triggers Run CI on push and pull requests to both main and dev branches. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: restructure workflow into setup, build, and test jobs - Split monolithic job into three separate jobs: - setup: installs dependencies and populates cache - build: compiles firmware (runs after setup) - test: runs native tests (runs after setup) - Build and test jobs run in parallel after setup completes - Use PROJECT_DIR env variable to reduce repetition - Improve cache key using platformio.ini hash for better invalidation Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: extract bit utilities to utils_bits.h Create utils_bits.h with bit_set, bit_read, bit_clear template functions that have no Arduino dependencies. This eliminates code duplication in native tests which previously had to copy these functions. - New utils_bits.h with portable bit manipulation functions - Update utils_pins.h to include utils_bits.h - Update native tests to use utils_bits.h instead of inline copies Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: move relay tests from native to embedded Native tests with stubs can diverge from real code. Moving relay tests to embedded ensures they test the actual RelayEngine and relayOutput implementations on real hardware. - Remove native/test_utils_relay (had stub implementations) - Add embedded/test_utils_relay (uses real utils_relay.h) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: use real utils_override.h in native tests Remove duplicate PinList, KeyIndexPair, OverridePins classes from tests. Tests now include the real utils_override.h header directly, ensuring they test the actual production code. - Guard printOverrideConfig() with #ifdef ARDUINO for native compatibility - Remove ~200 lines of duplicated class definitions from test file - Tests now verify the REAL implementation Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: move override tests to embedded (no stubs) Native tests with stubs diverge from real code. Moving override tests to embedded ensures they test the REAL RelayEngine, relayOutput, and config values on actual hardware. Native tests now only contain pure algorithm tests (EWMA, cloud patterns, negative threshold) that don't need any stubs. - Remove native/test_utils_override (had TestRelayEngine stub) - Remove native/test_override_no_relay (had RelayEngine stub) - Add embedded/test_utils_override (uses real config.h) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: remove native tests with stubs Remove native tests that contained duplicated/stubbed code: - test_ewma_benchmark (had copied EWMA_average class) - test_cloud_patterns (had copied EWMA_average, TestRelay stub) - test_negative_threshold (had MockRelayOutput stub) Only test_ewma_avg remains - it uses the real ewma_avg.hpp header. Tests requiring relay/config code must be embedded tests to use real code. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * refactor: move override tests back to native (pure C++) Override utilities (PinList, KeyIndexPair, OverridePins) are pure bitmask operations with no Arduino dependencies. Tests now use real utils_override.h header with test values to cover all cases. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: restore full relay test suite Restore the comprehensive relay tests that were accidentally lost: - settle_change tests (60s startup delay, reset after state change) - relay_ordering tests (ascending ON, descending OFF) - duration_overflow tests (UINT16_MAX saturation) Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: update FastDivision tests to match actual API The tests were testing non-existent functions (divu5, divu15, divu60). Updated to test only the functions that actually exist: - divu10: AVR assembly divide by 10 - divmod10: AVR assembly divide/modulo by 10 - divu8, divu4, divu2, divu1: constexpr shift-based divisions Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: include FastDivision.cpp in test for linking PlatformIO test builds don't automatically include project source files. Include FastDivision.cpp directly in the test to resolve undefined reference errors for divu10 and divmod10. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: add Wokwi simulation for embedded tests Configure Wokwi CI to run embedded unit tests in simulation: - Add diagram.json for Arduino Uno simulation - Add wokwi.toml for each embedded test - Update workflow with test-embedded job using matrix strategy - Tests run in parallel: fastdivision, utils_pins, utils_relay, teleinfo Requires WOKWI_CLI_TOKEN secret to be configured in GitHub. Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: correct diagram_file path for Wokwi CI Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: specify elf path directly in Wokwi action Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: run embedded tests sequentially with proper filter - Changed from parallel matrix to sequential loop - Fixed test filter pattern to use wildcard (*test_name*) - All tests share same build directory so must run one at a time Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: add wokwi-cli to PATH after installation Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: use relative path for diagram-file in wokwi-cli Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: use matrix for individual test visibility - Native tests: matrix with test_ewma_avg, test_utils_override (parallel) - Embedded tests: matrix with max-parallel:1 (sequential, separate jobs) - Each test now shows as a separate job in GitHub Actions UI - Use GITHUB_PATH for wokwi-cli instead of export Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: run embedded tests in parallel, add detailed results - Remove max-parallel (each job has its own workspace) - Add job summary with test results (PASS/FAIL lines) - Upload wokwi-output.txt as artifact for full logs Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * ci: add dorny/test-reporter for detailed test results - Convert Unity output to JUnit XML format - Use dorny/test-reporter to display results with expandable details - Add checks:write permission for test reporter - Upload both raw output and XML as artifacts Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * fix: handle non-UTF-8 characters in test output Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> * docs: add CI badge to README Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 08b79be commit 327dae2

23 files changed

Lines changed: 1485 additions & 1433 deletions

File tree

.github/workflows/build.yml

Lines changed: 213 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,227 @@
1-
# This is a basic workflow to help you get started with Actions
1+
# CI workflow for PVRouter-3-phase
2+
# Runs build and tests on push/PR to main and dev branches
23

34
name: CI
45
permissions:
56
contents: read
7+
checks: write
68

7-
# Controls when the action will run. Triggers the workflow on push or pull request
8-
# events but only for the master branch
99
on:
1010
push:
11-
branches: [ main ]
11+
branches: [ main, dev ]
1212
pull_request:
13-
branches: [ main ]
13+
branches: [ main, dev ]
14+
15+
env:
16+
PROJECT_DIR: Mk2_3phase_RFdatalog_temp
1417

15-
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
1618
jobs:
17-
# This workflow contains a single job called "build"
19+
# Setup job: installs dependencies and populates cache
20+
setup:
21+
name: Setup Environment
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v6
25+
with:
26+
submodules: 'true'
27+
28+
- name: Set up Python
29+
uses: actions/setup-python@v6
30+
with:
31+
python-version: '>=3.11'
32+
33+
- name: Cache PlatformIO
34+
uses: actions/cache@v5
35+
with:
36+
path: |
37+
~/.cache/pip
38+
~/.platformio
39+
key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }}
40+
41+
- name: Install PlatformIO
42+
run: |
43+
python -m pip install --upgrade pip
44+
pip install --upgrade platformio
45+
46+
- name: Install PlatformIO libraries
47+
run: pio pkg install --project-dir ${{ env.PROJECT_DIR }}
48+
49+
# Build job: compiles firmware for embedded targets
1850
build:
19-
# The type of runner that the job will run on
51+
name: Build Firmware
52+
needs: setup
2053
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v6
56+
with:
57+
submodules: 'true'
58+
59+
- name: Set up Python
60+
uses: actions/setup-python@v6
61+
with:
62+
python-version: '>=3.11'
2163

22-
# Steps represent a sequence of tasks that will be executed as part of the job
64+
- name: Restore PlatformIO cache
65+
uses: actions/cache@v5
66+
with:
67+
path: |
68+
~/.cache/pip
69+
~/.platformio
70+
key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }}
71+
72+
- name: Install PlatformIO
73+
run: pip install --upgrade platformio
74+
75+
- name: Build firmware
76+
run: pio run --project-dir ${{ env.PROJECT_DIR }}
77+
78+
# Native test jobs: runs each native test separately
79+
test-native:
80+
name: Native (${{ matrix.test }})
81+
needs: setup
82+
runs-on: ubuntu-latest
83+
strategy:
84+
fail-fast: false
85+
matrix:
86+
test:
87+
- test_ewma_avg
88+
- test_utils_override
2389
steps:
24-
- uses: actions/checkout@v6
25-
with:
26-
submodules: 'true'
27-
- uses: actions/cache@v5
28-
with:
29-
path: |
30-
~/.cache/pip
31-
~/.platformio/.cache
32-
key: ${{ runner.os }}-pio
33-
- name: Set up Python
34-
uses: actions/setup-python@v6
35-
with:
36-
python-version: '>=3.11'
37-
- name: Install dependencies
38-
run: |
39-
python -m pip install --upgrade pip
40-
- name: Install PlatformIO Core
41-
run: |
42-
pip install --upgrade platformio
43-
# It is important to first install the libraries before compiling, since otherwise compilation might fail to find the just-installed libraries
44-
- name: Install platformIO libraries
45-
run: pio pkg install --project-dir Mk2_3phase_RFdatalog_temp
46-
- name: Run PlatformIO
47-
run: |
48-
pio run --project-dir Mk2_3phase_RFdatalog_temp
90+
- uses: actions/checkout@v6
91+
with:
92+
submodules: 'true'
93+
94+
- name: Set up Python
95+
uses: actions/setup-python@v6
96+
with:
97+
python-version: '>=3.11'
98+
99+
- name: Restore PlatformIO cache
100+
uses: actions/cache@v5
101+
with:
102+
path: |
103+
~/.cache/pip
104+
~/.platformio
105+
key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }}
106+
107+
- name: Install PlatformIO
108+
run: pip install --upgrade platformio
109+
110+
- name: Run ${{ matrix.test }}
111+
run: pio test --project-dir ${{ env.PROJECT_DIR }} -e native -f "*${{ matrix.test }}*"
112+
113+
# Embedded test jobs: runs each test in Wokwi simulator
114+
test-embedded:
115+
name: Embedded (${{ matrix.test }})
116+
needs: setup
117+
runs-on: ubuntu-latest
118+
strategy:
119+
fail-fast: false
120+
matrix:
121+
test:
122+
- test_fastdivision
123+
- test_utils_pins
124+
- test_utils_relay
125+
- test_teleinfo
126+
steps:
127+
- uses: actions/checkout@v6
128+
with:
129+
submodules: 'true'
130+
131+
- name: Set up Python
132+
uses: actions/setup-python@v6
133+
with:
134+
python-version: '>=3.11'
135+
136+
- name: Restore PlatformIO cache
137+
uses: actions/cache@v5
138+
with:
139+
path: |
140+
~/.cache/pip
141+
~/.platformio
142+
key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }}
143+
144+
- name: Install PlatformIO
145+
run: pip install --upgrade platformio
146+
147+
- name: Install Wokwi CLI
148+
run: |
149+
curl -L https://wokwi.com/ci/install.sh | sh
150+
echo "$HOME/bin" >> $GITHUB_PATH
151+
echo "$HOME/.wokwi/bin" >> $GITHUB_PATH
152+
153+
- name: Build test firmware
154+
run: pio test --project-dir ${{ env.PROJECT_DIR }} -e uno -f "*${{ matrix.test }}*" --without-uploading --without-testing
155+
156+
- name: Run ${{ matrix.test }} in Wokwi
157+
env:
158+
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
159+
run: |
160+
wokwi-cli --timeout 60000 \
161+
--expect-text "OK" \
162+
--fail-text "FAIL" \
163+
--diagram-file ../diagram.json \
164+
--elf ${{ env.PROJECT_DIR }}/.pio/build/uno/firmware.elf \
165+
--serial-log-file wokwi-output.txt \
166+
${{ env.PROJECT_DIR }}/test/embedded/${{ matrix.test }}
167+
168+
- name: Convert Unity output to JUnit XML
169+
if: always()
170+
run: |
171+
# Convert Unity test output to JUnit XML format
172+
python3 << 'EOF'
173+
import re
174+
import sys
175+
from xml.etree.ElementTree import Element, SubElement, tostring
176+
from xml.dom import minidom
177+
178+
with open('wokwi-output.txt', 'r', encoding='utf-8', errors='ignore') as f:
179+
content = f.read()
180+
181+
# Parse Unity output
182+
test_pattern = r'([^:]+):(\d+):([^:]+):(PASS|FAIL)'
183+
summary_pattern = r'(\d+) Tests (\d+) Failures (\d+) Ignored'
184+
185+
tests = re.findall(test_pattern, content)
186+
summary = re.search(summary_pattern, content)
187+
188+
# Build JUnit XML
189+
testsuites = Element('testsuites')
190+
testsuite = SubElement(testsuites, 'testsuite')
191+
testsuite.set('name', '${{ matrix.test }}')
192+
testsuite.set('tests', str(len(tests)))
193+
194+
failures = 0
195+
for file, line, name, result in tests:
196+
testcase = SubElement(testsuite, 'testcase')
197+
testcase.set('name', name)
198+
testcase.set('classname', file.split('/')[-1])
199+
if result == 'FAIL':
200+
failures += 1
201+
failure = SubElement(testcase, 'failure')
202+
failure.set('message', f'Test failed at {file}:{line}')
203+
204+
testsuite.set('failures', str(failures))
205+
206+
# Pretty print
207+
xml_str = minidom.parseString(tostring(testsuites)).toprettyxml(indent=" ")
208+
with open('test-results.xml', 'w') as f:
209+
f.write(xml_str)
210+
EOF
211+
212+
- name: Publish test results
213+
if: always()
214+
uses: dorny/test-reporter@v1
215+
with:
216+
name: ${{ matrix.test }}
217+
path: test-results.xml
218+
reporter: java-junit
219+
220+
- name: Upload test artifacts
221+
if: always()
222+
uses: actions/upload-artifact@v4
223+
with:
224+
name: ${{ matrix.test }}-output
225+
path: |
226+
wokwi-output.txt
227+
test-results.xml

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ repos:
1414
- id: no-commit-to-branch
1515
args:
1616
- --branch=main
17+
- --branch=dev
1718
- id: end-of-file-fixer
1819
exclude: ^(.*/)?\.vscode/|\.drawio$
1920
- id: trailing-whitespace

CONTRIBUTING.md

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approacha
66

77
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
88

9-
Use the table of contents icon <img src="./assets/images/table-of-contents.png" width="25" height="25" /> on the top left corner of this document to get to a specific section of this guide quickly.
10-
119
## New contributor guide
1210

1311
To get an overview of the project, read the [README](README.md). Here are some resources to help you get started with open source contributions:
@@ -18,6 +16,69 @@ To get an overview of the project, read the [README](README.md). Here are some r
1816
- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
1917

2018

19+
## Branching Strategy
20+
21+
This project uses a two-branch workflow:
22+
23+
| Branch | Purpose | Description |
24+
|--------|---------|-------------|
25+
| `main` | Stable releases | Production-ready code that users download (default branch) |
26+
| `dev` | Development | Active development and testing |
27+
28+
> **Note:** Direct commits to `main` and `dev` are blocked by pre-commit hooks. Always work in feature branches.
29+
30+
### Daily Development Workflow
31+
32+
**Starting a new feature:**
33+
34+
```bash
35+
# Switch to dev and update
36+
git checkout dev
37+
git pull origin dev
38+
39+
# Create feature branch
40+
git checkout -b feature/my-feature-name
41+
```
42+
43+
**Working on your feature:**
44+
45+
```bash
46+
# Make changes, commit as usual
47+
git add .
48+
git commit -m "feat: description of changes"
49+
50+
# Push your feature branch
51+
git push -u origin feature/my-feature-name
52+
```
53+
54+
**Creating a Pull Request:**
55+
56+
```bash
57+
# Create PR targeting dev branch
58+
gh pr create --base dev --title "feat: my feature"
59+
60+
# Or use GitHub UI and change base branch from 'main' to 'dev'
61+
```
62+
63+
### Release Workflow
64+
65+
**When ready to release stable code to users:**
66+
67+
```bash
68+
# Switch to main branch
69+
git checkout main
70+
git pull origin main
71+
72+
# Merge dev into main
73+
git merge dev
74+
75+
# Tag the release
76+
git tag v1.x.x
77+
git push origin main --tags
78+
```
79+
80+
After merging to `main`, users will get the latest stable release when they clone or download the repository.
81+
2182
## Getting started
2283

2384
To navigate our codebase with confidence, see [the introduction to working in the docs repository](/contributing/working-in-docs-repository.md) :confetti_ball:. For more information on how we write our markdown files, see [the GitHub Markdown reference](contributing/content-markup-reference.md).
@@ -40,8 +101,6 @@ Scan through our [existing issues](https://github.com/github/docs/issues) to fin
40101

41102
Click **Make a contribution** at the bottom of any docs page to make small changes such as a typo, sentence fix, or a broken link. This takes you to the `.md` file where you can make your changes and [create a pull request](#pull-request) for a review.
42103

43-
<img src="./assets/images/contribution_cta.png" width="300" height="150" />
44-
45104
#### Make changes in a codespace
46105

47106
For more information about using a codespace for working on GitHub documentation, see "[Working in a codespace](https://github.com/github/docs/blob/main/contributing/codespace.md)."
@@ -125,7 +184,7 @@ If you need to commit without running hooks (not recommended):
125184
git commit --no-verify
126185
```
127186

128-
> **Note:** The `no-commit-to-branch` hook prevents direct commits to `main`. Create a feature branch for your changes.
187+
> **Note:** The `no-commit-to-branch` hook prevents direct commits to `main` and `dev`. Always create a feature branch for your changes (typically branching from `dev`).
129188
130189
### Commit your update
131190

0 commit comments

Comments
 (0)