Skip to content

Commit c953938

Browse files
committed
refactor shared test infrastructure
1 parent b1fa276 commit c953938

26 files changed

Lines changed: 1383 additions & 1546 deletions

docs/CONTRIBUTING.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,26 @@ not need to invoke them manually.
5858

5959
```bash
6060
# Python lint
61-
ruff check .
61+
ruff check
6262

6363
# Python formatting
64-
ruff format .
64+
ruff format
6565

6666
# Spelling
67-
codespell --toml pyproject.toml
67+
codespell
6868

6969
# Markdown lint/format
70-
rumdl check .
71-
rumdl fmt .
70+
rumdl check
71+
rumdl fmt
7272

7373
# Run all tests from project root (repo-level + modules)
74-
python -m pytest -v
74+
pytest
7575

7676
# Run project-level tests only
77-
python -m pytest tests/ -v
77+
pytest tests/
7878

7979
# Run a specific module
80-
python -m pytest modules/01-operating-room/tests/ -v
80+
pytest modules/01-operating-room/tests/
8181
```
8282

8383
### On every `git commit` (automatic)
@@ -125,7 +125,7 @@ committing. In exceptional circumstances you can bypass hooks with
125125

126126
| Job | What it does |
127127
| --- | --- |
128-
| Lint & Format | `ruff check .`, `ruff format --check .`, codespell, clang-format dry-run, markdown lint via `rvben/rumdl` action |
128+
| Lint & Format | `ruff check`, `ruff format --check`, codespell, clang-format dry-run, markdown lint via `rvben/rumdl` action |
129129
| Build | CMake configure + build all C++ modules |
130130
| Project-level Tests | `pytest tests/` |
131131
| Unit Tests | Fast Python type/script/QoS tests |
@@ -167,13 +167,13 @@ act push -j test \
167167

168168
```bash
169169
# All tests from project root (repo-level + modules)
170-
python -m pytest -v
170+
pytest -v
171171

172172
# Project-level tests only
173-
python -m pytest tests/ -v
173+
pytest tests/ -v
174174

175175
# Single module
176-
python -m pytest modules/01-operating-room/tests/ -v
176+
pytest modules/01-operating-room/tests/ -v
177177
```
178178

179179
### Option 2 — Docker (closest to CI)
@@ -191,7 +191,7 @@ docker compose -f tests/docker/docker-compose.yml run --rm --build test \
191191
modules/01-operating-room/tests/test_types.py -v
192192
```
193193

194-
> **Note:** The Docker default command runs `python -m pytest -v` via the
194+
> **Note:** The Docker default command runs `pytest -v` via the
195195
> test entrypoint. It executes functional/behavioral tests. It does **not** run Ruff lint,
196196
> rumdl markdown lint, or clang-format; those are enforced by pre-commit
197197
> (locally) and the CI lint job (on push/PR).
@@ -219,8 +219,8 @@ In summary:
219219
Before opening a PR, verify:
220220

221221
- [ ] `pre-commit run --all-files` passes cleanly
222-
- [ ] `python -m pytest tests/` passes locally
223-
- [ ] `python -m pytest -v` passes locally
222+
- [ ] `pytest tests/` passes locally
223+
- [ ] `pytest -v` passes locally
224224
- [ ] `CHANGELOG.md` updated if the change is user-visible
225225
- [ ] No `# noqa` suppressions added without a documented justification
226226

modules/01-operating-room/tests/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,25 @@ From the `modules/01-operating-room/` directory:
2121

2222
```bash
2323
# All tests (requires display for GUI tests)
24-
python -m pytest tests/ -v
24+
pytest tests/ -v
2525

2626
# Skip GUI tests (headless / CI)
27-
python -m pytest tests/ -v -m "not gui"
27+
pytest tests/ -v -m "not gui"
2828

2929
# Skip slow end-to-end tests
30-
python -m pytest tests/ -v -m "not slow"
30+
pytest tests/ -v -m "not slow"
3131

3232
# Only fast, non-GUI tests (best for quick validation)
33-
python -m pytest tests/ -v -m "not gui and not slow"
33+
pytest tests/ -v -m "not gui and not slow"
3434

3535
# Only DDS communication tests
36-
python -m pytest tests/test_dds_communication.py -v
36+
pytest tests/test_dds_communication.py -v
3737

3838
# Only security tests (requires setup_security.py)
39-
python -m pytest tests/ -v -m "secure"
39+
pytest tests/ -v -m "secure"
4040

4141
# Skip security tests
42-
python -m pytest tests/ -v -m "not secure"
42+
pytest tests/ -v -m "not secure"
4343
```
4444

4545
## Test Structure
Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Pytest-only hooks and fixtures for Module 01 tests."""
22

33
import importlib
4-
import os
54
import sys
65
from pathlib import Path
76

@@ -17,11 +16,22 @@
1716
module_runner = importlib.import_module("scripts.module_runner")
1817

1918
MODULE_DIR = module01_test_support.MODULE_DIR
20-
ProcessManager = module01_test_support.ProcessManager
2119
SECURITY_DIR = module01_test_support.SECURITY_DIR
22-
_has_display = module01_test_support._has_display
23-
_security_artifacts_exist = module01_test_support._security_artifacts_exist
24-
_security_plugin_available = module01_test_support._security_plugin_available
20+
21+
from scripts.test_utils import ( # noqa: E402
22+
ProcessManager,
23+
UtilityApp,
24+
has_display,
25+
security_plugin_available,
26+
)
27+
28+
29+
def _security_artifacts_exist() -> bool:
30+
"""Return True when setup_security.py has been run for module 01."""
31+
domain_scope_dir = SECURITY_DIR / "domain_scope"
32+
if not domain_scope_dir.is_dir():
33+
return False
34+
return any(domain_scope_dir.rglob("*.p7s"))
2535

2636

2737
def pytest_collection_modifyitems(config, items):
@@ -36,28 +46,31 @@ def pytest_collection_modifyitems(config, items):
3646
reason="DDS Security runtime probe failed",
3747
)
3848

39-
has_display = _has_display()
49+
_has_display = has_display()
50+
__has_security_plugin = security_plugin_available()
51+
_has_security_artifacts = _security_artifacts_exist()
52+
4053
module_items = [i for i in items if Path(i.fspath).is_relative_to(TESTS_DIR)]
4154

4255
for item in module_items:
43-
if "gui" in item.keywords and not has_display:
56+
if "gui" in item.keywords and not _has_display:
4457
item.add_marker(skip_gui)
45-
if "secure" in item.keywords and not _security_artifacts_exist():
58+
if "secure" in item.keywords and not _has_security_artifacts:
4659
item.add_marker(skip_sec_artifacts)
47-
elif "secure" in item.keywords and not _security_plugin_available():
60+
elif "secure" in item.keywords and not __has_security_plugin:
4861
item.add_marker(skip_sec_plugin)
4962

5063

5164
@pytest.fixture(scope="session")
5265
def dds_env():
53-
"""Session-scoped environment dict with NDDS_QOS_PROFILES configured (non-secure)."""
66+
"""Session-scoped environment for non-secure subprocess launches."""
5467
env, apps = module_runner.load_module_config(MODULE_DIR, flags={"security": False})
5568
return env, apps
5669

5770

5871
@pytest.fixture(scope="session")
5972
def dds_env_secure():
60-
"""Session-scoped environment dict with NDDS_QOS_PROFILES + DDS Security."""
73+
"""Session-scoped environment for secure subprocess launches."""
6174
env, apps = module_runner.load_module_config(MODULE_DIR, flags={"security": True})
6275
env["RTI_SECURITY_ARTIFACTS_DIR"] = str(SECURITY_DIR)
6376
return env, apps
@@ -67,7 +80,7 @@ def dds_env_secure():
6780
def proc_manager(dds_env):
6881
"""Yield a ProcessManager wired to the non-secure DDS environment."""
6982
env, apps = dds_env
70-
pm = ProcessManager(env, apps)
83+
pm = ProcessManager(env, apps, cwd=MODULE_DIR)
7184
yield pm
7285
pm.shutdown_all()
7386

@@ -76,7 +89,7 @@ def proc_manager(dds_env):
7689
def class_proc_manager(dds_env):
7790
"""Class-scoped ProcessManager for read-only test classes."""
7891
env, apps = dds_env
79-
pm = ProcessManager(env, apps)
92+
pm = ProcessManager(env, apps, cwd=MODULE_DIR)
8093
yield pm
8194
pm.shutdown_all()
8295

@@ -85,21 +98,14 @@ def class_proc_manager(dds_env):
8598
def proc_manager_secure(dds_env_secure):
8699
"""Yield a ProcessManager wired to the secure DDS environment."""
87100
env, apps = dds_env_secure
88-
pm = ProcessManager(env, apps)
101+
pm = ProcessManager(env, apps, cwd=MODULE_DIR)
89102
yield pm
90103
pm.shutdown_all()
91104

92105

93106
@pytest.fixture()
94-
def dds_participant(dds_env):
95-
"""Create a lightweight DDS DomainParticipant for test observation."""
96-
import rti.connextdds as dds
97-
98-
env, _apps = dds_env
99-
os.environ["NDDS_QOS_PROFILES"] = env["NDDS_QOS_PROFILES"]
100-
101-
provider = dds.QosProvider.default
102-
participant_qos = provider.participant_qos_from_profile("DpQosLib::Test")
103-
participant = dds.DomainParticipant(domain_id=0, qos=participant_qos)
104-
yield participant
105-
participant.close()
107+
def nonsecure_utility_app():
108+
"""Create a UtilityApp for in-process DDS observation."""
109+
app = UtilityApp.make_non_secure()
110+
yield app
111+
app.close()

0 commit comments

Comments
 (0)