From 62e93702c689e2b763d5c34e7bda7a223ff2558e Mon Sep 17 00:00:00 2001 From: JiwonSeok Date: Fri, 4 Sep 2026 23:32:45 +0900 Subject: [PATCH 1/2] ci: build Python bindings so the 17 registered pytest targets can run The ctest suite registers ~17 Python tests unconditionally (cosim/CMakeLists.txt gates them on Python3 being found, not on VDSIM_BUILD_PYTHON), but the CI Configure step used the default VDSIM_BUILD_PYTHON=OFF. The `vdsim` extension module was therefore never built and every one of those tests failed with ModuleNotFoundError -- 11 failed / 391 on all four matrix jobs of run 33879377041, identical across compiler and build type. - Configure: -DVDSIM_BUILD_PYTHON=ON and -Dpybind11_DIR from the installed pybind11, so python/CMakeLists.txt finds the config package without relying on Python3_EXECUTABLE (that find_package runs later, in cosim/). - apt: python3-dev for the extension headers. - pip: pybind11 pinned to 2.13.6 (it decides how the module is compiled, so an unpinned bump is a silent toolchain change), plus numpy and pyyaml which the test scripts import. - New "Report toolchain" step prints the Python/pybind11/numpy/pyyaml versions so drift between the CI runner and the measurement host stays visible. Verified on ailab-12 with the same flags (Unix Makefiles instead of Ninja, which is not installed there): 391/391 passed, 0 failed. Co-Authored-By: Claude Opus 5 --- .github/workflows/build.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68151bba..ca072d0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,9 +27,25 @@ jobs: sudo apt-get update sudo apt-get install -y \ ninja-build \ + python3-dev \ ${{ matrix.compiler.cc }} \ ${{ matrix.compiler.cxx }} + # The ctest suite registers ~17 Python tests unconditionally (cosim/ + # CMakeLists.txt); they import the `vdsim` extension module, so the + # bindings must be built or every one of them fails with + # ModuleNotFoundError. pybind11 is pinned: it decides how the module is + # compiled, so an unpinned bump is a silent toolchain change. + - name: Install Python dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install "pybind11==2.13.6" numpy pyyaml + + - name: Report toolchain + run: | + python3 --version + python3 -c "import pybind11, numpy, yaml; print('pybind11', pybind11.__version__); print('numpy', numpy.__version__); print('pyyaml', yaml.__version__)" + - name: Cache FetchContent uses: actions/cache@v4 with: @@ -40,7 +56,11 @@ jobs: env: CC: ${{ matrix.compiler.cc }} CXX: ${{ matrix.compiler.cxx }} - run: cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + run: | + cmake -G Ninja -B build \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DVDSIM_BUILD_PYTHON=ON \ + -Dpybind11_DIR="$(python3 -m pybind11 --cmakedir)" - name: Build run: cmake --build build -j From 8ff6e71f6bb425634d2bf256ac51d67c446a544c Mon Sep 17 00:00:00 2001 From: JiwonSeok Date: Sat, 5 Sep 2026 16:21:40 +0900 Subject: [PATCH 2/2] test: make the plant speed budget state which build it measures test_speed_budget asserted a 1.0 s wall budget for 5 s of simulated time. That number is the Release contract (real-time factor >= 5x). An unoptimised Debug build of the same code needs 3-6 s for the identical work, so on the Debug half of the CI matrix the assert reported the build type, not a regression (gcc-11 Debug 3.13 s, clang-14 Debug 5.36 s; both Release halves passed). The budget now comes from VDSIM_PERF_BUDGET_S and defaults to 1.0, so the Release contract is unchanged for anyone who sets nothing. The CI Test step passes 10.0 on Debug rows only. The test is not skipped or excluded on Debug: it still runs and still fails on a real regression, with roughly 2x headroom over the worst observed Debug time. Co-Authored-By: Claude Opus 5 --- .github/workflows/build.yml | 6 ++++++ tests/scripts/test_vla_plant.py | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca072d0d..926a40f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,12 @@ jobs: - name: Build run: cmake --build build -j + # The plant speed budget is a Release contract. Debug builds carry no + # optimisation, so they are given the budget that matches what they are, + # not an exemption: the assert still runs and still fails on a real + # regression. - name: Test working-directory: build + env: + VDSIM_PERF_BUDGET_S: ${{ matrix.build_type == 'Debug' && '10.0' || '1.0' }} run: ctest --output-on-failure diff --git a/tests/scripts/test_vla_plant.py b/tests/scripts/test_vla_plant.py index 0d6a9c8d..7535c679 100644 --- a/tests/scripts/test_vla_plant.py +++ b/tests/scripts/test_vla_plant.py @@ -2,6 +2,7 @@ """VLA plant acceptance smokes (docs/design/VLA_THESIS_PLANT.md).""" import math import sys +import os import time from pathlib import Path @@ -187,6 +188,13 @@ def test_longitudinal_fx_accel(): obs["ax"], obs["vx"])) +# Wall-clock budget for 5 s of simulated time. The default is the Release +# contract (real-time factor >= 5x). An unoptimised Debug build runs the same +# code 3-6x slower, which says nothing about a performance regression, so the +# caller states the budget that belongs to the build it produced. +BUDGET_S = float(os.environ.get("VDSIM_PERF_BUDGET_S", "1.0")) + + def test_speed_budget(): plant = VDSimPlant(base_mu=0.9, control_dt=DT, substep_dt=SUB) plant.reset([0.0, 0.0, 0.0, V0, 0.0, 0.0]) @@ -194,8 +202,12 @@ def test_speed_budget(): for _ in range(100): plant.step([0.02, 0.0]) elapsed = time.perf_counter() - t0 - assert elapsed < 1.0, f"5 s traj too slow: {elapsed:.2f} s wall" - print("speed budget: ok ({:.3f} s for 5 s sim)".format(elapsed)) + assert elapsed < BUDGET_S, ( + f"5 s traj too slow: {elapsed:.2f} s wall (budget {BUDGET_S:.2f} s, " + "override with VDSIM_PERF_BUDGET_S)" + ) + print("speed budget: ok ({:.3f} s for 5 s sim, budget {:.2f} s)".format( + elapsed, BUDGET_S)) def main():