Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt

Use this file to discover all available pages before exploring further.

Pytest plugin

The Roboticks pytest plugin is the bridge between in-code @confirms decorators and the JUnit XML the platform ingests. It loads automatically via the pytest11 entry point and writes properties at three lifecycle points: session-start (schema version), per-test setup/teardown (active-test thread-local), and per-test report (decorator metadata + nodeid + attached artifacts).
You almost never touch this plugin directly. It loads on pip install roboticks and does the right thing. This page is for the times you need to debug it or override its behaviour.

How it loads

The plugin is declared as a pytest11 entry point in the SDK’s pyproject.toml:
[project.entry-points.pytest11]
roboticks = "roboticks.pytest_plugin"
When pip install roboticks runs, pytest discovers the entry point on next session-start. No conftest.py change required. Verify it’s loaded:
pytest --trace-config 2>&1 | grep roboticks
# > active plugins: roboticks-0.2.0a0 at .../roboticks/pytest_plugin.py

What it does, in order

Properties emitted

Per session (suite-level):
<property name="roboticks_schema_version" value="2"/>
<property name="roboticks.sdk.version" value="0.2.0a0"/>
<property name="roboticks.python.version" value="3.13.1"/>
Per test (testcase-level), always:
<property name="roboticks.nodeid" value="tests/test_estop.py::test_estop_halts_motion"/>
Per test (testcase-level), only when set by decorators:
<property name="roboticks.confirms" value="REQ-001,REQ-014"/>
<property name="roboticks.tags" value="safety,smoke"/>
<property name="roboticks.deadline_ms" value="100"/>
<property name="roboticks.requires_sim" value="gazebo:gpu"/>
<property name="roboticks.fault_injection" value='[{"primitive":"drop","topic":"/scan","rate":0.3}]'/>
<property name="roboticks.mcap.path" value="captures/test_estop.mcap"/>
Per test (testcase-level), one per file the test registered via attach_artifact():
<property name="roboticks.attach.mcap" value="/tmp/run/test_estop.mcap"/>
<property name="roboticks.attach.attachments" value="/tmp/run/before.png"/>
<property name="roboticks.attach.attachments" value="/tmp/run/after.png"/>
<property name="roboticks.attach.logs" value="/tmp/run/decision_log.jsonl"/>
The runner uploader walks every roboticks.attach.* on the testcase and posts each file to the platform’s per-test-case S3 prefix. See the full wire contract for property semantics and the artifact attachment API.

The kill switch

If the plugin breaks something — a pytest plugin conflict, a CI environment that can’t tolerate the entry-point loading order, an investigation where you want the bare JUnit — disable just the property-writing without uninstalling the SDK:
pytest --no-roboticks-junit-extras tests/
This drops every roboticks.* property write. Decorators still work in-process (they still mutate function attributes); they just don’t surface in the XML. The schema-version stamp is also dropped — so a downstream platform will treat the result as stock JUnit (the wire contract defines the “no schema version” fallback). You can also disable the plugin entirely the normal pytest way:
pytest -p no:roboticks tests/

user_properties

The plugin uses pytest’s standard user_properties mechanism. Each roboticks.* property is a tuple (key, value) appended to request.node.user_properties during pytest_runtest_makereport. pytest’s built-in JUnit reporter writes them out as <property> elements automatically. This means you can read them in your own conftest hook:
# conftest.py
def pytest_runtest_makereport(item, call):
    if call.when == "call":
        props = dict(item.user_properties)
        confirms = props.get("roboticks.confirms", "")
        print(f"{item.nodeid} confirms: {confirms}")

Schema version stamping

The plugin reads its own version from roboticks.__version__ and the schema version from roboticks._version.SCHEMA_VERSION. These are independent integers — the SDK can ship 0.2.0, 0.3.0, 0.4.0 still emitting schema version 2, until a wire change bumps it. To override the schema version at runtime (e.g. for testing a future-version platform):
ROBOTICKS_SCHEMA_VERSION_OVERRIDE=3 pytest tests/
The override only changes the stamp; the actual property shapes are still whatever the running SDK knows how to emit.

Debugging the plugin

Show plugin events

pytest -p no:cacheprovider --tb=short -v tests/ 2>&1 | grep roboticks

Dry-run property emission

The plugin exposes a CLI to render what would be written without running pytest:
python -m roboticks.pytest_plugin --dry-run tests/
Output:
tests/test_estop.py::test_estop_halts_motion
  roboticks.confirms=REQ-001,REQ-014
  roboticks.deadline_ms=100
  roboticks.tags=safety

Force-regenerate JUnit from a re-parse

If you already have a JUnit XML without the plugin’s stamps (because the plugin was disabled, or an old SDK), the stitcher utility back-fills:
roboticks-stitch-junit \
  --in test_results/junit.xml \
  --confirms-from tests/  \
  --out test_results/junit.stitched.xml
The stitcher walks the test files, re-imports each, reads decorator attributes, and writes properties into a copy of the XML. Useful for retro-fitting old CI pipelines.

What the plugin does not do

  • It does not modify pytest’s test selection. @requires_sim is metadata; the platform router uses it, not pytest itself.
  • It does not swallow assertion errors. @deadline raises an AssertionError subclass; pytest reports it normally.
  • It does not auto-load the assertion helpers. Those import only when you import them.
  • It does not require rclpy. The plugin runs on any pytest installation.

Next

Decorators

What the plugin is reading.

Wire contract

What the plugin is writing.

Modules

Where to find the plugin source.

CI recipes

How the plugin’s output gets to the platform.