Skip to content

Commit 6b0edcb

Browse files
refactor: Enhance Meade command parsing and response handling (#291)
1 parent eea3cb2 commit 6b0edcb

49 files changed

Lines changed: 8032 additions & 8943 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- '**'
77
push:
88
branches:
9-
- '**'
9+
- 'develop'
1010

1111
jobs:
1212
unit-tests:
@@ -17,22 +17,26 @@ jobs:
1717
- uses: ./.github/actions/setup-pio
1818
with:
1919
install-unit-test-deps: 'true'
20-
- name: Run unit tests
21-
run: pio test -e native -v
20+
- name: Run unit tests with coverage
21+
if: always()
22+
run: |
23+
pip install gcovr
24+
./scripts/test-coverage.sh
2225
- name: Publish Coverage Summary
2326
if: always()
2427
run: |
2528
echo "## Native Unit Test Coverage" >> "$GITHUB_STEP_SUMMARY"
2629
echo >> "$GITHUB_STEP_SUMMARY"
27-
if [ -f .pio/coverage.md ]; then
28-
cat .pio/coverage.md >> "$GITHUB_STEP_SUMMARY"
30+
if [ -f .pio/build/native/coverage_report/coverage.md ]; then
31+
cat .pio/build/native/coverage_report/coverage.md >> "$GITHUB_STEP_SUMMARY"
2932
else
3033
echo "Coverage report was not generated." >> "$GITHUB_STEP_SUMMARY"
3134
fi
3235
3336
build:
3437
name: Build (${{ matrix.board }})
3538
runs-on: ubuntu-latest
39+
needs: unit-tests
3640
strategy:
3741
fail-fast: false
3842
matrix:
@@ -47,5 +51,7 @@ jobs:
4751
- uses: ./.github/actions/setup-pio
4852
with:
4953
install-matrix-deps: 'true'
54+
env:
55+
PLATFORMIO_BUILD_CACHE_DIR: ${{ github.workspace }}/build_cache
5056
- name: Build ${{ matrix.board }}
5157
run: python matrix_build.py -b ${{ matrix.board }}

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ pio run -e ramps -t upload
2727
# Run unit tests (native platform)
2828
pio test -e native
2929

30+
# Run unit tests with coverage report (requires gcovr: pip install gcovr)
31+
./scripts/test-coverage.sh # defaults to -e native
32+
./scripts/test-coverage.sh -e native
33+
# Report: .pio/build/native/coverage_report/index.html
34+
3035
# Run matrix build (tests many configuration combinations across boards)
3136
python matrix_build.py -b ramps # single board
3237
python matrix_build.py # all boards

specs/plan.md renamed to PLAN.md

Lines changed: 89 additions & 41 deletions
Large diffs are not rendered by default.

matrix_build_parallel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ def copy_caches_to_executors(src_proj_dir: Path, dst_executors: List[Executor]):
136136
dir_names_to_copy = ['.pio', 'build_cache']
137137
for dir_name_to_copy in dir_names_to_copy:
138138
src_path = Path(src_proj_dir, dir_name_to_copy)
139+
# If the cache dir isn't inside the temp proj dir (e.g. when
140+
# PLATFORMIO_BUILD_CACHE_DIR points at the repo root), try the repo root
141+
if not src_path.exists():
142+
src_path = Path('.', dir_name_to_copy)
143+
if not src_path.exists():
144+
continue
139145
for dst_executor in dst_executors:
140146
dst_path = Path(dst_executor.proj_dir, dir_name_to_copy)
141147
shutil.copytree(src_path, dst_path)

platformio.ini

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ include_dir = .
33
src_dir = ./src
44
lib_dir = ./src/libs
55
test_dir = ./unit_tests
6-
build_cache_dir = ./build_cache
76

87
[common]
98
lib_deps =
@@ -123,31 +122,42 @@ platform = espressif32
123122
board = esp32dev
124123
upload_speed = 460800
125124
monitor_filters = esp32_exception_decoder
125+
build_unflags = -std=gnu++11
126126
build_flags =
127127
${env.build_flags}
128+
-std=gnu++17
128129
-D BOARD=BOARD_ESP32_ESP32DEV
129130
lib_deps =
130131
${common.lib_deps}
131132
WiFi
132133

133134
[env:oaeboardv1]
134135
extends = env:esp32
136+
build_unflags = -std=gnu++11
135137
build_flags =
136138
${env.build_flags}
139+
-std=gnu++17
137140
-D BOARD=BOARD_OAE_V1 -D ESP32BOARD
138141

139142
[env:native]
140143
platform = native
141-
test_ignore = test_embedded
142-
build_flags =
144+
test_build_src = true
145+
test_framework = googletest
146+
build_src_filter =
147+
+<./core>
148+
+<./ports>
149+
+<./adapters>
150+
build_src_flags =
151+
-std=gnu++17
143152
-O0
144-
--coverage
145-
-fprofile-arcs
146-
-ftest-coverage
147-
; Linker flag for coverage
148-
extra_scripts = scripts/test-coverage.py
149-
test_testing_command =
150-
/usr/bin/env
151-
python3
152-
scripts/test-coverage.py
153-
${platformio.build_dir}/${this.__env__}/program
153+
-g
154+
--coverage
155+
-Wall
156+
-Wextra
157+
-Werror
158+
-Wpedantic
159+
-Wshadow
160+
test_lib_deps =
161+
ArduinoFake@^0.4.0
162+
extra_scripts = pre:scripts/test-coverage.py
163+
test_filter = test_core

scripts/MeadeCommandParser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import os
1212
import re
1313

14-
MEADE_CPP = "..\\src\\MeadeCommandProcessor.cpp"
15-
VERSION_FILE = "..\\Version.h"
14+
MEADE_CPP = os.path.join("..", "src", "core", "meade", "MeadeProtocol.hpp")
15+
VERSION_FILE = os.path.join("..", "Version.h")
1616
MODULE_PATH = os.path.dirname(os.path.realpath(__file__))
1717
START_LINE = 0
1818
END_LINE = 0

scripts/test-coverage.py

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,4 @@
1-
import os
2-
import subprocess
3-
from pathlib import Path
4-
import sys
5-
from typing import TYPE_CHECKING, Any
1+
Import("env")
62

7-
if TYPE_CHECKING:
8-
def Import(*names: str) -> tuple[Any, ...]:
9-
...
10-
11-
def _project_root():
12-
return Path(__file__).resolve().parent.parent
13-
14-
15-
def _native_build_dir():
16-
return _project_root() / ".pio" / "build" / "native"
17-
18-
19-
def _has_coverage_data():
20-
return any(_native_build_dir().rglob("*.gcda"))
21-
22-
23-
def ensure_gcovr_installed(build_env):
24-
"""Checks if gcovr is installed, and installs it via pip if not."""
25-
try:
26-
import gcovr
27-
except ImportError:
28-
print("gcovr not found! Installing it into the PlatformIO environment...")
29-
# $PYTHONEXE ensures we use PlatformIO's isolated Python environment, not the system OS Python
30-
build_env.Execute("$PYTHONEXE -m pip install gcovr")
31-
32-
33-
def generateCoverageInfo():
34-
if not _has_coverage_data():
35-
print("Skipping coverage report generation because no .gcda files were produced.")
36-
return
37-
38-
print("Generating code coverage report...")
39-
gcovr_cmd = ["gcovr"]
40-
report_dir = _project_root()
41-
# Adjust this path if you are testing multiple specific folders
42-
subprocess.run(gcovr_cmd + ["--html-details", ".pio/coverage.html", "--filter", "src/"], check=True, cwd=report_dir)
43-
print(f"Coverage report generated at: .pio/coverage.html")
44-
subprocess.run(gcovr_cmd + ["--markdown", ".pio/coverage.md", "--filter", "src/"], check=True, cwd=report_dir)
45-
print(f"Coverage report generated at: .pio/coverage.md")
46-
47-
48-
def configure_build():
49-
Import("env")
50-
build_env = globals()["env"]
51-
build_env.Append(LINKFLAGS=["--coverage"])
52-
ensure_gcovr_installed(build_env)
53-
54-
55-
def main(argv):
56-
if not argv:
57-
print("Usage: test-coverage.py <test-program> [args...]", file=sys.stderr)
58-
return 2
59-
60-
completed = subprocess.run(argv, cwd=_project_root(), env=os.environ.copy(), check=False)
61-
generateCoverageInfo()
62-
return completed.returncode
63-
64-
65-
if __name__ == "__main__":
66-
raise SystemExit(main(sys.argv[1:]))
67-
68-
configure_build()
3+
# Ensure coverage flags reach the linker so .gcda files are produced.
4+
env.Append(LINKFLAGS=["--coverage"])

scripts/test-coverage.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env bash
2+
# Run pio test and auto-generate coverage report.
3+
# Usage: ./scripts/test-coverage.sh [-e <env>] [extra pio test args...]
4+
#
5+
# This wrapper is needed because pio test spawns the test binary as a
6+
# subprocess — coverage data (.gcda files) is only flushed when that
7+
# subprocess exits, so we can't generate the report from inside the
8+
# test binary or from PlatformIO's pre-scripts.
9+
10+
set -euo pipefail
11+
12+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
14+
cd "$PROJECT_DIR"
15+
16+
# Extract environment name from args (default: native)
17+
ENV="native"
18+
NEXT_IS_ENV=0
19+
for arg in "$@"; do
20+
if [ "$NEXT_IS_ENV" -eq 1 ]; then
21+
ENV="$arg"
22+
break
23+
fi
24+
if [ "$arg" = "-e" ]; then
25+
NEXT_IS_ENV=1
26+
fi
27+
done
28+
29+
# Run tests (default to -e native if no args)
30+
if [ $# -gt 0 ]; then
31+
pio test "$@" 2>&1
32+
else
33+
pio test -e native 2>&1
34+
fi
35+
TEST_EXIT=$?
36+
37+
if [ $TEST_EXIT -ne 0 ]; then
38+
echo ""
39+
echo "⚠️ Tests failed — skipping coverage report."
40+
exit $TEST_EXIT
41+
fi
42+
43+
# Determine gcov executable
44+
if [ "$(uname -s)" = "Darwin" ]; then
45+
GCOV_TOOL="llvm-cov gcov"
46+
else
47+
GCOV_TOOL="gcov"
48+
fi
49+
50+
# Check if gcovr is available
51+
if ! command -v gcovr &>/dev/null; then
52+
echo ""
53+
echo "⚠️ gcovr not found — skipping coverage report (pip install gcovr)"
54+
exit 0
55+
fi
56+
57+
# Generate coverage report
58+
OUTPUT_DIR=".pio/build/$ENV/coverage_report"
59+
mkdir -p "$OUTPUT_DIR"
60+
61+
echo ""
62+
echo "Generating coverage report..."
63+
gcovr \
64+
--root . \
65+
--gcov-executable "$GCOV_TOOL" \
66+
--html-details "$OUTPUT_DIR/index.html" \
67+
--markdown "$OUTPUT_DIR/coverage.md" \
68+
--exclude '.pio/*' \
69+
--exclude 'unit_tests/*' \
70+
--gcov-ignore-errors=no_working_dir_found \
71+
--gcov-ignore-errors=source_not_found \
72+
--print-summary
73+
74+
echo ""
75+
echo "✅ Coverage report: $OUTPUT_DIR/index.html"

0 commit comments

Comments
 (0)