From 877e2d4045a08cbbf621cec2301f10985fa9d9b3 Mon Sep 17 00:00:00 2001 From: Zimri Leisher Date: Thu, 22 Oct 2026 10:43:41 -0400 Subject: [PATCH 1/3] Consolidate harnesses --- .gitmodules | 5 - test/fprime-wasm | 1 - test/harness/{ => fpy}/FpyHarnessMain.cpp | 0 test/harness/{ => fpy}/FpySequencerTester.cpp | 0 test/harness/{ => fpy}/FpySequencerTester.fpp | 0 test/harness/{ => fpy}/FpySequencerTester.hpp | 0 test/harness/patches/README.md | 18 -- .../patches/wasm-sequencer-fixes.patch | 184 ------------------ .../wasm/{tester => }/WasmSequencerTester.cpp | 0 .../wasm/{tester => }/WasmSequencerTester.fpp | 0 .../wasm/{tester => }/WasmSequencerTester.hpp | 0 test/harness/wasm/settings.ini | 3 - test/harness/wasm/tester/CMakeLists.txt | 20 -- 13 files changed, 231 deletions(-) delete mode 160000 test/fprime-wasm rename test/harness/{ => fpy}/FpyHarnessMain.cpp (100%) rename test/harness/{ => fpy}/FpySequencerTester.cpp (100%) rename test/harness/{ => fpy}/FpySequencerTester.fpp (100%) rename test/harness/{ => fpy}/FpySequencerTester.hpp (100%) delete mode 100644 test/harness/patches/README.md delete mode 100644 test/harness/patches/wasm-sequencer-fixes.patch rename test/harness/wasm/{tester => }/WasmSequencerTester.cpp (100%) rename test/harness/wasm/{tester => }/WasmSequencerTester.fpp (100%) rename test/harness/wasm/{tester => }/WasmSequencerTester.hpp (100%) delete mode 100644 test/harness/wasm/settings.ini delete mode 100644 test/harness/wasm/tester/CMakeLists.txt diff --git a/.gitmodules b/.gitmodules index 4d1919d..0c42164 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,8 +2,3 @@ path = test/fprime url = https://github.com/nasa/fprime.git shallow = true -[submodule "test/fprime-wasm"] - path = test/fprime-wasm - url = https://github.com/Kronos3/fprime.git - branch = wasm - shallow = true \ No newline at end of file diff --git a/test/fprime-wasm b/test/fprime-wasm deleted file mode 160000 index 8d5ad09..0000000 --- a/test/fprime-wasm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8d5ad09f18bb7e05e659b11d43913316592a84f7 diff --git a/test/harness/FpyHarnessMain.cpp b/test/harness/fpy/FpyHarnessMain.cpp similarity index 100% rename from test/harness/FpyHarnessMain.cpp rename to test/harness/fpy/FpyHarnessMain.cpp diff --git a/test/harness/FpySequencerTester.cpp b/test/harness/fpy/FpySequencerTester.cpp similarity index 100% rename from test/harness/FpySequencerTester.cpp rename to test/harness/fpy/FpySequencerTester.cpp diff --git a/test/harness/FpySequencerTester.fpp b/test/harness/fpy/FpySequencerTester.fpp similarity index 100% rename from test/harness/FpySequencerTester.fpp rename to test/harness/fpy/FpySequencerTester.fpp diff --git a/test/harness/FpySequencerTester.hpp b/test/harness/fpy/FpySequencerTester.hpp similarity index 100% rename from test/harness/FpySequencerTester.hpp rename to test/harness/fpy/FpySequencerTester.hpp diff --git a/test/harness/patches/README.md b/test/harness/patches/README.md deleted file mode 100644 index 9a18e8b..0000000 --- a/test/harness/patches/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Local patches to the fprime-wasm submodule - -`wasm-sequencer-fixes.patch` carries two fixes to `Svc::WasmSequencer` the -wasm harness needs: - -1. **The `env` float imports exist.** LLVM materializes library calls for - guest float arithmetic (`pow`, `fmod`, `log`) under wasm's default import - module `env`; without them, any sequence using float modulo, exponent or - log fails to load. -2. **Guest FATAL/COMMAND events are emitted.** The guest-severity - restriction (HostFunctionInvalidSeverity) is being removed upstream; - until that lands, the patch restores the LogFatal/LogCommand events so a - guest `log()` at any F Prime severity round-trips, matching the - FpySequencer. - -The patch is applied to `test/fprime-wasm` automatically before the wasm -harness is built (see `build_wasm_harness` in `src/fpy/harness.py`). It -should be dropped once the fixes land upstream. diff --git a/test/harness/patches/wasm-sequencer-fixes.patch b/test/harness/patches/wasm-sequencer-fixes.patch deleted file mode 100644 index e52dc21..0000000 --- a/test/harness/patches/wasm-sequencer-fixes.patch +++ /dev/null @@ -1,184 +0,0 @@ -diff --git a/Svc/WasmSequencer/WasmSequencer.hpp b/Svc/WasmSequencer/WasmSequencer.hpp -index 7006f49f..1eada14b 100644 ---- a/Svc/WasmSequencer/WasmSequencer.hpp -+++ b/Svc/WasmSequencer/WasmSequencer.hpp -@@ -1162,6 +1162,9 @@ class WasmSequencer final : public WasmSequencerComponentBase { - //! Set up the fprime host interface - void hostFprimeV1(spacewasm_host_t*); - -+ //! Set up the "env" host module (float library calls) -+ void hostEnv(spacewasm_host_t*); -+ - /// FPrime Wasm Interface Host Functions - spacewasm_hostcall_result_t wasmExit(struct spacewasm_caller_t* caller, - const struct spacewasm_value_t* params, -diff --git a/Svc/WasmSequencer/WasmSequencerEvents.fppi b/Svc/WasmSequencer/WasmSequencerEvents.fppi -index 8e7981ba..32584667 100644 ---- a/Svc/WasmSequencer/WasmSequencerEvents.fppi -+++ b/Svc/WasmSequencer/WasmSequencerEvents.fppi -@@ -163,16 +163,6 @@ event SerialInFrameTooLarge(portIndex: U32, $size: U32, maxSize: U32) \ - severity warning high \ - format "Serial input port {} dropped oversized frame of {} bytes (queue max {})" - --@ The guest requested an event at a severity that is not permitted for guest --@ programs (an out-of-range id, or FATAL/COMMAND which are reserved for the --@ host). The guest message is preserved and reported here at WARNING_HI. --event HostFunctionInvalidSeverity( -- raw: I32 -- msg: string size Wasm.GUEST_EVENT_MESSAGE_SIZE --) \ -- severity warning high \ -- format "Guest event at invalid/forbidden severity '{}': {}" -- - @ A sequence is starting after the main entrypoint was invoked - event SequenceStarting(index: ModuleIdx) \ - severity activity high \ -@@ -281,15 +271,21 @@ event InvalidModuleEntrypoint(moduleIdx: ModuleIdx, status: Status) \ - format "Module at index {} does not have a valid 'main' entrypoint: {}" - - # ---------------------------------------------------------------------- --# Guest-emitted events (fprime.event host function). One event per guest- --# permitted F´ severity; the host function switches on the requested --# severity id to emit the matching one so the event carries the correct --# FPP severity. FATAL and COMMAND are intentionally NOT guest-emittable --# (FATAL would let untrusted guest code trigger the FatalHandler; COMMAND --# is reserved for the command dispatcher). A guest request for either, or --# for an out-of-range id, is reported via HostFunctionInvalidSeverity. -+# Guest-emitted events (fprime.event host function). One event per F´ -+# severity; the host function switches on the requested severity id to -+# emit the matching one so the event carries the correct FPP severity. - # ---------------------------------------------------------------------- - -+@ A FATAL event emitted by the guest program -+event LogFatal(msg: string size Wasm.GUEST_EVENT_MESSAGE_SIZE) \ -+ severity fatal \ -+ format "{}" -+ -+@ A COMMAND event emitted by the guest program -+event LogCommand(msg: string size Wasm.GUEST_EVENT_MESSAGE_SIZE) \ -+ severity command \ -+ format "{}" -+ - @ A WARNING_HI event emitted by the guest program - event LogWarningHi(msg: string size Wasm.GUEST_EVENT_MESSAGE_SIZE) \ - severity warning high \ -diff --git a/Svc/WasmSequencer/WasmSequencerHelpers.cpp b/Svc/WasmSequencer/WasmSequencerHelpers.cpp -index 1fe89cb2..4c643772 100644 ---- a/Svc/WasmSequencer/WasmSequencerHelpers.cpp -+++ b/Svc/WasmSequencer/WasmSequencerHelpers.cpp -@@ -156,10 +156,11 @@ void WasmSequencer ::createStore() { - this->takeAllocatorLock(); - - spacewasm_host_t host; -- spacewasm_status_t status = spacewasm_host_new(1, &host); -+ spacewasm_status_t status = spacewasm_host_new(2, &host); - FW_ASSERT(status == SPACEWASM_OK, status); - - this->hostFprimeV1(&host); -+ this->hostEnv(&host); - - spacewasm_compiler_options_t options; - options.allow_memory_grow = true; // implemented in a restricted way (see guestRealloc) -diff --git a/Svc/WasmSequencer/WasmSequencerHost.cpp b/Svc/WasmSequencer/WasmSequencerHost.cpp -index 26d9223e..f4d9ba1a 100644 ---- a/Svc/WasmSequencer/WasmSequencerHost.cpp -+++ b/Svc/WasmSequencer/WasmSequencerHost.cpp -@@ -4,6 +4,7 @@ - // \brief cpp file for WasmSequencer Wasm Host functions - // ====================================================================== - -+#include - #include - - #include "Fw/Types/Assert.hpp" -@@ -36,6 +37,52 @@ static spacewasm_hostcall_result_t hostFunctionTrampoline(struct spacewasm_calle - return (static_cast(userdata)->*Handler)(caller, params, n_params, out_result); - } - -+void WasmSequencer ::hostEnv(spacewasm_host_t* host) { -+ // The float library calls LLVM materializes for guest float arithmetic -+ // (llvm.pow, frem, llvm.log). wasm-ld leaves them under wasm's default -+ // import module "env". They are pure math, so they answer synchronously. -+ spacewasm_status_t status; -+ U32 module_idx; -+ status = spacewasm_add_host_module(host, "env", 3, 0, &module_idx); -+ FW_ASSERT(status == SPACEWASM_OK, status); -+ -+ status = spacewasm_add_host_function( -+ host, module_idx, "pow", "dd", "d", -+ [](spacewasm_caller_t*, void*, const spacewasm_value_t* params, size_t n_params, spacewasm_value_t* out) { -+ FW_ASSERT(params != nullptr); -+ FW_ASSERT(n_params == 2, static_cast(n_params)); -+ out->tag = SPACEWASM_F64; -+ out->u.f64_ = std::pow(params[0].u.f64_, params[1].u.f64_); -+ return SPACEWASM_CONTINUE_SOME; -+ }, -+ nullptr); -+ FW_ASSERT(status == SPACEWASM_OK, status); -+ -+ status = spacewasm_add_host_function( -+ host, module_idx, "fmod", "dd", "d", -+ [](spacewasm_caller_t*, void*, const spacewasm_value_t* params, size_t n_params, spacewasm_value_t* out) { -+ FW_ASSERT(params != nullptr); -+ FW_ASSERT(n_params == 2, static_cast(n_params)); -+ out->tag = SPACEWASM_F64; -+ out->u.f64_ = std::fmod(params[0].u.f64_, params[1].u.f64_); -+ return SPACEWASM_CONTINUE_SOME; -+ }, -+ nullptr); -+ FW_ASSERT(status == SPACEWASM_OK, status); -+ -+ status = spacewasm_add_host_function( -+ host, module_idx, "log", "d", "d", -+ [](spacewasm_caller_t*, void*, const spacewasm_value_t* params, size_t n_params, spacewasm_value_t* out) { -+ FW_ASSERT(params != nullptr); -+ FW_ASSERT(n_params == 1, static_cast(n_params)); -+ out->tag = SPACEWASM_F64; -+ out->u.f64_ = std::log(params[0].u.f64_); -+ return SPACEWASM_CONTINUE_SOME; -+ }, -+ nullptr); -+ FW_ASSERT(status == SPACEWASM_OK, status); -+} -+ - void WasmSequencer ::hostFprimeV1(spacewasm_host_t* host) { - spacewasm_status_t status; - U32 module_idx; -diff --git a/Svc/WasmSequencer/WasmSequencerInterpreter.cpp b/Svc/WasmSequencer/WasmSequencerInterpreter.cpp -index 4be05e70..72221553 100644 ---- a/Svc/WasmSequencer/WasmSequencerInterpreter.cpp -+++ b/Svc/WasmSequencer/WasmSequencerInterpreter.cpp -@@ -363,14 +363,16 @@ void WasmSequencer ::dispatchEvent() { - } - stringStorage[this->m_pendingHostFunction.u.event.msgLen] = 0; - -- // Emit the event at the guest-requested severity. FATAL and -- // COMMAND are forbidden for guest programs (FATAL would let -- // untrusted code trigger the FatalHandler; COMMAND is reserved -- // for the command dispatcher). A forbidden or out-of-range -- // severity is reported via HostFunctionInvalidSeverity, carrying -- // the raw id and the guest message, and the guest continues. -+ // Emit the event at the guest-requested severity. An -+ // out-of-range severity emits nothing and the guest continues. - const I32 rawSeverity = static_cast(this->m_pendingHostFunction.u.event.rawSeverity); - switch (static_cast(rawSeverity)) { -+ case Fw::LogSeverity::FATAL: -+ this->log_FATAL_LogFatal(msg); -+ break; -+ case Fw::LogSeverity::COMMAND: -+ this->log_COMMAND_LogCommand(msg); -+ break; - case Fw::LogSeverity::WARNING_HI: - this->log_WARNING_HI_LogWarningHi(msg); - break; -@@ -386,10 +388,7 @@ void WasmSequencer ::dispatchEvent() { - case Fw::LogSeverity::DIAGNOSTIC: - this->log_DIAGNOSTIC_LogDiagnostic(msg); - break; -- case Fw::LogSeverity::FATAL: -- case Fw::LogSeverity::COMMAND: - default: -- this->log_WARNING_HI_HostFunctionInvalidSeverity(rawSeverity, msg); - break; - } - diff --git a/test/harness/wasm/tester/WasmSequencerTester.cpp b/test/harness/wasm/WasmSequencerTester.cpp similarity index 100% rename from test/harness/wasm/tester/WasmSequencerTester.cpp rename to test/harness/wasm/WasmSequencerTester.cpp diff --git a/test/harness/wasm/tester/WasmSequencerTester.fpp b/test/harness/wasm/WasmSequencerTester.fpp similarity index 100% rename from test/harness/wasm/tester/WasmSequencerTester.fpp rename to test/harness/wasm/WasmSequencerTester.fpp diff --git a/test/harness/wasm/tester/WasmSequencerTester.hpp b/test/harness/wasm/WasmSequencerTester.hpp similarity index 100% rename from test/harness/wasm/tester/WasmSequencerTester.hpp rename to test/harness/wasm/WasmSequencerTester.hpp diff --git a/test/harness/wasm/settings.ini b/test/harness/wasm/settings.ini deleted file mode 100644 index 937bac3..0000000 --- a/test/harness/wasm/settings.ini +++ /dev/null @@ -1,3 +0,0 @@ -[fprime] -framework_path: ../../fprime-wasm -project_root: ../../.. diff --git a/test/harness/wasm/tester/CMakeLists.txt b/test/harness/wasm/tester/CMakeLists.txt deleted file mode 100644 index 6189424..0000000 --- a/test/harness/wasm/tester/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -#### -# The WasmSequencerTester component that stands in for the rest of the -# topology around a real Svc::WasmSequencer. The JSON protocol layer is -# shared with the FpySequencer harness (../../). -#### - -register_fprime_module( - AUTOCODER_INPUTS - "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.fpp" - SOURCES - "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/../../Harness.cpp" - "${CMAKE_CURRENT_LIST_DIR}/../../Json.cpp" - HEADERS - "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.hpp" - "${CMAKE_CURRENT_LIST_DIR}/../../Harness.hpp" - "${CMAKE_CURRENT_LIST_DIR}/../../Json.hpp" - DEPENDS - Svc_WasmSequencer -) From d76619d27802ffb326ce78d381316787cbae3a2b Mon Sep 17 00:00:00 2001 From: Zimri Leisher Date: Thu, 22 Oct 2026 10:54:59 -0400 Subject: [PATCH 2/3] Switch to just one fprime submod --- .github/actions/spelling/expect.txt | 4 +- .github/workflows/fprime-gds-tests.yml | 10 +- CMakeLists.txt | 25 +++-- README.md | 8 +- pyproject.toml | 9 +- src/fpy/compiler.py | 2 + src/fpy/harness.py | 93 ++++++------------ src/fpy/macros.py | 42 ++++---- src/fpy/semantics.py | 18 +++- src/fpy/test_helpers.py | 7 ++ test/fprime | 2 +- test/fpy/test_arithmetic.py | 8 ++ test/fpy/test_aug_assign.py | 8 +- test/fpy/test_functions.py | 5 + test/fpy/test_imports.py | 4 + test/fpy/test_logging.py | 26 ++--- test/fpy/test_seq_calling.py | 6 ++ test/fpy/test_sequence_metadata.py | 19 ++++ test/fpy/test_wasm.py | 6 +- test/harness/CMakeLists.txt | 16 +-- test/harness/config/AcConstants.fpp | 73 ++++++++++++++ test/harness/config/CMakeLists.txt | 15 +++ test/harness/config/FpConfig.fpp | 97 +++++++++++++++++++ test/harness/fpy/CMakeLists.txt | 27 ++++++ ...yHarnessMain.cpp => FpySeqHarnessMain.cpp} | 2 +- test/harness/fpy/FpySequencerTester.cpp | 2 +- test/harness/fpy/FpySequencerTester.hpp | 6 +- test/harness/wasm/CMakeLists.txt | 42 ++++---- test/harness/wasm/WasmSeqHarnessMain.cpp | 2 +- test/harness/wasm/WasmSequencerTester.cpp | 25 +++-- test/harness/wasm/WasmSequencerTester.hpp | 6 +- uv.lock | 25 +++-- 32 files changed, 449 insertions(+), 191 deletions(-) create mode 100644 test/harness/config/AcConstants.fpp create mode 100644 test/harness/config/CMakeLists.txt create mode 100644 test/harness/config/FpConfig.fpp create mode 100644 test/harness/fpy/CMakeLists.txt rename test/harness/fpy/{FpyHarnessMain.cpp => FpySeqHarnessMain.cpp} (84%) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 2551eac..938366d 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -27,6 +27,7 @@ ctx cuz dedenting Deframer +deinit Deser downcasted downcasting @@ -84,12 +85,11 @@ itrunc kinda lalr libcall +libm llo LOGACTIVITYHI LOGACTIVITYLO -LOGCOMMAND LOGDIAGNOSTIC -LOGFATAL LOGWARNINGHI LOGWARNINGLO lvar diff --git a/.github/workflows/fprime-gds-tests.yml b/.github/workflows/fprime-gds-tests.yml index 3f3a41c..57a2776 100644 --- a/.github/workflows/fprime-gds-tests.yml +++ b/.github/workflows/fprime-gds-tests.yml @@ -22,11 +22,11 @@ jobs: steps: - uses: actions/checkout@v4 # The tests run sequences on the real Svc::FpySequencer and - # Svc::WasmSequencer, through C++ harnesses built from these two - # checkouts of fprime. The harnesses are built by the test session - # itself; see "Running tests" in the README. - - name: Check out the fprime submodules - run: git submodule update --init test/fprime test/fprime-wasm + # Svc::WasmSequencer, through C++ harnesses built from this checkout + # of fprime. The harnesses are built by the test session itself; see + # "Running tests" in the README. + - name: Check out the fprime submodule + run: git submodule update --init test/fprime - name: Set up uv and Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v5 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 963fa00..985d563 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,31 +1,28 @@ #### -# FpyHarness: +# Fpy test harnesses: # -# The test harness executable that runs compiled fpy sequences on the real -# Svc::FpySequencer. The pytest suite starts this program and talks to it over -# stdin/stdout; see test/harness/README.md. +# The test harness executables that run compiled sequences on the real +# sequencers of the fprime submodule: FpySeqHarness runs fpy bytecode on +# Svc::FpySequencer, WasmSeqHarness runs wasm modules on Svc::WasmSequencer. +# The pytest suite starts them and talks to them over stdin/stdout; see +# test/harness/Harness.hpp. # # Build with the tools from the `harness` dependency group: # uv run fprime-util generate -# uv run fprime-util build +# uv run fprime-util build --target FpySeqHarness +# uv run fprime-util build --target WasmSeqHarness #### cmake_minimum_required(VERSION 3.18) cmake_policy(SET CMP0048 NEW) -project(FpyHarness VERSION 1.0.0 LANGUAGES C CXX) +project(FpyHarnesses VERSION 1.0.0 LANGUAGES C CXX) -# The harness wires ports together at runtime, which direct port calls +# The harnesses wire ports together at runtime, which direct port calls # (the default outside unit-test builds) compile away. add_compile_definitions(FW_DIRECT_PORT_CALLS=0) include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime.cmake") include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime-Code.cmake") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/test/harness/config") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/test/harness") - -register_fprime_deployment( - SOURCES - "${CMAKE_CURRENT_LIST_DIR}/test/harness/FpyHarnessMain.cpp" - DEPENDS - test_harness -) diff --git a/README.md b/README.md index 94321f4..0a27537 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,10 @@ log("hello world!") # creates an ACTIVITY_HI event with the text "hello world!" You can configure the severity of the event: ```py log("uh oh", Fw.LogSeverity.WARNING_HI) -log("oh no!", Fw.LogSeverity.FATAL) +log("all clear", Fw.LogSeverity.DIAGNOSTIC) ``` -All F Prime severity levels are supported. +All F Prime severity levels are supported except `FATAL` and `COMMAND`, which are reserved for the flight software. At the moment, only constant string arguments are supported. See [Strings](#strings). @@ -749,11 +749,11 @@ Some tests compile and run sequences. Those tests will generate both Fpy bytecod To run these tests, the following steps are required: -* The submodules must be checked out: `git submodule update --init test/fprime test/fprime-wasm` +* The fprime submodule must be checked out: `git submodule update --init test/fprime` * The harness build tools (cmake, ninja, fprime-util, fpp) and the `wasm` extra. `uv sync` installs them as part of the dev environment; with pip the extra is `pip install -e '.[wasm]'`. * A Rust toolchain (the wasm sequencer builds the spacewasm interpreter with cargo). Install via [rustup](https://rustup.rs). -Each harness is built automatically when the first test that needs it runs. The wasm harness build applies a small local patch to the submodule first (see `test/harness/patches/README.md`). +Each harness is built automatically when the first test that needs it runs. ### `--backend` diff --git a/pyproject.toml b/pyproject.toml index 773ad94..620e7b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ fprime-fpy-disasm = "fpy.main:disassemble_main" fprime-fpy-cmd = "fpy.main:cmd_main" #### -# The test suite lives in test/fpy. The fprime submodules and the harness +# The test suite lives in test/fpy. The fprime submodule and the harness # build trees carry their own (huge) test collections, so keep pytest from # ever recursing into them. #### @@ -75,7 +75,6 @@ fprime-fpy-cmd = "fpy.main:cmd_main" testpaths = ["test/fpy"] norecursedirs = [ "fprime", - "fprime-wasm", "harness", "build-*", ".*", @@ -95,7 +94,7 @@ norecursedirs = [ #### # The test suite covers both backends, so a development environment picks up # the wasm extra without having to ask for it. The harness group holds the -# tools that build the C++ test harness from the fprime submodules; the pins +# tools that build the C++ test harnesses from the fprime submodule; the pins # match test/fprime/requirements.txt. #### [dependency-groups] @@ -107,8 +106,8 @@ dev = [ ] harness = [ "cmake==3.26.0", - "fprime-fpp==3.3.0", - "fprime-tools==4.3.0", + "fprime-fpp==3.4.0", + "fprime-tools==4.4.0", "ninja==1.11.1.4", ] diff --git a/src/fpy/compiler.py b/src/fpy/compiler.py index 9007649..5b0e868 100644 --- a/src/fpy/compiler.py +++ b/src/fpy/compiler.py @@ -43,6 +43,7 @@ CollectUsedFunctions, ResolveTransitiveGlobalUses, CheckGlobalsInitializedBeforeCall, + CheckLogSeverities, CheckSequenceArgs, DefineFunctions, DefineVariables, @@ -294,6 +295,7 @@ def analyze_ast(body: AstBlock, state: CompileState) -> CompileState: CheckConstArrayAccesses(), WarnRangesAreNotEmpty(), CheckSequenceArgs(), + CheckLogSeverities(), # now that semantic analysis is done, we can desugar things. # Fill in default arguments before desugaring for loops DesugarDefaultArgs(), diff --git a/src/fpy/harness.py b/src/fpy/harness.py index d924d86..9efc3de 100644 --- a/src/fpy/harness.py +++ b/src/fpy/harness.py @@ -1,8 +1,8 @@ -"""Client for the C++ test harness that runs sequences on the real -Svc::FpySequencer (see test/harness). The harness program is started once and -reused: each request is one JSON line on its stdin, each reply one JSON line -on its stdout, and every request runs on a brand-new sequencer instance -inside the harness.""" +"""Client for the C++ test harnesses that run sequences on the real +Svc::FpySequencer and Svc::WasmSequencer (see test/harness). Each harness +program is started once and reused: each request is one JSON line on its +stdin, each reply one JSON line on its stdout, and every request runs on a +brand-new sequencer instance inside the harness.""" from __future__ import annotations @@ -12,18 +12,9 @@ from pathlib import Path REPO_ROOT = Path(__file__).parent.parent.parent -FPY_HARNESS_BINARY = ( - REPO_ROOT / "build-artifacts" / "Linux" / "FpyHarness" / "bin" / "FpyHarness" -) -_WASM_HARNESS_ROOT = REPO_ROOT / "test" / "harness" / "wasm" -WASM_HARNESS_BINARY = ( - _WASM_HARNESS_ROOT - / "build-artifacts" - / "Linux" - / "WasmSeqHarness" - / "bin" - / "WasmSeqHarness" -) +_BUILD_ARTIFACTS = REPO_ROOT / "build-artifacts" / "Linux" +FPY_HARNESS_BINARY = _BUILD_ARTIFACTS / "FpySeqHarness" / "bin" / "FpySeqHarness" +WASM_HARNESS_BINARY = _BUILD_ARTIFACTS / "WasmSeqHarness" / "bin" / "WasmSeqHarness" class HarnessError(Exception): @@ -31,61 +22,36 @@ class HarnessError(Exception): a malformed reply, or crashed. Distinct from a sequence failing.""" -def build_harness() -> None: +def build_fpy_harness() -> None: """Builds the FpySequencer harness executable from the fprime submodule. The build is incremental, so this is cheap when nothing changed.""" - if not (REPO_ROOT / "test" / "fprime" / "CMakeLists.txt").exists(): - raise HarnessError( - "the fprime submodule is not checked out. Run:\n" - " git submodule update --init test/fprime" - ) - _fprime_util_build(REPO_ROOT) + _build_deployment("FpySeqHarness") def build_wasm_harness() -> None: - """Builds the WasmSequencer harness executable from the fprime-wasm - submodule, with the exit-code patch applied (test/harness/patches).""" - if not (REPO_ROOT / "test" / "fprime-wasm" / "CMakeLists.txt").exists(): - raise HarnessError( - "the fprime-wasm submodule is not checked out. Run:\n" - " git submodule update --init test/fprime-wasm" - ) - _apply_wasm_patch() - _fprime_util_build(_WASM_HARNESS_ROOT, build_args=["--all"]) - - -def _apply_wasm_patch() -> None: - """Applies the local WasmSequencer fixes to the fprime-wasm submodule if - they are not applied yet (see test/harness/patches/README.md).""" - patch = REPO_ROOT / "test" / "harness" / "patches" / "wasm-sequencer-fixes.patch" - submodule = REPO_ROOT / "test" / "fprime-wasm" - applied = subprocess.run( - ["git", "apply", "--check", "--reverse", str(patch)], - cwd=submodule, - capture_output=True, - ) - if applied.returncode == 0: - return - result = subprocess.run( - ["git", "apply", str(patch)], cwd=submodule, capture_output=True, text=True - ) - if result.returncode != 0: - raise HarnessError( - f"could not apply {patch.name} to test/fprime-wasm:\n{result.stderr}" - ) + """Builds the WasmSequencer harness executable from the fprime submodule. + The build is incremental, so this is cheap when nothing changed.""" + _build_deployment("WasmSeqHarness") -def _fprime_util_build(deployment: Path, build_args: list[str] = None) -> None: - if not (deployment / "build-fprime-automatic-native").exists(): - _run_fprime_util(deployment, ["generate"]) - _run_fprime_util(deployment, ["build"] + (build_args or [])) +def _build_deployment(target: str) -> None: + """Builds and installs one of the harness deployments (see the root + CMakeLists.txt), generating the shared build cache first if needed.""" + if not (REPO_ROOT / "test" / "fprime" / "CMakeLists.txt").exists(): + raise HarnessError( + "the fprime submodule is not checked out. Run:\n" + " git submodule update --init test/fprime" + ) + if not (REPO_ROOT / "build-fprime-automatic-native").exists(): + _run_fprime_util(["generate"]) + _run_fprime_util(["build", "--target", target]) -def _run_fprime_util(deployment: Path, args: list[str]) -> None: +def _run_fprime_util(args: list[str]) -> None: try: result = subprocess.run( ["fprime-util"] + args, - cwd=deployment, + cwd=REPO_ROOT, capture_output=True, text=True, ) @@ -96,7 +62,7 @@ def _run_fprime_util(deployment: Path, args: list[str]) -> None: ) if result.returncode != 0: raise HarnessError( - f"'fprime-util {' '.join(args)}' in {deployment} failed. Are the " + f"'fprime-util {' '.join(args)}' failed. Are the " "harness build tools installed (uv sync installs them)?\n" + result.stdout[-2000:] + result.stderr[-2000:] @@ -134,7 +100,8 @@ def run(self, request: dict) -> dict: def _start(self) -> None: if not self._binary.exists(): raise HarnessError( - f"harness binary {self._binary} does not exist; build it with build_harness()" + f"harness binary {self._binary} does not exist; build it with " + "build_fpy_harness() or build_wasm_harness()" ) self._stderr_file = tempfile.TemporaryFile(mode="w+") self._process = subprocess.Popen( @@ -176,7 +143,7 @@ def fpy_harness() -> SequencerHarness: raise _fpy_build_error if _fpy_harness is None: try: - build_harness() + build_fpy_harness() except HarnessError as e: _fpy_build_error = e raise diff --git a/src/fpy/macros.py b/src/fpy/macros.py index 2edfc8b..75182f4 100644 --- a/src/fpy/macros.py +++ b/src/fpy/macros.py @@ -330,6 +330,29 @@ def generate_write_to_port_llvm(builder, args): const_arg_indices=frozenset({0, 1, 2}), ) +LOG_MACRO = BuiltinFuncSymbol( + "log", + NOTHING, + [ + ("message", INTERNAL_STRING, None), + ("severity", LOG_SEVERITY, FpyValue(LOG_SEVERITY, "ACTIVITY_HI")), + ], + lambda n, c, t: [ + PushValDirective(c[1].serialize()), + PushValDirective(c[0].val.encode("utf-8")), + PushValDirective( + FpyValue(StackSizeType, len(c[0].val.encode("utf-8"))).serialize() + ), + PopEventDirective(), + ], + generate_log_event_llvm, + const_arg_indices=frozenset({0, 1}), +) + +# The Fw.LogSeverity values a sequence may not log at: FATAL and COMMAND are +# reserved for the flight software itself. +RESERVED_LOG_SEVERITIES = frozenset({"FATAL", "COMMAND"}) + MACROS: dict[str, BuiltinFuncSymbol] = { "sleep": MACRO_SLEEP_SECONDS_USECONDS, "sleep_until": BuiltinFuncSymbol( @@ -367,24 +390,7 @@ def generate_write_to_port_llvm(builder, args): # The generate function should never be called since this is always const-evaluated "time": TIME_MACRO, # Event logging builtin — compile-time string + severity, defaults to ACTIVITY_HI - "log": BuiltinFuncSymbol( - "log", - NOTHING, - [ - ("message", INTERNAL_STRING, None), - ("severity", LOG_SEVERITY, FpyValue(LOG_SEVERITY, "ACTIVITY_HI")), - ], - lambda n, c, t: [ - PushValDirective(c[1].serialize()), - PushValDirective(c[0].val.encode("utf-8")), - PushValDirective( - FpyValue(StackSizeType, len(c[0].val.encode("utf-8"))).serialize() - ), - PopEventDirective(), - ], - generate_log_event_llvm, - const_arg_indices=frozenset({0, 1}), - ), + "log": LOG_MACRO, # Serial write: port typed by the dictionary-backed Svc.Fpy.SerialPortIndex enum; value typed SIZED "write_to_port": BuiltinFuncSymbol( "write_to_port", diff --git a/src/fpy/semantics.py b/src/fpy/semantics.py index ff841f4..f5bc866 100644 --- a/src/fpy/semantics.py +++ b/src/fpy/semantics.py @@ -10,7 +10,7 @@ from typing import Union from fpy.error import CompileError, diagnostic_context -from fpy.macros import TIME_MACRO +from fpy.macros import LOG_MACRO, RESERVED_LOG_SEVERITIES, TIME_MACRO from fpy.types import ( pick_binary_op_case, pick_unary_op_case, @@ -2987,6 +2987,22 @@ def visit_AstRange(self, node: AstRange, state: CompileState): state.warn(WarningType.EMPTY_RANGE, "Range is empty", node) +class CheckLogSeverities(Visitor): + """Check that no log() call uses a reserved severity.""" + + def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState): + if state.resolved_symbols[node.func] is not LOG_MACRO: + return + severity_arg = state.resolved_args[node][1] + if not is_instance_compat(severity_arg, Ast): + # the default severity, which is not reserved + return + severity = state.const_expr_values[severity_arg] + if severity.val in RESERVED_LOG_SEVERITIES: + state.err(f"Cannot log an event with severity {severity.val}", severity_arg) + return + + class CheckSequenceArgs(Visitor): """Check sequence argument constraints: - vararg data fits in the SeqArgs buffer diff --git a/src/fpy/test_helpers.py b/src/fpy/test_helpers.py index 0cee698..1e395c3 100644 --- a/src/fpy/test_helpers.py +++ b/src/fpy/test_helpers.py @@ -82,6 +82,13 @@ class ValidationError(Exception): # the single_backend fixture). active_backends: tuple[str, ...] = ALL_BACKENDS +# fpybc_only reasons for sequences the Svc::WasmSequencer cannot run. +NO_WASM_SEQ_ARGS = "the WasmSequencer cannot pass sequence arguments" +NO_WASM_LIBM = ( + "the WasmSequencer does not provide the libm functions (wasm import " + "module env) that float %, ** and ln compile to" +) + def _catch_all_seq_maps(seq_dir: str | None) -> list[tuple[str, str]] | None: """A catch-all seq map rooting every called sequence's source in *seq_dir*.""" diff --git a/test/fprime b/test/fprime index cd3845f..a37ffa4 160000 --- a/test/fprime +++ b/test/fprime @@ -1 +1 @@ -Subproject commit cd3845fc2ba957aa65751c4851046ee1c7d3ef3b +Subproject commit a37ffa40871e567481d0a7626c12b24d8acdbf15 diff --git a/test/fpy/test_arithmetic.py b/test/fpy/test_arithmetic.py index 9efb469..143db73 100644 --- a/test/fpy/test_arithmetic.py +++ b/test/fpy/test_arithmetic.py @@ -7,6 +7,7 @@ assert_compile_failure, assert_run_failure, assert_run_success, + NO_WASM_LIBM, ) @@ -293,6 +294,7 @@ def test_chain_div(self, fprime_test_api): class TestPowerModLog: + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_pow_unsigned(self, fprime_test_api): seq = """ var1: U32 = 20 @@ -303,6 +305,7 @@ def test_pow_unsigned(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_pow_signed(self, fprime_test_api): seq = """ var1: I32 = -20 @@ -313,6 +316,7 @@ def test_pow_signed(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_pow_float(self, fprime_test_api): seq = """ var1: F32 = 4.0 @@ -323,6 +327,7 @@ def test_pow_float(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_pow_zero_to_negative_is_inf(self, fprime_test_api): """0 ** is a pole: C/IEEE pow() yields +inf, not a crash. @@ -339,6 +344,7 @@ def test_pow_zero_to_negative_is_inf(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_log(self, fprime_test_api): seq = """ if ln(4.0) > 1.385 and ln(4.0) < 1.387: @@ -347,6 +353,7 @@ def test_log(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_mod_float(self, fprime_test_api): seq = """ var1: F32 = 25.25 @@ -775,6 +782,7 @@ def test_float_mod_by_zero_is_nan(self, fprime_test_api): assert_run_success(fprime_test_api, seq) + @pytest.mark.skip(NO_WASM_LIBM) @pytest.mark.wasm_only("the VM computes fmod, which yields NaN, instead") def test_float_mod_by_zero_faults(self, fprime_test_api): seq = """ diff --git a/test/fpy/test_aug_assign.py b/test/fpy/test_aug_assign.py index 137d053..25aed24 100644 --- a/test/fpy/test_aug_assign.py +++ b/test/fpy/test_aug_assign.py @@ -1,4 +1,9 @@ -from fpy.test_helpers import assert_compile_failure, assert_run_success +import pytest +from fpy.test_helpers import ( + assert_compile_failure, + assert_run_success, + NO_WASM_LIBM, +) class TestAugmentedAssignment: @@ -46,6 +51,7 @@ def test_mod_assign(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_pow_assign(self, fprime_test_api): seq = """ f: F64 = 4.5 diff --git a/test/fpy/test_functions.py b/test/fpy/test_functions.py index e7c9c44..07fadba 100644 --- a/test/fpy/test_functions.py +++ b/test/fpy/test_functions.py @@ -1,9 +1,11 @@ +import pytest from fpy.types import U32 from fpy.test_helpers import ( assert_compile_failure, assert_run_failure, assert_run_success, + NO_WASM_LIBM, ) from fpy.error import WarningType @@ -313,6 +315,7 @@ def noop(): """ assert_compile_failure(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_function_named_pow_does_not_replace_exponent(self, fprime_test_api): """A script function may be named after a float libcall (pow, fmod) without becoming the implementation of the operator that libcall @@ -340,6 +343,7 @@ def fmod(a: F64, b: F64) -> F64: """ assert_run_success(fprime_test_api, seq) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_function_named_log_does_not_replace_ln(self, fprime_test_api): seq = """ def log(x: F64) -> F64: @@ -354,6 +358,7 @@ def log(x: F64) -> F64: fprime_test_api, seq, expected_warnings={WarningType.SHADOW_CALLABLE} ) + @pytest.mark.fpybc_only(NO_WASM_LIBM) def test_variable_named_pow_does_not_break_exponent(self, fprime_test_api): seq = """ pow: F64 = 2.0 diff --git a/test/fpy/test_imports.py b/test/fpy/test_imports.py index faf4691..2d4e8ab 100644 --- a/test/fpy/test_imports.py +++ b/test/fpy/test_imports.py @@ -84,10 +84,13 @@ from pathlib import Path +import pytest + from fpy.test_helpers import ( assert_compile_failure, assert_run_success, compile_seq, + NO_WASM_SEQ_ARGS, ) from fpy.error import WarningType from fpy.types import U32, FpyValue @@ -405,6 +408,7 @@ class TestImporterWithArguments: declaring its own sequence arguments is fine: it may both take arguments and import another (argument-less) sequence, and use both together.""" + @pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_importer_declares_args_and_imports(self, fprime_test_api, tmp_path): _write_sequence( tmp_path, diff --git a/test/fpy/test_logging.py b/test/fpy/test_logging.py index 120d81e..93d730f 100644 --- a/test/fpy/test_logging.py +++ b/test/fpy/test_logging.py @@ -15,16 +15,21 @@ def test_default_severity(self, fprime_test_api): """ assert_run_success(fprime_test_api, seq) - @pytest.mark.skipif( - "config.getoption('--use-gds')", - reason="FATAL severity kills the program on the GDS", - ) def test_explicit_severity(self, fprime_test_api): seq = """ -log("oh no", Fw.LogSeverity.FATAL) +log("oh no", Fw.LogSeverity.WARNING_HI) """ assert_run_success(fprime_test_api, seq) + @pytest.mark.parametrize("severity", ["FATAL", "COMMAND"]) + def test_reserved_severity_rejected(self, fprime_test_api, severity): + seq = f""" +log("oh no", Fw.LogSeverity.{severity}) +""" + assert_compile_failure( + fprime_test_api, seq, match=f"Cannot log an event with severity {severity}" + ) + def test_default_severity_is_activity_hi(self, fprime_test_api): seq = """ log("test message") @@ -36,16 +41,16 @@ def test_default_severity_is_activity_hi(self, fprime_test_api): assert push_vals[-3].val == bytes([5]) assert push_vals[-2].val == b"test message" - def test_explicit_fatal(self, fprime_test_api): + def test_explicit_warning_lo(self, fprime_test_api): seq = """ -log("critical", Fw.LogSeverity.FATAL) +log("careful", Fw.LogSeverity.WARNING_LO) """ _, directives, _ = compile_seq(seq) push_vals = [d for d in directives if isinstance(d, PushValDirective)] assert len(push_vals) >= 3 - # FATAL = 1 - assert push_vals[-3].val == bytes([1]) - assert push_vals[-2].val == b"critical" + # WARNING_LO = 3 + assert push_vals[-3].val == bytes([3]) + assert push_vals[-2].val == b"careful" def test_explicit_warning_hi(self, fprime_test_api): seq = """ @@ -86,7 +91,6 @@ def test_multiple_events(self, fprime_test_api): seq = """ log("test") log("test", Fw.LogSeverity.DIAGNOSTIC) -log("test", Fw.LogSeverity.COMMAND) log("test", Fw.LogSeverity.ACTIVITY_HI) log("test", Fw.LogSeverity.ACTIVITY_LO) log("test", Fw.LogSeverity.WARNING_HI) diff --git a/test/fpy/test_seq_calling.py b/test/fpy/test_seq_calling.py index f7a6fdb..8f00f17 100644 --- a/test/fpy/test_seq_calling.py +++ b/test/fpy/test_seq_calling.py @@ -34,6 +34,7 @@ compile_seq, default_dictionary, write_child_seq as _write_child, + NO_WASM_SEQ_ARGS, ) @@ -90,6 +91,7 @@ def test_call_child_no_args(self, fprime_test_api): assert_run_success(fprime_test_api, parent_seq, seq_dir=tmpdir) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) class TestSeqCallingWithArgs: """Test calling child sequences with various argument types.""" @@ -402,6 +404,7 @@ def test_two_calls_to_same_child(self): self._analyze(parent_seq, _catch_all_seq_maps(tmpdir)) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) class TestSeqCallingNested: """Test nested sequence execution: parent -> child -> grandchild.""" @@ -500,6 +503,7 @@ def test_branch_on_child_failure(self, fprime_test_api): """ assert_run_success(fprime_test_api, parent_seq, seq_dir=tmpdir) + @pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_branch_on_success_with_args(self, fprime_test_api): """Parent branches on OK response from a child that receives args.""" with tempfile.TemporaryDirectory() as tmpdir: @@ -520,6 +524,7 @@ def test_branch_on_success_with_args(self, fprime_test_api): """ assert_run_success(fprime_test_api, parent_seq, seq_dir=tmpdir) + @pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_branch_on_failure_wrong_arg(self, fprime_test_api): """Parent detects failure when child gets wrong arg value.""" with tempfile.TemporaryDirectory() as tmpdir: @@ -541,6 +546,7 @@ def test_branch_on_failure_wrong_arg(self, fprime_test_api): assert_run_success(fprime_test_api, parent_seq, seq_dir=tmpdir) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) class TestSeqCallingNamedArgs: """Test calling child sequences with named arguments.""" diff --git a/test/fpy/test_sequence_metadata.py b/test/fpy/test_sequence_metadata.py index d7a1db1..24b5f35 100644 --- a/test/fpy/test_sequence_metadata.py +++ b/test/fpy/test_sequence_metadata.py @@ -5,6 +5,7 @@ assert_compile_success, assert_run_success, assert_run_failure, + NO_WASM_SEQ_ARGS, ) from fpy.bytecode.directives import DirectiveErrorCode from fpy.error import WarningType @@ -334,6 +335,7 @@ def test_defining_sequence_in_if_stmt(fprime_test_api): # ---- End-to-end arg passing tests ---- +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_run_sequence_with_u32_arg(fprime_test_api): """Run a sequence that takes a U32 arg and uses it.""" seq = """ @@ -343,6 +345,7 @@ def test_run_sequence_with_u32_arg(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 42)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_run_sequence_with_multiple_args(fprime_test_api): """Run a sequence that takes multiple args of different types.""" seq = """ @@ -365,6 +368,7 @@ def test_run_sequence_no_args_expected_none_provided(fprime_test_api): assert_run_success(fprime_test_api, seq) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_run_sequence_args_wrong_size(fprime_test_api): """Passing wrong-size args should raise an error.""" seq = """ @@ -386,6 +390,7 @@ def test_run_sequence_args_expected_but_missing(fprime_test_api): # ---- Value assertion tests ---- +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_value_u32(fprime_test_api): """Assert on the value of a U32 arg.""" seq = """ @@ -395,6 +400,7 @@ def test_arg_value_u32(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 42)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_value_bool_true(fprime_test_api): """Assert on a bool arg being True.""" seq = """ @@ -404,6 +410,7 @@ def test_arg_value_bool_true(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(BOOL, True)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_value_bool_false(fprime_test_api): """Assert on a bool arg being False.""" seq = """ @@ -413,6 +420,7 @@ def test_arg_value_bool_false(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(BOOL, False)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_value_arithmetic(fprime_test_api): """Use arg in arithmetic and assert the result.""" seq = """ @@ -424,6 +432,7 @@ def test_arg_value_arithmetic(fprime_test_api): ) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_value_in_if(fprime_test_api): """Use arg as a condition and verify control flow.""" seq = """ @@ -436,6 +445,7 @@ def test_arg_value_in_if(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(BOOL, True)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_wrong_value_fails_assert(fprime_test_api): """Passing a value that doesn't satisfy the assert should fail.""" seq = """ @@ -450,6 +460,7 @@ def test_arg_wrong_value_fails_assert(fprime_test_api): ) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_multiple_args_correct_offsets(fprime_test_api): """Each arg should be at the correct frame offset and readable independently.""" seq = """ @@ -465,6 +476,7 @@ def test_multiple_args_correct_offsets(fprime_test_api): ) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_args_and_locals_coexist(fprime_test_api): """Args and local variables should both be accessible.""" seq = """ @@ -476,6 +488,7 @@ def test_args_and_locals_coexist(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 5)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_passed_to_function(fprime_test_api): """Sequence arg can be passed to a user-defined function.""" seq = """ @@ -489,6 +502,7 @@ def double(v: U32) -> U64: assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 42)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_returned_from_function(fprime_test_api): """Function can return a value derived from an arg, and the arg is still valid after.""" seq = """ @@ -509,6 +523,7 @@ def add(a: U32, b: U32) -> U64: ) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_with_flags_modification(fprime_test_api): """Modifying flags should not corrupt sequence args.""" seq = """ @@ -522,6 +537,7 @@ def test_arg_with_flags_modification(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 7)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_with_flags_and_locals(fprime_test_api): """Args, flags, and locals should all coexist without stack corruption.""" seq = """ @@ -541,6 +557,7 @@ def test_arg_with_flags_and_locals(fprime_test_api): ) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_arg_survives_function_call(fprime_test_api): """Args should not be corrupted after a function call modifies the stack.""" seq = """ @@ -558,6 +575,7 @@ def work(v: U32) -> U64: assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 5)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_modify_arg(fprime_test_api): """Sequence args can be reassigned.""" seq = """ @@ -569,6 +587,7 @@ def test_modify_arg(fprime_test_api): assert_run_success(fprime_test_api, seq, args=[FpyValue(U32, 10)]) +@pytest.mark.fpybc_only(NO_WASM_SEQ_ARGS) def test_modify_arg_does_not_affect_other_args(fprime_test_api): """Modifying one arg doesn't corrupt another.""" seq = """ diff --git a/test/fpy/test_wasm.py b/test/fpy/test_wasm.py index 6ce4ced..9685d4a 100644 --- a/test/fpy/test_wasm.py +++ b/test/fpy/test_wasm.py @@ -184,9 +184,11 @@ def test_default_severity_is_activity_hi(self): assert events == [(5, "hello world")] # ACTIVITY_HI = 5 def test_explicit_severity(self): - code, events = run_seq_wasm_with_events('log("oh no", Fw.LogSeverity.FATAL)\n') + code, events = run_seq_wasm_with_events( + 'log("oh no", Fw.LogSeverity.WARNING_LO)\n' + ) assert code == NO_ERROR - assert events == [(1, "oh no")] # FATAL = 1 + assert events == [(3, "oh no")] # WARNING_LO = 3 def test_multiple_events_in_call_order(self): code, events = run_seq_wasm_with_events( diff --git a/test/harness/CMakeLists.txt b/test/harness/CMakeLists.txt index 37368fe..a78cada 100644 --- a/test/harness/CMakeLists.txt +++ b/test/harness/CMakeLists.txt @@ -1,22 +1,22 @@ #### # test_harness module: # -# The FpySequencerTester component that stands in for the rest of the topology -# around a real Svc::FpySequencer, plus the small JSON layer the harness uses -# to talk to pytest. +# The protocol both harness executables speak with pytest, plus the small +# JSON layer it is built on. Each harness executable lives in its own +# subdirectory, with the tester component that stands in for the rest of the +# topology around its sequencer. #### register_fprime_module( - AUTOCODER_INPUTS - "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.fpp" SOURCES - "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.cpp" "${CMAKE_CURRENT_LIST_DIR}/Harness.cpp" "${CMAKE_CURRENT_LIST_DIR}/Json.cpp" HEADERS - "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.hpp" "${CMAKE_CURRENT_LIST_DIR}/Harness.hpp" "${CMAKE_CURRENT_LIST_DIR}/Json.hpp" DEPENDS - Svc_FpySequencer + Fw_Types ) + +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/fpy") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/wasm") diff --git a/test/harness/config/AcConstants.fpp b/test/harness/config/AcConstants.fpp new file mode 100644 index 0000000..26222c3 --- /dev/null +++ b/test/harness/config/AcConstants.fpp @@ -0,0 +1,73 @@ +# ====================================================================== +# AcConstants.fpp +# F Prime configuration constants +# ====================================================================== + +@ Number of rate group member output ports for ActiveRateGroup +constant ActiveRateGroupOutputPorts = 10 + +@ Number of rate group member output ports for PassiveRateGroup +constant PassiveRateGroupOutputPorts = 10 + +@ Used to drive rate groups +constant RateGroupDriverRateGroupPorts = 3 + +@ Used for command and registration ports +constant CmdDispatcherComponentCommandPorts = 30 + +@ Used for uplink/sequencer buffer/response ports +constant CmdDispatcherSequencePorts = 5 + +@ Used for dispatching sequences to command sequencers +constant SeqDispatcherSequencerPorts = 2 + +@ Used for sizing the command splitter input arrays +constant CmdSplitterPorts = CmdDispatcherSequencePorts + +@ Number of static memory allocations +constant StaticMemoryAllocations = 4 + +@ Used to ping active components +constant HealthPingPorts = 25 + +@ Used for broadcasting completed file downlinks +constant FileDownCompletePorts = 1 + +@ Used for number of Fw::Com type ports supported by Svc::ComQueue +constant ComQueueComPorts = 2 + +@ Used for number of Fw::Buffer type ports supported by Svc::ComQueue +constant ComQueueBufferPorts = 1 + +@ Used for maximum number of connected buffer repeater consumers +constant BufferRepeaterOutputPorts = 10 + +@ Size of port array for DpManager +constant DpManagerNumPorts = 5 + +@ Size of data product routing port arrays for DpWriter +constant DpWriterNumPorts = 5 + +@ Size of processing port array for DpWriter +constant DpWriterNumProcPorts = 5 + +@ The size of a file name string +constant FileNameStringSize = 240 + +@ The size of an assert text string +constant FwAssertTextSize = 256 + +@ The size of a file name in an AssertFatalAdapter event (leading-truncation: +@ the tail of the path is kept) +@ Note: Svc::AssertFatalAdapter truncates to the smaller of this constant and +@ FW_LOG_STRING_MAX_SIZE (set in FpConstants.fpp), so raising this alone has no +@ effect once it exceeds that bound. With the defaults (240 here, 200 there) +@ FW_LOG_STRING_MAX_SIZE is what governs. +@ FwAssertTextSize (in this file) is a separate bound on the console assert +@ text, which also carries the timestamp and assert arguments. +constant AssertFatalAdapterEventFileSize = FileNameStringSize + +# fpy harness: 255, as in the deployment test/fpy/RefTopologyDictionary.json describes +@ The maximum size in bytes of the argument blob carried in a Svc::SeqArgs buffer +@ (CmdSeqIn / RUN / INVOKE). +constant SequenceArgumentsMaxSize = 255 diff --git a/test/harness/config/CMakeLists.txt b/test/harness/config/CMakeLists.txt new file mode 100644 index 0000000..88ca425 --- /dev/null +++ b/test/harness/config/CMakeLists.txt @@ -0,0 +1,15 @@ +#### +# test_harness_config: +# +# The F Prime configuration of the deployment the test dictionary +# (test/fpy/RefTopologyDictionary.json) describes, where it differs from the +# framework defaults. Each override is a copy of the framework's file with +# only the differing definitions changed. +#### + +register_fprime_config( + CONFIGURATION_OVERRIDES + "${CMAKE_CURRENT_LIST_DIR}/AcConstants.fpp" + "${CMAKE_CURRENT_LIST_DIR}/FpConfig.fpp" + INTERFACE +) diff --git a/test/harness/config/FpConfig.fpp b/test/harness/config/FpConfig.fpp new file mode 100644 index 0000000..52376b7 --- /dev/null +++ b/test/harness/config/FpConfig.fpp @@ -0,0 +1,97 @@ +# ====================================================================== +# \title config/FpConfig.fpp +# \author mstarch +# \brief FPP alias configuration file +# +# \copyright +# Copyright 2025, by the California Institute of Technology. +# ALL RIGHTS RESERVED. United States Government Sponsorship +# acknowledged. +# +# FPrime uses FPP to define a set of type aliases for various named types +# used throughout the system. This file is used to configure those types. +# ====================================================================== + +#### +# Integer type aliases: +# Used for the project to override types supplied by the platform for things like sizes, indices, etc. +#### + +@ The unsigned type of larger sizes internal to the software, +@ e.g., memory buffer sizes, file sizes. Must be unsigned. +type FwSizeType = PlatformSizeType + +@ The signed type of larger sizes internal to the software, used +@ for signed offsets, e.g., file seek offsets. Must be signed. +type FwSignedSizeType = PlatformSignedSizeType + +@ The type of smaller indices internal to the software, used +@ for array indices, e.g., port indices. Must be signed. +type FwIndexType = PlatformIndexType + +@ The type of arguments to assert functions. +type FwAssertArgType = PlatformAssertArgType + +@ The type of task priorities used. +type FwTaskPriorityType = PlatformTaskPriorityType + +@ The type of queue priorities used. +type FwQueuePriorityType = PlatformQueuePriorityType + +@ The id type. +type FwIdType = U32 + +@ The type of task identifiers used. +type FwTaskIdType = PlatformTaskIdType + +#### +# GDS type aliases: +# Used for the project to override types shared with GDSes and other remote systems. +#### + +@ The type of a telemetry channel identifier +dictionary type FwChanIdType = FwIdType + +@ The type of a data product identifier +type FwDpIdType = FwIdType + +@ The type of a data product priority +type FwDpPriorityType = U32 + +@ The type of an event identifier +dictionary type FwEventIdType = FwIdType + +@ The type of a command opcode +dictionary type FwOpcodeType = FwIdType + +@ The type of a parameter identifier +type FwPrmIdType = FwIdType + +# fpy harness: U16, as in the deployment test/fpy/RefTopologyDictionary.json describes +@ The type used to serialize a size value +dictionary type FwSizeStoreType = U16 + +@ The type used to serialize a time context value +dictionary type FwTimeContextStoreType = U8 + +@ The type of a telemetry packet identifier +type FwTlmPacketizeIdType = U16 + +@ The type of a trace identifier +type FwTraceIdType = U32 + +@ The type used to serialize a C++ enumeration constant +@ FPP enumerations are serialized according to their representation types +type FwEnumStoreType = I32 + +@ The type used to serialize a time base value +type FwTimeBaseStoreType = U16 + +@ Define enumeration for Time base types +dictionary enum TimeBase : FwTimeBaseStoreType { + TB_NONE = 0 @< No time base has been established (Required) + TB_PROC_TIME = 1 @< Indicates time is processor cycle time. Not tied to external time + TB_WORKSTATION_TIME = 2 @< Time as reported on workstation where software is running. For testing. (Required) + TB_SC_TIME = 3, @< Time as reported by the spacecraft clock. + TB_DONT_CARE = 0xFFFF @< Don't care value for sequences. If FwTimeBaseStoreType is changed, value should be changed (Required) +} default TB_NONE; diff --git a/test/harness/fpy/CMakeLists.txt b/test/harness/fpy/CMakeLists.txt new file mode 100644 index 0000000..00ea372 --- /dev/null +++ b/test/harness/fpy/CMakeLists.txt @@ -0,0 +1,27 @@ +#### +# FpySeqHarness: +# +# Runs compiled fpy sequences on a real Svc::FpySequencer. The +# FpySequencerTester component stands in for the rest of the topology around +# it. +#### + +register_fprime_module( + AUTOCODER_INPUTS + "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.fpp" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/FpySequencerTester.hpp" + DEPENDS + Svc_FpySequencer + test_harness +) + +register_fprime_deployment( + FpySeqHarness + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/FpySeqHarnessMain.cpp" + DEPENDS + test_harness_fpy +) diff --git a/test/harness/fpy/FpyHarnessMain.cpp b/test/harness/fpy/FpySeqHarnessMain.cpp similarity index 84% rename from test/harness/fpy/FpyHarnessMain.cpp rename to test/harness/fpy/FpySeqHarnessMain.cpp index 9360b15..48fcc2d 100644 --- a/test/harness/fpy/FpyHarnessMain.cpp +++ b/test/harness/fpy/FpySeqHarnessMain.cpp @@ -2,7 +2,7 @@ // fpy sequences on a real Svc::FpySequencer. See Harness.hpp for the // stdin/stdout protocol. -#include "test/harness/FpySequencerTester.hpp" +#include "test/harness/fpy/FpySequencerTester.hpp" #include "test/harness/Harness.hpp" int main() { diff --git a/test/harness/fpy/FpySequencerTester.cpp b/test/harness/fpy/FpySequencerTester.cpp index 2e9469a..7ec97f3 100644 --- a/test/harness/fpy/FpySequencerTester.cpp +++ b/test/harness/fpy/FpySequencerTester.cpp @@ -1,4 +1,4 @@ -#include "test/harness/FpySequencerTester.hpp" +#include "test/harness/fpy/FpySequencerTester.hpp" #include diff --git a/test/harness/fpy/FpySequencerTester.hpp b/test/harness/fpy/FpySequencerTester.hpp index c4d7b50..ba831a8 100644 --- a/test/harness/fpy/FpySequencerTester.hpp +++ b/test/harness/fpy/FpySequencerTester.hpp @@ -9,12 +9,12 @@ // class a friend, which is what lets the harness dispatch the message queue // and read internal state (state machine, stack, telemetry) directly. -#ifndef TEST_HARNESS_FPYSEQUENCERTESTER_HPP -#define TEST_HARNESS_FPYSEQUENCERTESTER_HPP +#ifndef TEST_HARNESS_FPY_FPYSEQUENCERTESTER_HPP +#define TEST_HARNESS_FPY_FPYSEQUENCERTESTER_HPP #include "Fw/Types/MallocAllocator.hpp" #include "Svc/FpySequencer/FpySequencer.hpp" -#include "test/harness/FpySequencerTesterComponentAc.hpp" +#include "test/harness/fpy/FpySequencerTesterComponentAc.hpp" #include "test/harness/Harness.hpp" namespace Svc { diff --git a/test/harness/wasm/CMakeLists.txt b/test/harness/wasm/CMakeLists.txt index 533a036..8fe1e35 100644 --- a/test/harness/wasm/CMakeLists.txt +++ b/test/harness/wasm/CMakeLists.txt @@ -1,32 +1,34 @@ #### # WasmSeqHarness: # -# The test harness executable that runs compiled wasm sequences on the real -# Svc::WasmSequencer, from the fprime-wasm submodule (with the local patch in -# ../patches applied so exit codes get reported). Same stdin/stdout protocol -# as FpyHarness. -# -# Build with: -# uv run fprime-util generate -d test/harness/wasm -# uv run fprime-util build -d test/harness/wasm +# Runs compiled wasm sequences on a real Svc::WasmSequencer. The +# WasmSequencerTester component stands in for the rest of the topology around +# it. #### -cmake_minimum_required(VERSION 3.18) -cmake_policy(SET CMP0048 NEW) -project(WasmSeqHarness VERSION 1.0.0 LANGUAGES C CXX) - -# The harness wires ports together at runtime, which direct port calls -# (the default outside unit-test builds) compile away. -add_compile_definitions(FW_DIRECT_PORT_CALLS=0) - -include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime.cmake") -include("${FPRIME_FRAMEWORK_PATH}/cmake/FPrime-Code.cmake") +# Svc_WasmSequencer is disabled without a usable Rust toolchain; FpySeqHarness +# still builds then. +if (NOT TARGET Svc_WasmSequencer) + fprime_cmake_status("[harness] WasmSeqHarness disabled: Svc_WasmSequencer is not available") + return() +endif() -add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tester") +register_fprime_module( + AUTOCODER_INPUTS + "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.fpp" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/WasmSequencerTester.hpp" + DEPENDS + Svc_WasmSequencer + test_harness +) register_fprime_deployment( + WasmSeqHarness SOURCES "${CMAKE_CURRENT_LIST_DIR}/WasmSeqHarnessMain.cpp" DEPENDS - test_harness_wasm_tester + test_harness_wasm ) diff --git a/test/harness/wasm/WasmSeqHarnessMain.cpp b/test/harness/wasm/WasmSeqHarnessMain.cpp index fe1b349..fbf4ca5 100644 --- a/test/harness/wasm/WasmSeqHarnessMain.cpp +++ b/test/harness/wasm/WasmSeqHarnessMain.cpp @@ -3,7 +3,7 @@ // for the stdin/stdout protocol. #include "test/harness/Harness.hpp" -#include "test/harness/wasm/tester/WasmSequencerTester.hpp" +#include "test/harness/wasm/WasmSequencerTester.hpp" int main() { return harness::harnessMain(); diff --git a/test/harness/wasm/WasmSequencerTester.cpp b/test/harness/wasm/WasmSequencerTester.cpp index 77f569c..8ec3102 100644 --- a/test/harness/wasm/WasmSequencerTester.cpp +++ b/test/harness/wasm/WasmSequencerTester.cpp @@ -1,6 +1,4 @@ -#include "test/harness/wasm/tester/WasmSequencerTester.hpp" - -#include +#include "test/harness/wasm/WasmSequencerTester.hpp" #include "Fw/Com/ComPacket.hpp" #include "config/SerialPortIndexEnumAc.hpp" @@ -35,6 +33,13 @@ WasmSequencerTester::WasmSequencerTester() this->m_sequencer.loadParameters(); } +WasmSequencerTester::~WasmSequencerTester() { + // Releases the pools configure() allocated and the sequencer's slot in + // the process-wide allocator registry, which only has room for a few + // sequencers at a time. + this->m_sequencer.deinit(); +} + void WasmSequencerTester::connectPorts() { WasmSequencer& seq = this->m_sequencer; @@ -95,14 +100,10 @@ void WasmSequencerTester::sendRunCommand() { return; } - Svc::SeqArgs seqArgs; - if (request.args.size() > sizeof seqArgs.get_buffer()) { - this->m_result.error = "sequence arguments do not fit in Svc::SeqArgs"; - return; - } - seqArgs.set_size(request.args.size()); + // The WasmSequencer runs sequences without arguments. if (!request.args.empty()) { - (void)std::memcpy(seqArgs.get_buffer(), request.args.data(), request.args.size()); + this->m_result.error = "the WasmSequencer cannot pass sequence arguments"; + return; } Fw::CmdArgBuffer args; @@ -110,8 +111,6 @@ void WasmSequencerTester::sendRunCommand() { FW_ASSERT(status == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast(status)); status = args.serializeFrom(Svc::BlockState(Svc::BlockState::BLOCK)); FW_ASSERT(status == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast(status)); - status = args.serializeFrom(seqArgs); - FW_ASSERT(status == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast(status)); this->cmdSend_out(0, WasmSequencer::OPCODE_RUN, 0, args); } @@ -260,10 +259,8 @@ void WasmSequencerTester::cmdRegIn_handler(FwIndexType portNum, FwOpcodeType opC bool WasmSequencerTester::isGuestLogEvent(FwEventIdType id) { switch (id) { - case WasmSequencer::EVENTID_LOGFATAL: case WasmSequencer::EVENTID_LOGWARNINGHI: case WasmSequencer::EVENTID_LOGWARNINGLO: - case WasmSequencer::EVENTID_LOGCOMMAND: case WasmSequencer::EVENTID_LOGACTIVITYHI: case WasmSequencer::EVENTID_LOGACTIVITYLO: case WasmSequencer::EVENTID_LOGDIAGNOSTIC: diff --git a/test/harness/wasm/WasmSequencerTester.hpp b/test/harness/wasm/WasmSequencerTester.hpp index f321d25..a0b85c8 100644 --- a/test/harness/wasm/WasmSequencerTester.hpp +++ b/test/harness/wasm/WasmSequencerTester.hpp @@ -16,13 +16,14 @@ #include "Fw/Types/MallocAllocator.hpp" #include "Svc/WasmSequencer/WasmSequencer.hpp" #include "test/harness/Harness.hpp" -#include "test/harness/wasm/tester/WasmSequencerTesterComponentAc.hpp" +#include "test/harness/wasm/WasmSequencerTesterComponentAc.hpp" namespace Svc { class WasmSequencerTester : public WasmSequencerTesterComponentBase { public: WasmSequencerTester(); + ~WasmSequencerTester(); // Runs one wasm module on the owned sequencer and reports what happened. harness::HarnessResult run(const harness::HarnessRequest& request); @@ -74,8 +75,7 @@ class WasmSequencerTester : public WasmSequencerTesterComponentBase { // True when the event is one the guest program logged (its log builtin). static bool isGuestLogEvent(FwEventIdType id); - // Backs the sequencer's configure() pools; declared before the sequencer, - // whose destructor deallocates through it. + // Backs the sequencer's configure() pools. Fw::MallocAllocator m_allocator; WasmSequencer m_sequencer; const harness::HarnessRequest* m_request = nullptr; diff --git a/uv.lock b/uv.lock index a4b5c21..c2a5e0c 100644 --- a/uv.lock +++ b/uv.lock @@ -281,14 +281,13 @@ wheels = [ [[package]] name = "fprime-fpp" -version = "3.3.0" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/60/6d37e9bed292553a67ec46f6664836dc293ef5ffb39550e414113ab519c5/fprime_fpp-3.3.0-py3-none-any.whl", hash = "sha256:615e76e05b8bdf1b5db2cbba34e8cc73f6a3067d14db3816a59194d046bfb660", size = 28847492, upload-time = "2026-08-19T17:01:00.855Z" }, - { url = "https://files.pythonhosted.org/packages/c8/bd/b00365c012349253e5df544e5df74c986cb137d306845237d34cdffe837a/fprime_fpp-3.3.0-py3-none-macosx_14_0_arm64.whl", hash = "sha256:36e3c75d257665592b28eb582a4cc9bf50a4d00621c129a2ceb8f31f9522e255", size = 45310063, upload-time = "2026-08-19T17:01:05.764Z" }, - { url = "https://files.pythonhosted.org/packages/96/f2/f279060badf709d698d9a075aaa949efbd2fe751d4dcd4a25ef8c7108cab/fprime_fpp-3.3.0-py3-none-macosx_15_0_x86_64.whl", hash = "sha256:465dc6948b78f15ee737caa00d5d728f04dbbb0857284c143a552c2d4eb546f1", size = 44867607, upload-time = "2026-08-19T17:01:03.027Z" }, - { url = "https://files.pythonhosted.org/packages/cc/1b/af2aad9e3a2000faa9428e8d8e46a571ac2e7c091fcc092484e1b58baa19/fprime_fpp-3.3.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:54ca7fad89a4adab1fb26bd69b38ea52a558213df0a3246e4edaa62edd1a74d3", size = 44858067, upload-time = "2026-08-19T17:00:56.357Z" }, - { url = "https://files.pythonhosted.org/packages/92/4f/c8d82dd761131568794bb5a1a103b8332000cec66b3d8d233f57d4fdd027/fprime_fpp-3.3.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b8a766eb44812dc231ce737350daacc354098b7cb8d29638a71940b74cdaec31", size = 44867011, upload-time = "2026-08-19T17:01:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/49/eb/9f0988a3c4cd945de656ab91ddf3cccbc1537376897b6524dcfa6b5780ee/fprime_fpp-3.4.0-py3-none-any.whl", hash = "sha256:3412bfed70362e75b2db5b0b6e50640e4d72784870bf85c1140ca859c224ecc1", size = 32031820, upload-time = "2026-09-28T20:37:10.993Z" }, + { url = "https://files.pythonhosted.org/packages/d7/4e/4fee02b9225656bebdbe39c14b785961aa591f58868a3637d78620505bde/fprime_fpp-3.4.0-py3-none-macosx_14_0_arm64.whl", hash = "sha256:95acab12f4bfd4b0c1d992aad9cfaaf4ca1d3bdeec01afd23626c4f4eb60484d", size = 48135188, upload-time = "2026-09-28T20:37:14.828Z" }, + { url = "https://files.pythonhosted.org/packages/83/cb/f60327529f269ae7b5d9696de0daf689d61346b7b844e3af5fe2056db9f9/fprime_fpp-3.4.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6e106d037915600c7151ad7031b8d93538a8d7c1e29f985ffd25e32be736e0f7", size = 47773795, upload-time = "2026-09-28T20:37:16.89Z" }, + { url = "https://files.pythonhosted.org/packages/34/c4/d0fd9de581873033561173348b11834d9ee23224b8af188340e6f6e12e3e/fprime_fpp-3.4.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:e0bf33d6a625b4fc999b7914553cbd51c43d7fb93ed2348080c11964b373dbed", size = 47940546, upload-time = "2026-09-28T20:37:19.119Z" }, ] [[package]] @@ -335,22 +334,22 @@ provides-extras = ["wasm"] dev = [ { name = "black", specifier = ">=26.5.1" }, { name = "cmake", specifier = "==3.26.0" }, - { name = "fprime-fpp", specifier = "==3.3.0" }, + { name = "fprime-fpp", specifier = "==3.4.0" }, { name = "fprime-fpy", extras = ["wasm"] }, - { name = "fprime-tools", specifier = "==4.3.0" }, + { name = "fprime-tools", specifier = "==4.4.0" }, { name = "ninja", specifier = "==1.11.1.4" }, { name = "pre-commit", specifier = ">=4.6.0" }, ] harness = [ { name = "cmake", specifier = "==3.26.0" }, - { name = "fprime-fpp", specifier = "==3.3.0" }, - { name = "fprime-tools", specifier = "==4.3.0" }, + { name = "fprime-fpp", specifier = "==3.4.0" }, + { name = "fprime-tools", specifier = "==4.4.0" }, { name = "ninja", specifier = "==1.11.1.4" }, ] [[package]] name = "fprime-tools" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cookiecutter" }, @@ -361,9 +360,9 @@ dependencies = [ { name = "requests" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/7b/aa6f01db64f85b6187f242782d54acabe0f3e985385921fd5c48e302b73b/fprime_tools-4.3.0.tar.gz", hash = "sha256:f38662a639689ee21d80caf164c9441bc057bb887a3480122595470b6fd95a01", size = 149495, upload-time = "2026-08-19T23:16:49.986Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6a263d7740d92c579103f18b9e232840ede25fa499ff5d8afd432c61ab37/fprime_tools-4.4.0.tar.gz", hash = "sha256:08fd52087b6dad97954fe62dc236b0e46b812737624108065a26a4862062e782", size = 151000, upload-time = "2026-09-30T22:10:48.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b5/4cb4f51c4cdb105dbb22ef239b52c6cb37081b71efa33ba487ebe9a277e1/fprime_tools-4.3.0-py3-none-any.whl", hash = "sha256:2be1d7c78927a288b94be8081c15d6893085d23bc4f2d275b51d6a81ac6df2b6", size = 154329, upload-time = "2026-08-19T23:16:48.579Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/6875b83e084032f9ce3c5e32c28a76366bafea25ee7a4d04dd6e5bab109b/fprime_tools-4.4.0-py3-none-any.whl", hash = "sha256:fe51389f06aa9783b11eeffb11a9fd15385ddbef6fe51f60afb28b8faba0be59", size = 154500, upload-time = "2026-09-30T22:10:46.956Z" }, ] [[package]] From cb604891b5156b4a79170bb21a20063bf7e58d95 Mon Sep 17 00:00:00 2001 From: Zimri Leisher Date: Thu, 22 Oct 2026 11:01:48 -0400 Subject: [PATCH 3/3] sp --- .github/actions/spelling/excludes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index db76388..64424b3 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -1,6 +1,7 @@ pyproject.toml setup.py ^uv\.lock$ +^test/harness/config/ ignore$ /html/ (?:^|/)(?i)COPYRIGHT