Skip to content

Commit 60da0ec

Browse files
authored
Improve Zephyr environment management + improve integration test reliability (#38)
* Reenable bin/ caching * Reenable python venv cache * Use official uv cache method * Fix uv cache minimization * Fix missing $ * Reenable submodule cache * Fix submodule cache key * Reenable zephyr cache * Backup, try to fix submodule cache * Try to get sha right * Still working on submod cache key * Still working on submod cache key * Reenable zephyr cache take 2 * Trying something a little different with zephyr * Break out all zephyr steps * Improved submodule checkout * allow prerelease python modules * Allow prerelease * poke * Try new uv based west command * Fix spacing * fmt * Remove pip dependency * Work with existing zephyr directory structure * Stop caching zephyr sdk, cache entire fprime-venv * Always run uv * Stop caching * Simplifying * Remove pyproject.toml bc it was causing redownloads * poke * poke * readd pip * poke * Remove zephyr python deps from base requirements file * poke * Remove changes from submodule target * Hide submodule command * revert zephyr submodule change * target name change * Fix Makefile * Try adding start_gds to all test fixtures * Extend timeouts, look for command completion opcode * Increase timeout * Try retries on sending commands * Fix send and assert command * Add interogate and comments for all python code * Fix bootloader trigger * DRY up tests, use event predicates to simplify rtc test * Fix bootloader * Fix bootloader trigger
1 parent 7769ee4 commit 60da0ec

File tree

19 files changed

+516
-264
lines changed

19 files changed

+516
-264
lines changed

.github/workflows/ci.yaml

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,91 +11,55 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v4
14+
1415
- name: Lint
1516
run: |
1617
make fmt
18+
1719
build:
1820
runs-on: deathstar
1921
steps:
20-
- name: Checkout repository
21-
uses: actions/checkout@v4
22-
with:
23-
submodules: false # We'll handle submodules with smart caching
24-
fetch-depth: 0
25-
26-
# - name: Cache bin
27-
# id: cache-bin
28-
# uses: actions/cache@v4
29-
# with:
30-
# path: |
31-
# bin
32-
# key: bin-${{ hashFiles('Makefile') }}-v3
33-
# restore-keys: |
34-
# bin-${{ hashFiles('Makefile') }}-
35-
# bin-
22+
- uses: actions/checkout@v4
3623

3724
- name: Download bin tools
3825
if: steps.cache-bin.outputs.cache-hit != 'true'
3926
run: |
4027
make download-bin
4128
42-
# - name: Cache submodules
43-
# id: cache-submodules
44-
# uses: actions/cache@v4
45-
# with:
46-
# path: |
47-
# lib/fprime
48-
# lib/fprime-zephyr
49-
# lib/zephyr-workspace/zephyr
50-
# key: submodules-${{ hashFiles('.gitmodules') }}-v3
51-
# restore-keys: |
52-
# submodules-${{ hashFiles('.gitmodules') }}-
53-
# submodules-
54-
5529
- name: Setup submodules
5630
if: steps.cache-submodules.outputs.cache-hit != 'true'
5731
run: |
5832
make submodules
5933
60-
# - name: Cache python venv
61-
# id: cache-python
62-
# uses: actions/cache@v4
63-
# with:
64-
# path: fprime-venv
65-
# key: python-venv-${{ runner.os }}-${{ hashFiles('requirements.txt') }}-v3
66-
# restore-keys: |
67-
# python-venv-${{ runner.os }}-
68-
# python-venv-
69-
70-
- name: Setup python venv
71-
if: steps.cache-python.outputs.cache-hit != 'true'
34+
- name: Create python venv
7235
run: |
7336
make fprime-venv
7437
75-
# - name: Cache Zephyr workspace and SDK
76-
# id: cache-zephyr
77-
# uses: actions/cache@v4
78-
# with:
79-
# path: |
80-
# lib/zephyr-workspace/modules
81-
# lib/zephyr-workspace/bootloader
82-
# ~/zephyr-sdk-0.17.2
83-
# key: zephyr-${{ hashFiles('west.yml') }}-${{ runner.os }}-v3
84-
# restore-keys: |
85-
# zephyr-${{ hashFiles('west.yml') }}-${{ runner.os }}-
86-
# zephyr-
87-
8838
- name: Setup Zephyr
89-
if: steps.cache-zephyr.outputs.cache-hit != 'true'
39+
if: steps.cache-zephyr-workspace.outputs.cache-hit != 'true'
40+
run: |
41+
make zephyr-workspace
42+
43+
- name: Setup Zephyr SDK
44+
if: steps.cache-zephyr-sdk.outputs.cache-hit != 'true'
45+
run: |
46+
make zephyr-sdk
47+
48+
- name: Setup Zephyr Export
9049
run: |
91-
make zephyr-setup
92-
env:
93-
PIP_DISABLE_PIP_VERSION_CHECK: 1
94-
PIP_NO_COMPILE: 1
50+
make zephyr-export
51+
52+
- name: Install Zephyr Python Dependencies
53+
run: |
54+
make zephyr-python-deps
55+
56+
- name: Generate
57+
run: |
58+
make generate
9559
9660
- name: Build
9761
run: |
98-
make generate-ci build-ci
62+
make build
9963
10064
- name: Upload build artifacts
10165
uses: actions/upload-artifact@v4
@@ -105,25 +69,32 @@ jobs:
10569
build-artifacts/zephyr.uf2
10670
build-artifacts/zephyr/fprime-zephyr-deployment/dict/ReferenceDeploymentTopologyDictionary.json
10771
retention-days: 30
72+
10873
integration:
109-
runs-on: [integration]
74+
runs-on:
75+
- integration
11076
needs: build
11177
steps:
11278
- uses: actions/checkout@v4
79+
11380
- uses: actions/download-artifact@v5
81+
11482
- name: Set up dependencies
11583
run: |
116-
mkdir -p build-artifacts/zephyr/fprime-zephyr-deployment/dict && mv zephyr/fprime-zephyr-deployment/dict/ReferenceDeploymentTopologyDictionary.json build-artifacts/zephyr/fprime-zephyr-deployment/dict
117-
make submodules
118-
make fprime-venv
84+
mkdir -p build-artifacts/zephyr/fprime-zephyr-deployment/dict \
85+
&& mv zephyr/fprime-zephyr-deployment/dict/ReferenceDeploymentTopologyDictionary.json build-artifacts/zephyr/fprime-zephyr-deployment/dict
86+
make submodules fprime-venv
87+
11988
- name: Trigger Bootloader
12089
run: |
12190
make bootloader
12291
sleep 10
92+
12393
- name: Copy Firmware
12494
run: |
12595
picotool load ./zephyr.uf2
12696
picotool reboot
97+
12798
- name: Run Integration Tests
12899
run: |
129100
make test-integration

.pre-commit-config.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ repos:
3333
- id: ruff-check
3434
args: [--fix, --select, I] # import sorting
3535
- id: ruff-format
36+
37+
- repo: https://github.com/econchick/interrogate/
38+
rev: 1.7.0
39+
hooks:
40+
- id: interrogate
41+
args:
42+
- --ignore-init-method
43+
- --omit-covered-files
44+
- --fail-under=100
45+
- -vv
46+
- --color

FprimeZephyrReference/Components/Burnwire/Burnwire.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ void Burnwire ::schedIn_handler(FwIndexType portNum, U32 context) {
7272
// ----------------------------------------------------------------------
7373

7474
void Burnwire ::START_BURNWIRE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
75-
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
7675
this->startBurn();
76+
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
7777
}
7878

7979
void Burnwire ::STOP_BURNWIRE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
80-
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
8180
this->stopBurn();
81+
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
8282
}
8383

8484
} // namespace Components

FprimeZephyrReference/test/bootloader_trigger.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1+
"""
2+
bootloader_trigger.py:
3+
4+
This module is responsible for sending the command to trigger the bootloader mode
5+
on the PROVES hardware during integration tests. Once in bootloader mode, the device
6+
can accept firmware updates.
7+
"""
8+
19
import os
210
import subprocess
311
import time
412

513
import pytest
614
from fprime_gds.common.testing_fw.api import IntegrationTestAPI
15+
from int.common import cmdDispatch
716

817

918
@pytest.fixture(scope="session", autouse=True)
1019
def start_gds(fprime_test_api_session: IntegrationTestAPI):
20+
"""Fixture to start GDS
21+
22+
GDS is used to send the bootloader commands.
23+
"""
1124
process = subprocess.Popen(["make", "gds-integration"], cwd=os.getcwd())
1225

1326
gds_working = False
1427
timeout_time = time.time() + 30
1528
while time.time() < timeout_time:
1629
try:
1730
fprime_test_api_session.send_and_assert_command(
18-
command="CdhCore.cmdDisp.CMD_NO_OP"
31+
command=f"{cmdDispatch}.CMD_NO_OP"
1932
)
2033
gds_working = True
2134
break
@@ -28,6 +41,10 @@ def start_gds(fprime_test_api_session: IntegrationTestAPI):
2841

2942

3043
def test_bootloader(fprime_test_api: IntegrationTestAPI):
44+
"""Trigger bootloader mode on PROVES hardware"""
45+
# Don't use proves_send_and_assert_command here because we don't expect
46+
# a response from the bootloader trigger command. The device will reboot
47+
# into bootloader mode and may not send a command completion event.
3148
fprime_test_api.send_command(
3249
"ReferenceDeployment.bootloaderTrigger.TRIGGER_BOOTLOADER"
3350
)
Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,64 @@
1-
# """
2-
# burnwire_test.py:
1+
"""
2+
burnwire_test.py:
33
4-
# Integration tests for the Burnwire component.
5-
# """
4+
Integration tests for the Burnwire component.
5+
"""
66

77
import pytest
8+
from common import proves_send_and_assert_command
89
from fprime_gds.common.testing_fw.api import IntegrationTestAPI
910

10-
# Constants
11+
burnwire = "ReferenceDeployment.burnwire"
1112

1213

1314
@pytest.fixture(autouse=True)
14-
def reset_burnwire(fprime_test_api: IntegrationTestAPI):
15+
def reset_burnwire(fprime_test_api: IntegrationTestAPI, start_gds):
1516
"""Fixture to stop burnwire and clear histories before/after each test"""
1617
# Stop burnwire and clear before test
17-
fprime_test_api.clear_histories()
1818
stop_burnwire(fprime_test_api)
1919
yield
2020
# Clear again after test to prevent residue
21-
fprime_test_api.clear_histories()
2221
stop_burnwire(fprime_test_api)
2322

2423

2524
def stop_burnwire(fprime_test_api: IntegrationTestAPI):
26-
fprime_test_api.send_and_assert_command(
27-
"ReferenceDeployment.burnwire.STOP_BURNWIRE"
28-
)
25+
"""Stop the burnwire and clear histories"""
26+
proves_send_and_assert_command(fprime_test_api, f"{burnwire}.STOP_BURNWIRE")
2927

30-
fprime_test_api.assert_event(
31-
"ReferenceDeployment.burnwire.SetBurnwireState", "OFF", timeout=10
32-
)
28+
fprime_test_api.assert_event(f"{burnwire}.SetBurnwireState", "OFF", timeout=10)
3329

34-
fprime_test_api.assert_event(
35-
"ReferenceDeployment.burnwire.BurnwireEndCount", timeout=2
36-
)
37-
38-
received_events = fprime_test_api.get_event_subhistory()
39-
print(f"Received events: {received_events}")
30+
fprime_test_api.assert_event(f"{burnwire}.BurnwireEndCount", timeout=2)
4031

4132

4233
def test_01_start_and_stop_burnwire(fprime_test_api: IntegrationTestAPI, start_gds):
4334
"""Test that burnwire starts and stops as expected"""
4435

4536
# Start burnwire
46-
fprime_test_api.send_and_assert_command(
47-
"ReferenceDeployment.burnwire.START_BURNWIRE"
48-
)
37+
proves_send_and_assert_command(fprime_test_api, f"{burnwire}.START_BURNWIRE")
4938

5039
# Wait for SetBurnwireState = ON
51-
fprime_test_api.assert_event(
52-
"ReferenceDeployment.burnwire.SetBurnwireState", "ON", timeout=2
53-
)
40+
fprime_test_api.assert_event(f"{burnwire}.SetBurnwireState", "ON", timeout=2)
5441

55-
fprime_test_api.assert_event(
56-
"ReferenceDeployment.burnwire.SafetyTimerState", timeout=2
57-
)
42+
fprime_test_api.assert_event(f"{burnwire}.SafetyTimerState", timeout=2)
5843

59-
fprime_test_api.assert_event(
60-
"ReferenceDeployment.burnwire.SetBurnwireState", "OFF", timeout=10
61-
)
44+
fprime_test_api.assert_event(f"{burnwire}.SetBurnwireState", "OFF", timeout=10)
6245

63-
fprime_test_api.assert_event(
64-
"ReferenceDeployment.burnwire.BurnwireEndCount", timeout=2
65-
)
46+
fprime_test_api.assert_event(f"{burnwire}.BurnwireEndCount", timeout=2)
6647

6748

6849
def test_02_manual_stop_before_timeout(fprime_test_api: IntegrationTestAPI, start_gds):
6950
"""Test that burnwire stops manually before the safety timer expires"""
7051

7152
# Start burnwire
72-
fprime_test_api.send_and_assert_command(
73-
"ReferenceDeployment.burnwire.START_BURNWIRE"
74-
)
53+
proves_send_and_assert_command(fprime_test_api, f"{burnwire}.START_BURNWIRE")
7554

7655
# Confirm Burnwire turned ON
77-
fprime_test_api.assert_event(
78-
"ReferenceDeployment.burnwire.SetBurnwireState", "ON", timeout=2
79-
)
56+
fprime_test_api.assert_event(f"{burnwire}.SetBurnwireState", "ON", timeout=2)
8057

8158
# # Stop burnwire before safety timer triggers
82-
fprime_test_api.send_and_assert_command(
83-
"ReferenceDeployment.burnwire.STOP_BURNWIRE"
84-
)
59+
proves_send_and_assert_command(fprime_test_api, f"{burnwire}.STOP_BURNWIRE")
8560

8661
# Confirm Burnwire turned OFF
87-
fprime_test_api.assert_event(
88-
"ReferenceDeployment.burnwire.SetBurnwireState", "OFF", timeout=2
89-
)
62+
fprime_test_api.assert_event(f"{burnwire}.SetBurnwireState", "OFF", timeout=2)
9063

91-
fprime_test_api.assert_event(
92-
"ReferenceDeployment.burnwire.BurnwireEndCount", timeout=2
93-
)
64+
fprime_test_api.assert_event(f"{burnwire}.BurnwireEndCount", timeout=2)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
common.py:
3+
4+
This module provides a functions and constants shared by
5+
integration tests for PROVES microcontroller hardware.
6+
"""
7+
8+
from fprime_gds.common.testing_fw.api import IntegrationTestAPI
9+
from fprime_gds.common.testing_fw.predicates import event_predicate
10+
11+
cmdDispatch = "CdhCore.cmdDisp"
12+
13+
14+
def proves_send_and_assert_command(
15+
fprime_test_api: IntegrationTestAPI,
16+
command: str,
17+
args: list[str] = [],
18+
events: list[event_predicate] = [],
19+
):
20+
"""Send command and assert completion
21+
22+
PROVES microcontroller hardware responds more slowly than typical FPrime
23+
hardware which use microprocessors. As a result, some commands may
24+
take longer to complete. This function clears histories before sending
25+
the command, sets a longer timeout for command completion, and retries
26+
up to 3 times if command assertion fails.
27+
"""
28+
fprime_test_api.clear_histories()
29+
30+
for attempt in range(3):
31+
try:
32+
fprime_test_api.send_and_assert_command(
33+
command,
34+
args,
35+
timeout=5,
36+
max_delay=5,
37+
events=events,
38+
)
39+
break
40+
except AssertionError:
41+
if attempt == 2:
42+
raise

0 commit comments

Comments
 (0)