testing.pytest_plugin

Pytest plugin for the raccoon sim test harness.

This module is registered as a pytest11 entry point in the raccoon wheel, so once raccoon is installed in a project’s test environment the fixtures below are available with no conftest boilerplate.

The three fixtures that matter:

  • robot: a fully wired instance of the project’s generated src.hardware.robot.Robot class, backed by the mock HAL.

  • scene: a factory for entering a raccoon.testing.sim.use_scene() context. Automatically resolves scene names against the project’s own scenes/ dir first, then raccoon’s bundled scenes.

  • run_step: a sync callable that awaits step.run_step(robot) with a timeout. Tests stay sync; no pytest-asyncio dependency.

Typical usage:

from raccoon.step.motion.drive_dsl import drive_forward
from raccoon.testing.sim import pose


def test_drives_30cm(robot, scene, run_step):
    scene("empty_table.ftmap", start=(20, 50, 0))
    run_step(drive_forward(cm=30), robot)
    assert pose().x > 45

Attributes

ROBOT_IMPORT_PATH

ROBOT_CLASS_NAME

Functions

project_info(→ testing._project.ProjectInfo)

The discovered raccoon project (root, raw yml data, derived SimRobotConfig).

robot_sim_config(→ testing.sim.SimRobotConfig)

A fresh SimRobotConfig derived from the project's yml.

robot(→ Any)

Instantiate the project's generated Robot class.

scene(→ collections.abc.Callable[Ellipsis, None])

Factory that enters a use_scene context for the current test.

run_step(→ collections.abc.Callable[Ellipsis, Any])

Sync wrapper that awaits step.run_step(robot) with a timeout.

pytest_sessionfinish(→ collections.abc.Generator)

Bypass interpreter shutdown when the mock bundle is loaded.

Module Contents

testing.pytest_plugin.ROBOT_IMPORT_PATH = 'src.hardware.robot'
testing.pytest_plugin.ROBOT_CLASS_NAME = 'Robot'
testing.pytest_plugin.project_info() testing._project.ProjectInfo

The discovered raccoon project (root, raw yml data, derived SimRobotConfig).

Session-scoped because the project layout doesn’t change during a test run. If your tests aren’t inside a raccoon project, requesting this fixture fails with a clear error.

testing.pytest_plugin.robot_sim_config(project_info: testing._project.ProjectInfo) testing.sim.SimRobotConfig

A fresh SimRobotConfig derived from the project’s yml.

Function-scoped so individual tests can mutate it (e.g. add line_sensors) without leaking state across tests. Use this as an override hook when a test needs non-default sim geometry.

testing.pytest_plugin.robot(project_info: testing._project.ProjectInfo) Any

Instantiate the project’s generated Robot class.

Skips with a helpful message if the installed raccoon wheel wasn’t built with the mock driver bundle. Uses a fresh instance per test so any mutable state on the Robot (motion history, calibration caches) doesn’t leak between tests.

testing.pytest_plugin.scene(project_info: testing._project.ProjectInfo, robot_sim_config: testing.sim.SimRobotConfig) collections.abc.Callable[Ellipsis, None]

Factory that enters a use_scene context for the current test.

Call it once per test:

def test_foo(robot, scene, run_step):
    scene("empty_table.ftmap", start=(20, 50, 0))
    run_step(my_step, robot)

The scene is detached automatically at the end of the test. Calling scene twice in one test replaces the previous scene.

testing.pytest_plugin.run_step() collections.abc.Callable[Ellipsis, Any]

Sync wrapper that awaits step.run_step(robot) with a timeout.

Tests stay sync so this plugin doesn’t drag in pytest-asyncio. If you need multiple awaits sharing an event loop, write the test as async def and use pytest-asyncio yourself — run_step is the simple path for the common case.

testing.pytest_plugin.pytest_sessionfinish(session: Any, exitstatus: int) collections.abc.Generator

Bypass interpreter shutdown when the mock bundle is loaded.

The pybind11-bound MockPlatform singleton has a destruction-order race with the motor/IMU wrapper destructors that can segfault at interpreter shutdown. This applies whenever raccoon’s C extension is imported — not only when the robot fixture was used — because the extension initialises global state on import.

Passes the real exitstatus to os._exit so CI still sees failures. Suppress with RACCOON_TESTING_NO_EXIT_SHORTCUT=1 to debug.

Uses wrapper=True, tryfirst=True so that we are the outermost wrapper and yield first — letting all other hooks (especially the terminal reporter which prints the ERRORS and short-summary sections) complete before we call os._exit(). With the old trylast=True non-wrapper approach the terminal reporter’s post-yield summary code never ran because os._exit killed the process before it could.