Skip to content

Commit 83b5929

Browse files
committed
mass code cleaning
1 parent 8279d90 commit 83b5929

69 files changed

Lines changed: 533 additions & 510 deletions

File tree

Some content is hidden

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

.agents/skills/add-unit-tests/SKILL.md

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ robot/ros_ws/src/<layer>/<package>/
3535
└── CMakeLists.txt # wires ament_add_gtest under BUILD_TESTING
3636
3737
tests/robot/<layer>/<package>/
38-
└── test_<name>.py # ← thin PROXY (re-exports tests from above)
38+
└── test_<name>.py # ← thin PROXY (registers tests from above)
3939
```
4040

41-
The **proxy** is a one-file shim that loads the real test module with `importlib`
42-
and re-exports every `test_*` function. This means:
41+
The **proxy** is a one-file shim that calls ``register_unit_tests()`` to load the
42+
real test module with ``importlib`` and expose every ``test_*`` function to pytest.
43+
This means:
4344

4445
| Invocation | What runs |
4546
|---|---|
@@ -48,6 +49,39 @@ and re-exports every `test_*` function. This means:
4849
| CI `system-tests.yml` (PR open / approved) | Same path via `pytest tests/` |
4950
| `colcon test --packages-select <pkg>` | Real test in `package/test/` directly |
5051

52+
### `register_unit_tests` (in `tests/conftest.py`)
53+
54+
Proxies call this helper; they do not duplicate test logic. Signature:
55+
56+
```python
57+
register_unit_tests(target_globals, test_dir, *module_files)
58+
```
59+
60+
| Argument | Meaning |
61+
|---|---|
62+
| `target_globals` | Pass ``globals()`` from the proxy module — pytest collects ``test_*`` names injected here |
63+
| `test_dir` | Co-located test directory, usually ``repo_path("robot/ros_ws/src/<layer>/<pkg>/test")`` |
64+
| `*module_files` | One or more filenames under ``test_dir`` (e.g. ``"test_foo.py"``); fold several into one proxy |
65+
66+
**What it does (in order):**
67+
68+
1. Prepends ``test_dir`` and ``test_dir.parent`` (package or extension root) to
69+
``sys.path`` so loaded modules can import production code and sibling helpers.
70+
2. Loads each file via ``importlib.util.spec_from_file_location`` under a
71+
synthetic name (``_unit_<parent>_<stem>``) so proxy and source can share the
72+
same basename without circular imports.
73+
3. Copies every ``test_*`` callable from the loaded module into ``target_globals``.
74+
4. Wraps each with ``pytest.mark.unit`` so ``pytest tests/ -m unit`` selects them
75+
even if the source omitted ``pytestmark``.
76+
77+
**What it does not do:** run tests, install packages, or replace ``colcon test``.
78+
For local iteration against source only, run ``pytest <package>/test/`` (add a tiny
79+
``conftest.py`` in that dir for ``sys.path`` — see the emulator example below).
80+
81+
Pair with ``repo_path()`` from the same conftest — paths are anchored on
82+
``AIRSTACK_ROOT`` (CI export, repo root locally). **Never** use
83+
``Path(__file__).parents[N]`` in a proxy.
84+
5185
## Step-by-Step: Adding a Python Unit Test
5286

5387
### 1. Identify pure-Python logic to test
@@ -114,39 +148,29 @@ For `rclpy.node.Node` subclasses use a real dummy base class instead of a
114148

115149
### 3. Write the thin proxy in tests/robot/
116150

117-
Create `tests/robot/<layer>/<package>/test_<name>.py`. Use the shared
118-
`reexport_unit_tests` + `repo_path` helpers from `tests/conftest.py` so the proxy
119-
stays a two-call shim and the cross-tree path is anchored on `AIRSTACK_ROOT`
120-
(exported by CI, defaults to the repo root locally) — **never** count
121-
`Path(__file__).parents[N]` or hardcode `sys.path` walks in the proxy:
151+
Create `tests/robot/<layer>/<package>/test_<name>.py`. Use
152+
``register_unit_tests`` + ``repo_path`` from ``tests/conftest.py`` so the proxy
153+
stays a two-call shim:
122154

123155
```python
124156
# Copyright (c) 2024 Carnegie Mellon University
125157
# MIT License - see LICENSE in the repository root for full text.
126-
"""Proxy: re-exposes <package> unit tests from the package source tree.
158+
"""Proxy: registers <package> unit tests from the package source tree.
127159
128-
Unit test logic lives co-located with the package source (ROS 2 / colcon convention):
160+
Unit test logic lives co-located with the package (ROS 2 / colcon convention):
129161
robot/ros_ws/src/<layer>/<package>/test/test_<name>.py
130162
131-
This file makes those tests discoverable by ``pytest tests/`` (CI) and
132-
``airstack test -m unit`` without any changes to the CI workflow.
163+
Discoverable by ``pytest tests/`` (CI) and ``airstack test -m unit``.
133164
"""
134-
from conftest import reexport_unit_tests, repo_path
165+
from conftest import register_unit_tests, repo_path
135166

136-
reexport_unit_tests(
167+
register_unit_tests(
137168
globals(),
138169
repo_path("robot/ros_ws/src/<layer>/<package>/test"),
139170
"test_<name>.py", # pass several filenames to fold multiple modules into one proxy
140171
)
141172
```
142173

143-
`reexport_unit_tests` (in `tests/conftest.py`) execs each co-located module with
144-
`importlib` under a unique name (avoiding the same-filename circular import), puts
145-
both the test dir and its parent (the package root) on `sys.path` so the source
146-
can import its package and sibling helpers, and tags every re-exported `test_*`
147-
with `pytest.mark.unit`. Because the root `conftest` is imported before any proxy
148-
is collected, `from conftest import ...` resolves in both CI and local runs.
149-
150174
For a **direct** `pytest <package>/test/` (or `colcon test`) run — which bypasses
151175
the proxies — add a tiny `conftest.py` in the package `test/` dir that puts the
152176
package/extension root on `sys.path` (see
@@ -251,16 +275,16 @@ there are listed in [`tests/colcon_unit_test_packages.yaml`](../../../tests/colc
251275

252276
The same proxy pattern applies verbatim:
253277

254-
**Sim-side Python** (e.g. motive emulator protocol logic):
278+
**Sim-side Python** (e.g. OptiTrack NatNet emulator):
255279
```
256280
simulation/.../<tool>/test/test_<name>.py ← source
257-
tests/sim/<tool>/test_<name>.py ← proxy (parents[3] = repo root)
281+
tests/sim/<tool>/test_<name>.py ← proxy (register_unit_tests + repo_path)
258282
```
259283

260284
**GCS modules**:
261285
```
262286
gcs/.../<pkg>/test/test_<name>.py ← source
263-
tests/gcs/<pkg>/test_<name>.py ← proxy (parents[3] = repo root)
287+
tests/gcs/<pkg>/test_<name>.py ← proxy (register_unit_tests + repo_path)
264288
```
265289

266290
`pytest tests/ -m unit` discovers them through the proxy without any
@@ -274,7 +298,8 @@ pytest.ini or CI changes needed.
274298
|---|---|
275299
| Where does test source live? | `<component>/…/<package>/test/` (co-located with the package) |
276300
| Where does pytest discover tests? | `tests/robot/` (or `tests/sim/`, `tests/gcs/`) via thin proxy |
277-
| How does the proxy avoid circular import? | `importlib.util.spec_from_file_location` with a unique module name |
301+
| How does the proxy register tests? | ``register_unit_tests(globals(), repo_path(...), "test_*.py")`` in `tests/conftest.py` |
302+
| How does the proxy avoid circular import? | `importlib.util.spec_from_file_location` with a unique synthetic module name |
278303
| What mark do all unit tests use? | `@pytest.mark.unit` |
279304
| What CI workflow runs them? | `system-tests.yml` — runs `pytest tests/` which includes unit tests |
280305
| When does that workflow trigger? | PR opened, `/pytest` comment, `workflow_dispatch` |
@@ -296,8 +321,9 @@ Corresponding proxies: `tests/robot/perception/natnet_ros2/test_natnet_ros2.py`,
296321
## Files to Know
297322

298323
- `.github/workflows/system-tests.yml` — CI workflow (runs `pytest tests/` including unit tests)
324+
- `tests/conftest.py``register_unit_tests`, `repo_path`, shared fixtures
299325
- `tests/pytest.ini` — mark registration (`unit`, `build_docker`, etc.)
300326
- `tests/robot/` — proxy layer mirroring `robot/ros_ws/src/`
301-
- `tests/sim/` — proxy layer for sim-side code (future)
327+
- `tests/sim/` — proxy layer for sim-side extensions and tools
302328
- `tests/gcs/` — proxy layer for GCS code (future)
303329
- `tests/README.md` — full test harness reference

.agents/skills/optitrack-development/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ flowchart LR
3939
| Robot client | [`robot/ros_ws/src/perception/natnet_ros2/`](../../../robot/ros_ws/src/perception/natnet_ros2/) | ROS 2 node; uses **official NatNet SDK** (`NatNetClient::Connect`) |
4040
| SDK install | `natnet_ros2/lib/libNatNet.so`, `include/natnet/` | Download via `airstack setup --natnet` (proprietary, not in git) |
4141
| Emulator (WIP) | [`simulation/isaac-sim/extensions/optitrack.natnet.emulator/`](../../../simulation/isaac-sim/extensions/optitrack.natnet.emulator/) | Python NatNet **server** for sim / integration tests |
42-
| Integration tests | [`tests/integration/natnet/README.md`](../../../tests/integration/natnet/README.md) | End-to-end UDP tests against real SDK parser (marks: `integration`, `natnet`) |
42+
| Integration tests | [`tests/integration/natnet/README.md`](../../../tests/integration/natnet/README.md) | End-to-end UDP tests against real SDK parser (mark: `integration`) |
4343

4444
**Enable on robot:** `LAUNCH_NATNET=true` in `.env`[`perception.launch.xml`](../../../robot/ros_ws/src/perception/perception_bringup/launch/perception.launch.xml) includes `natnet_ros2.launch.py`.
4545

@@ -198,7 +198,7 @@ Use the SDK's `NatNetTypes.h` and `PacketClient.cpp` for on-wire layouts — not
198198
|-------|----------|-----------|
199199
| Unit (no network) | `test_natnet_logic.cpp`, `FakeNatNetClient` | Negotiation logic, topic names |
200200
| Protocol capture | Minimal client + UDP stub (see above) | Wire-format `NAT_CONNECT`, client endpoint model |
201-
| Integration | `tests/integration/natnet/` | Full SDK parser + `natnet_ros2_node` (marks: `integration`, `natnet`) |
201+
| Integration | `tests/integration/natnet/` | Full SDK parser + `natnet_ros2_node` (mark: `integration`) |
202202
| System (future) | `airstack test -m sensors` | Topic Hz on `/perception/optitrack/...` |
203203

204204
```bash

.agents/skills/run-system-tests/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This skill is about the **test harness itself** — pytest marks, fixtures, the
2525
The suite lives at `tests/` (repo root) and is fully pytest-based. Configuration is in `tests/pytest.ini` and shared infrastructure in `tests/conftest.py`.
2626

2727
- **`tests/system/`** — Docker stack integration tests. Marks: `build_docker`, `build_packages`, `liveliness`, `sensors`, `takeoff_hover_land`.
28-
- **`tests/robot/`** and **`tests/sim/`** — Hermetic **unit** tests (`@pytest.mark.unit`). These are **thin proxy files** that re-export tests from each ROS 2 package's own `test/` directory (co-located with the source, the ROS 2 / colcon convention). The proxy pattern keeps test source next to the code it tests while making tests discoverable by `pytest tests/`.
28+
- **`tests/robot/`** and **`tests/sim/`** — Hermetic **unit** tests (`@pytest.mark.unit`). These are **thin proxy files** that call ``register_unit_tests()`` to register tests from each package's co-located `test/` directory so `pytest tests/` discovers them.
2929

3030
### Unit tests vs system tests
3131

.env

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@ URDF_FILE="robot_descriptions/iris/urdf/iris_with_sensors.pegasus.robot.urdf"
5151

5252
DEBUG_RVIZ="false" # "true" or "false". If true, launches RViz alongside the robot via desktop_bringup/robot.launch.xml.
5353

54-
LAUNCH_NATNET="false"
55-
# PX4 SITL param profile for isaac-sim (simulation/isaac-sim/docker/px4-profiles/).
56-
# Use "vision" with LAUNCH_NATNET + MAVROS vision_pose; "default" for plain Pegasus SITL.
57-
PX4_PARAM_PROFILE="default"
58-
5954
# offboard API streaming out. this is so that ports don't conflict for multi-agent FCU communication.
6055
OFFBOARD_BASE_PORT=14540
6156
ONBOARD_BASE_PORT=14580

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,15 +197,15 @@ docker exec airstack-robot-desktop-1 bash -c "ros2 topic echo <topic_name> --onc
197197
- Verify module behavior in isolation
198198
- Test with synthetic data
199199
- Located in module's `test/` directory
200-
- **Run in the robot container** with `colcon test` (after `bws`), not via `airstack test -m unit`. The root [`tests/`](tests/) suite does **not** register a `unit` pytest mark; `airstack test -m <mark>` only selects marks declared in [`tests/pytest.ini`](tests/pytest.ini) (`unit`, `build_docker`, `build_packages`, `liveliness`, `sensors`, `takeoff_hover_land`, `integration`, `natnet`).
200+
- **Run in the robot container** with `colcon test` (after `bws`), not via `airstack test -m unit`. The root [`tests/`](tests/) suite does **not** register a `unit` pytest mark; `airstack test -m <mark>` only selects marks declared in [`tests/pytest.ini`](tests/pytest.ini) (`unit`, `build_docker`, `build_packages`, `integration`, `liveliness`, `sensors`, `takeoff_hover_land`).
201201

202202
```bash
203203
docker exec airstack-robot-desktop-1 bash -c "sws && colcon test --packages-select natnet_ros2 --event-handlers console_direct+"
204204
```
205205

206-
2. **Unit tests (`pytest`, `unit` mark):** Fast, hermetic checks. Test **source** lives co-located with each ROS 2 package in `<package>/test/` (standard colcon convention). Thin **proxy** files in [`tests/robot/`](tests/robot/) and [`tests/sim/`](tests/sim/) re-export those tests so `pytest tests/` discovers them. Unit tests run as part of the `system-tests.yml` suite. Example: `airstack test -m unit -v`. See `add-unit-tests` skill.
206+
2. **Unit tests (`pytest`, `unit` mark):** Fast, hermetic checks. Test **source** lives co-located with each ROS 2 package in `<package>/test/` (standard colcon convention). Thin **proxy** files in [`tests/robot/`](tests/robot/) and [`tests/sim/`](tests/sim/) call `register_unit_tests()` so `pytest tests/` discovers them. Unit tests run as part of the `system-tests.yml` suite. Example: `airstack test -m unit -v`. See `add-unit-tests` skill.
207207

208-
3. **Integration (`pytest`, `integration` mark, [`tests/integration/`](tests/integration/)):** Wire a few real components together — the robot autonomy container plus a host-side component — **without** a sim or GPU. The shared `robot_autonomy_stack` fixture reuses a running `robot-desktop` container or brings one up only when `--run-integration` is passed (else skips), so a plain `pytest tests/` never spins up Docker. First resident: `tests/integration/natnet/` (host NatNet emulator → `natnet_ros2` pose Hz; marks `integration`, `natnet`). Example: `airstack test -m integration --run-integration -v`.
208+
3. **Integration (`pytest`, `integration` mark, [`tests/integration/`](tests/integration/)):** Wire a few real components together — the robot autonomy container plus a host-side component — **without** a sim or GPU. The shared `robot_autonomy_stack` fixture reuses a running `robot-desktop` container or brings one up only when `--run-integration` is passed (else skips), so a plain `pytest tests/` never spins up Docker. Collection order runs integration after `build_docker` and `build_packages`. First resident: `tests/integration/natnet/` (host NatNet emulator → `natnet_ros2` pose Hz). Example: `airstack test -m integration --run-integration -v`.
209209

210210
4. **System Level (`tests/system/`):** Full simulation tests (Isaac Sim or Microsoft AirSim legacy)
211211
- End-to-end autonomy stack testing

docs/development/intermediate/testing/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ hardware requirement:
1212
## Unit tests (`pytest -m unit`)
1313

1414
Fast, hermetic Python tests that run in seconds with no Docker or GPU. Test source
15-
lives **co-located with its ROS 2 package** (`<package>/test/`) and is re-exported
16-
through thin proxy files in `tests/robot/` for centralized discovery.
15+
lives **co-located with its ROS 2 package** (`<package>/test/`) and is registered
16+
through thin proxy files in `tests/robot/` via `register_unit_tests()`.
1717

1818
```bash
1919
airstack test -m unit -v

docs/development/intermediate/testing/unit_testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ AirStack unit tests are **fast, hermetic, and purely Python** — no Docker stac
55
## Design principles
66

77
- **Co-located with source.** Test files live in `<package>/test/` alongside the code they test. This is the standard ROS 2 / colcon convention and ensures tests are discovered by both `colcon test` and `pytest`.
8-
- **Proxy for centralized discovery.** A thin shim in `tests/robot/<layer>/<package>/` re-exports the test functions so `pytest tests/` (the CI command) and `airstack test -m unit` discover them without any changes to the CI workflow.
8+
- **Proxy for centralized discovery.** A thin shim in `tests/robot/<layer>/<package>/` calls `register_unit_tests()` (in `tests/conftest.py`) to register co-located `test_*` functions so `pytest tests/` and `airstack test -m unit` discover them. See the `add-unit-tests` skill for the full contract.
99
- **`@pytest.mark.unit` on every test.** The `unit` mark is the filter that keeps unit tests isolated from system tests that need Docker, GPUs, and sim licenses.
1010

1111
## Repository layout

docs/simulation/isaac_sim/docker.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ simulation/isaac-sim/docker/
1212
├── fastdds.xml # DDS configuration
1313
├── omni_pass.env # Omniverse credentials (git-ignored)
1414
├── omni_pass_TEMPLATE.env # Template for credentials
15+
├── sitl-files/ # SITL env bundles (PX4_PARAM_* via compose)
16+
│ ├── default.env # Plain Pegasus SITL (no param overrides)
17+
│ └── px4-vision.env # External vision / NatNet → MAVROS → EKF2
1518
├── omniverse.toml # Omniverse settings
1619
├── user.config.json # Isaac Sim configuration (enables extensions)
1720
└── user_TEMPLATE.config.json # Template configuration
@@ -116,11 +119,14 @@ Key variables for Isaac Sim configuration:
116119
| `ISAAC_SIM_SCRIPT_NAME` | Standalone script filename | - |
117120
| `PX4_PHYSICS_HZ` | Physics step rate for PX4 SITL — also sets PX4 `IMU_INTEG_RATE` | `250` |
118121
| `PX4_RENDERING_HZ` | Rendering frame rate for PX4 profiles (independent of physics) | `60` |
122+
| `SITL_PARAM_PROFILE` | SITL env bundle stem under `sitl-files/` (e.g. `default`, `px4-vision`) | `default` |
119123
| `ARDUPILOT_PHYSICS_HZ` | Physics step rate for ArduPilot SITL | `800` |
120124
| `ARDUPILOT_RENDERING_HZ` | Rendering frame rate for ArduPilot profiles | `120` |
121125

122126
`PX4_PHYSICS_HZ` and `PX4_RENDERING_HZ` are set in the top-level `.env`. Pegasus defaults to 250 Hz but AirStack runs PX4 at **100 Hz** for near-real-time performance. See [Pegasus Scene Setup → Physics Rate](pegasus_scene_setup.md) for valid values and the full configuration flow.
123127

128+
SITL parameter bundles live in `sitl-files/` (`default.env`, `px4-vision.env`). Compose loads `sitl-files/${SITL_PARAM_PROFILE}.env` into the container; see [Pegasus Scene Setup → PX4 SITL env bundles](pegasus_scene_setup.md#px4-sitl-env-bundles-external-vision).
129+
124130
**Example overrides:**
125131

126132
```bash

docs/simulation/isaac_sim/pegasus_scene_setup.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,19 @@ PX4_RENDERING_HZ="30"
165165
- **`PX4_PHYSICS_HZ`** — Sets `physics_dt = 1 / PX4_PHYSICS_HZ` in Isaac Sim's physics scene, and automatically syncs PX4's `IMU_INTEG_RATE` parameter to the same value via `PX4LaunchTool``px4-rc.simulator`. Patched within the Docker image to read the environment variable and set the IMU_INTEG_RATE parameter.
166166
- **`PX4_RENDERING_HZ`** — Sets the rendering frame rate independently of physics. 30 Hz rendering has no effect on physics accuracy or PX4 behavior, but does slightly affect performance due to resource usage.
167167

168-
### PX4 parameter profiles (external vision)
168+
### PX4 SITL env bundles (external vision)
169169

170-
Isaac Sim loads a layered PX4 SITL parameter profile from `simulation/isaac-sim/docker/px4-profiles/`. Select the profile with `PX4_PARAM_PROFILE` in the top-level `.env`:
170+
Isaac Sim loads optional PX4 SITL parameter bundles from `simulation/isaac-sim/docker/sitl-files/`. Select the bundle with `SITL_PARAM_PROFILE` in the top-level `.env` (compose maps this to `sitl-files/<profile>.env`):
171171

172172
```bash
173-
# Plain Pegasus SITL
174-
PX4_PARAM_PROFILE="default"
173+
# Plain Pegasus SITL (default.env — comments only, no PX4_PARAM_* overrides)
174+
SITL_PARAM_PROFILE="default"
175175

176-
# NatNet / mocap → MAVROS vision_pose → EKF2 external vision
177-
PX4_PARAM_PROFILE="vision"
176+
# NatNet / mocap → MAVROS vision_pose → EKF2 external vision (px4-vision.env)
177+
SITL_PARAM_PROFILE="px4-vision"
178178
```
179179

180-
Profiles inject `PX4_PARAM_*` variables into the isaac-sim container; PX4 applies them at boot via `init.d-posix/rcS`. Pair `vision` with robot-side `LAUNCH_NATNET=true` and `publish_to_mavros: true` in `natnet_config.yaml`.
180+
Bundles inject `PX4_PARAM_*` variables into the isaac-sim container; PX4 applies them at boot via `init.d-posix/rcS`. Pair `px4-vision` with robot-side `LAUNCH_NATNET=true` and `publish_to_mavros: true` in `natnet_config.yaml`.
181181

182182
Convenience bundle: `airstack up --env-file overrides/isaac-natnet-vision.env`.
183183

overrides/isaac-natnet-vision.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
# Usage: airstack up --env-file overrides/isaac-natnet-vision.env
33

44
LAUNCH_NATNET=true
5-
PX4_PARAM_PROFILE=vision
5+
SITL_PARAM_PROFILE=px4-vision
66
ISAAC_SIM_USE_STANDALONE=true
77
ISAAC_SIM_SCRIPT_NAME=example_multi_px4_pegasus_launch_script.py

0 commit comments

Comments
 (0)