Collect co-located unit tests in CI, and correct the unit-test docs - #383
Draft
JohnYanxinLiu wants to merge 5 commits into
Draft
Collect co-located unit tests in CI, and correct the unit-test docs#383JohnYanxinLiu wants to merge 5 commits into
JohnYanxinLiu wants to merge 5 commits into
Conversation
Unit test source moved into <package>/test/ and is collected from colcon_unit_test_packages.yaml, but the surrounding documentation still described the mirror-directory-and-proxy scheme that replaced. Six per-layer stubs under tests/robot/ told authors to add tests in directories tests no longer live in, and tests/sim/motive_emulator/README.md proposed a NatNet emulator that was built at simulation/isaac-sim/extensions/optitrack.natnet.emulator/ instead. Remove them and rewrite the two tree READMEs as signposts. Correct the add-unit-tests and run-system-tests skills, which future agents read to work in this area, on four points they had wrong: - Running them. `pytest tests/` does not collect co-located unit tests — the injection in conftest.pytest_configure is skipped whenever a path is given on the command line. It reports "no tests collected" and exits 5, which reads as a failure but means nothing ran. `airstack test -m unit` and `cd tests && pytest -m unit` are the working forms; verified 155 passed vs exit 5. - CI. No workflow runs unit tests. system-tests.yml invokes `pytest tests/`, and fires only on PR-open, /pytest, or workflow_dispatch. - The mark. pytest_itemcollected applies @pytest.mark.unit by file location, so test sources should not declare it. The skill previously said "always decorate", which is where the redundant declarations came from. - colcon. It runs only what a package's CMakeLists registers. natnet_ros2 has ament_add_gtest but no ament_add_pytest_test, so its Python tests run only under the root harness. Also fixes a pytest_args example that would silently do nothing (`-m not linter`; ament's pytest runner ignores -m via PYTEST_ADDOPTS, and the real value is []), and the same stale layout claim in the testing docs and the emulator README. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
C++ gtests run under colcon test, which CI executes inside the robot container via the build_packages mark (test_build_packages.py::test_colcon_test_robot). Python unit tests run under the root harness, which no workflow invokes. Whether colcon test also picks up a package's Python tests depends on its build type: lidar_point_cloud_filter is ament_python and exposes them via setup.cfg (testpaths = test), so they run in both places; natnet_ros2 is ament_cmake and registers only ament_add_gtest, so its Python tests run nowhere in CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unit-test source lives outside tests/, so pytest_configure appends it to the collection args. That injection was gated on args_source != ARGS, which pytest sets for any positional path — including `tests/`. The intent was that `pytest tests/system/foo.py` should not drag in 155 unrelated tests, but the guard could not tell narrowing from naming the whole suite, so CI's `pytest tests/` collected 97 of 252 items and the Python unit tests ran nowhere. Decide on the paths instead: a positional is broad when it names tests/ itself or an ancestor, narrow otherwise. `pytest tests/` and `pytest .` inject; `pytest tests/system`, a single file, and a node id do not. Node ids are split on `::` first, since only the part before it addresses the filesystem. `any` rather than `all` is deliberate — pytest_configure appends the co-located files (narrow, absolute) to config.args, so `all` would flip the answer for anything re-deriving it after that mutation. The decision is also stashed on config for the contract test to read. tests/meta/test_collection_contract.py pins the behaviour: a table over broad/narrow invocations, a check that the command in system-tests.yml is classified broad (the test that would have caught this), and a check that every discovered file produced collected items. It lives under tests/ on purpose — co-located, it would stop being collected at the same moment it stopped guarding anything. Verified: `pytest tests/ -m unit` 0 -> 170 passed; `cd tests && pytest -m unit` unchanged at 170; `pytest tests/system/test_liveliness.py` still collects 16. Unit tests now run with every system-tests.yml invocation. That workflow's triggers are unchanged and intentional — PR open, /pytest, workflow_dispatch — since the same run drives the GPU system tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The split was documented as a fact without its reason. A gtest is a binary compiled against the package's headers and rclcpp, so it can only run where the ROS toolchain is — colcon test inside the robot container, which build_packages reaches after building with -DBUILD_TESTING=ON. Python unit tests stub ROS at the import boundary and touch no ROS runtime, so they need neither a build nor a container, which is what keeps the suite under a second. State the invariant that follows: a Python test needing a live ROS node belongs in tests/integration/ or tests/system/, not in a package test/ dir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test Metrics —
|
| Test | Pass | Fail | Skip | Rate |
|---|---|---|---|---|
| isaac-sim.extensions.PegasusSimulator.extensions.pegasus.simulator.pegasus.simulator.tests.test_hello_world | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.replicator.pose_generation.pose_tests.test_utils | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.benchmark.services.test_no_rendering | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_determinism | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_root | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_delete_in_contact | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_rendering | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_save_stage | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_time_stepping | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.cortex.framework.cortex_bringup_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros1.bridge.test_carter_lidar | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_carter_camera_multi_robot_nav | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_people_sim | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.physics.contact_sensor_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_lidar_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_radar_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_createstage_config | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extension_count | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_external | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extra_args | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fabric_frame_delay | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fetch_results | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_frame_delay | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ogn | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ovd | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_syntheticdata | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_unsaved_on_exit | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.isaac.dynamic_control.test_zero_step | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.replicator.agent.test_scripting | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.syntheticdata.test_basic | 0 | 1 | 0 | 0% |
| ros_packages.gui.rviz.rviz_behavior_tree_panel.scripts.test_behavior_tree_publisher | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.local.planners.takeoff_landing_planner.test.scripts.test_takeoff_landing_planner | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Baseline.DPVO.Pangolin.components.pango_python.pybind11.tests | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_circular_buffer | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_loadable | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_macvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_modules | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_sequence | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_tartanvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_frontend | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_matching | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_performance_macvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_stereo_depth | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_pep257 | 0 | 1 | 0 | 0% |
Metrics
| Test | Metric | Value |
|---|---|---|
| ros_packages.gui.rviz.rviz_behavior_tree_panel.scripts.test_behavior_tree_publisher | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_copyright | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_flake8 | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_pep257 | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_copyright | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_flake8 | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_pep257 | duration_s | 0s |
| ros_ws.src.local.planners.takeoff_landing_planner.test.scripts.test_takeoff_landing_planner | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Baseline.DPVO.Pangolin.components.pango_python.pybind11.tests | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_circular_buffer | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_loadable | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_macvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_modules | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_sequence | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_tartanvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_frontend | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_matching | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_performance_macvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_stereo_depth | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_copyright | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_flake8 | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_pep257 | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_copyright | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_flake8 | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_pep257 | duration_s | 0s |
| isaac-sim.extensions.PegasusSimulator.extensions.pegasus.simulator.pegasus.simulator.tests.test_hello_world | duration_s | 0s |
| isaac-sim.standalone_examples.replicator.pose_generation.pose_tests.test_utils | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.benchmark.services.test_no_rendering | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_determinism | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_root | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_delete_in_contact | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_rendering | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_save_stage | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_time_stepping | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.cortex.framework.cortex_bringup_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros1.bridge.test_carter_lidar | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_carter_camera_multi_robot_nav | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_people_sim | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.physics.contact_sensor_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_lidar_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_radar_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_createstage_config | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extension_count | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_external | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extra_args | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fabric_frame_delay | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fetch_results | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_frame_delay | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ogn | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ovd | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_syntheticdata | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_unsaved_on_exit | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.isaac.dynamic_control.test_zero_step | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.replicator.agent.test_scripting | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.syntheticdata.test_basic | duration_s | 0s |
They are hermetic and they guard the collection of everything above them, so running them after the GPU sim suites is backwards — a hung flight test would mean they never execute. Rank them in _MODULE_ORDER right after the co-located unit tests, ahead of system.test_build_docker. Also drop the `from conftest import repo_path` in favour of harness.discovery, which the module already imports from — one less thing between the test and the function it needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What features did you add and/or bugs did you address?
None.
CI was running zero unit tests, and had been for as long as the co-located layout has
existed.
Unit-test source lives outside
tests/(in<package>/test/, listed incolcon_unit_test_packages.yaml), so pytest never reaches it by recursion —conftest.pytest_configureappends those files to the collection args. That injection wasgated on
args_source != "ARGS", and pytest setsARGSfor any positional path,including
tests/. The intent was thatpytest tests/system/foo.pyshould not drag in155 unrelated tests, but the guard could not tell narrowing from naming the whole suite.
system-tests.ymlrunspytest tests/. So did nearly every documented command. Result:The Python unit tests for
natnet_ros2,lidar_point_cloud_filterandoptitrack.natnet.emulatorran nowhere in CI.natnet_ros2's 14 are the sharpest case —its
CMakeLists.txtregistersament_add_gtestbut noament_add_pytest_test, so theroot harness was their only possible runner.
This PR fixes the guard, adds contract tests so it cannot regress silently, and corrects
the documentation and agent skills that described the pre-co-location layout.
Not applicable — test-infrastructure and documentation change.
How did you implement it?
The guard. Decide on the paths rather than on "was a path given". A positional is
broad when it names
tests/itself or an ancestor of it, narrow otherwise(
harness/discovery.py):Three details carry the weight, and are the ones worth a reviewer's eye:
is_relative_to. It isroot.is_relative_to(arg)— is the testsroot inside the argument? Inverting it makes
pytest tests/systembroad andpytest tests/narrow, i.e. the original bug with extra steps.split("::")— positionals may be node ids (test_x.py::TestY::test_z); only thepart before
::addresses the filesystem.any, notall—pytest_configurethen appends the co-located files (narrow,absolute) to
config.args, soallwould flip the answer for anything re-deriving itafter that mutation. The decision is also stashed on
configso nothing has to.Scope of the behaviour change. Only
pytest <path-containing-tests/>changes. Barepytestfromtests/(whatairstack testruns) took theTESTPATHSbranch before andstill does.
Documentation. The repo still carried scaffolding from the mirror-directory-and-proxy
scheme that co-location replaced: six per-layer stub READMEs under
tests/robot/tellingauthors to add tests where tests no longer live, and
tests/sim/motive_emulator/README.mdproposing an emulator that was built at
simulation/isaac-sim/extensions/optitrack.natnet.emulator/instead. Several files alsopointed at
tests/sim/optitrack_natnet_emulator/, a directory that was never committed —following those instructions collected zero tests. Removed, with the two tree READMEs
rewritten as signposts.
The
add-unit-testsandrun-system-testsskills — what a future agent reads to workhere — were wrong on four counts, now corrected:
@pytest.mark.unit"pytest_itemcollectedapplies it by file location; writing it is redundant and warns undercolcon test. This instruction is where the redundant declarations came frompytest_args"-m not linter"[]— forwarded viaPYTEST_ADDOPTS, where ament's pytest runner ignores-m, so a marker expression there silently does nothingtests/sim/simulation/**/<ext>/test/Why C++ and Python take different runners — documented, since it was previously stated
as a bare fact. A gtest is a binary: it must be compiled against the package's headers and
rclcpp, so it only runs where the ROS toolchain is (colcon testin the robot container,which CI reaches via
build_packagesafter building with-DBUILD_TESTING=ON). Pythonunit tests are deliberately hermetic — they stub ROS at the import boundary and touch no
ROS runtime — so they need neither a build nor a container, which is what keeps the suite
under a second. The invariant that follows is now written down: a Python test needing a
live ROS node belongs in
tests/integration/ortests/system/, not a packagetest/dir.
colcon testin the robot containerbuild_packagesmarkament_pythonpackagecolcon testpytest tests/andbuild_packagesament_cmakepackagepytest tests/How do you run and use it?
No runtime behaviour changes. To see the fix:
Testing with PyTest
tests/meta/test_collection_contract.py— 15 tests in three groups:collection_is_broad— seven broad invocations (pytest tests/,pytest ., absolute paths, thetestpathsform) and six narrow ones (a subdirectory,a file, a node id, a co-located file). Pure function, microseconds, no subprocess.
system-tests.yml, extracts the actualpytest <path>positional, and asserts it classifies as broad. This is the test thatwould have caught the original bug, and it survives someone "fixing" CI by hand.
unit_test_files()discoveredcontributed at least one collected item. Catches breakage below the guard: YAML
drift, a glob change, an import error that turns a module into a collection error.
Skips on a narrowed run, where having none is correct.
These live under
tests/deliberately. Co-located, they would stop being collected at thesame moment they stopped guarding anything; under
tests/plain recursion finds them, soa broken guard makes them run and fail. They also carry the only hand-written
@pytest.mark.unitin the repo — not being co-located, the path-based marker does notreach them — commented as the exception.
airstack test -m unit -v170 passed, 97 deselected — 155 co-located unit tests plus the 15 contract tests.
On
developthe same command viapytest tests/ -m unitcollects nothing and exits 5.Measured on this branch:
pytest tests/ -m unit(CI)cd tests && pytest -m unitpytest tests/system/test_liveliness.pyDocumentation
n — no new pages. Existing pages corrected:
unit_testing.md,testing/index.md,ci_cd.md. None of the deleted READMEs were innav, so the build is unaffected.Yes. The docs and both agent skills now state which runner each language uses and why,
when each edit takes effect, and the hermetic-Python invariant that keeps the fast path
fast.
Not applicable.
Versioning
Yes —
0.19.0-alpha.17.Reviewer notes
155 tests will run on the self-hosted runner for the first time. They pass locally and
every
pxrimport isimportorskip-guarded (usd-coreis intests/requirements.txt),so there is no collection-error risk. The residual exposure is a handful of emulator tests
that bind ephemeral UDP loopback sockets. Worth one
/pytest -m uniton this PR beforemerge.
system-tests.ymltriggers are unchanged and intentionally so — PR open,/pytest,workflow_dispatch. Unit tests ride along with that workflow rather than getting aseparate always-on job, because the same run also drives the GPU system tests.
Untracked
__pycache__residue from the deleted proxy modules may still exist locally attests/sim/optitrack_natnet_emulator/andtests/robot/**/<pkg>/. Never tracked; not partof this diff.