testing.pytest_plugin ===================== .. py:module:: testing.pytest_plugin .. autoapi-nested-parse:: 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 :func:`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 ---------- .. autoapisummary:: testing.pytest_plugin.ROBOT_IMPORT_PATH testing.pytest_plugin.ROBOT_CLASS_NAME Functions --------- .. autoapisummary:: testing.pytest_plugin.project_info testing.pytest_plugin.robot_sim_config testing.pytest_plugin.robot testing.pytest_plugin.scene testing.pytest_plugin.run_step testing.pytest_plugin.pytest_sessionfinish Module Contents --------------- .. py:data:: ROBOT_IMPORT_PATH :value: 'src.hardware.robot' .. py:data:: ROBOT_CLASS_NAME :value: 'Robot' .. py:function:: 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. .. py:function:: robot_sim_config(project_info: testing._project.ProjectInfo) -> testing.sim.SimRobotConfig A fresh :class:`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. .. py:function:: 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. .. py:function:: scene(project_info: testing._project.ProjectInfo, robot_sim_config: testing.sim.SimRobotConfig) -> 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. .. py:function:: run_step() -> 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. .. py:function:: pytest_sessionfinish(session: Any, exitstatus: int) -> None Bypass interpreter shutdown when the mock HAL was exercised. The pybind11-bound MockPlatform singleton has a destruction-order race with the motor/IMU wrapper destructors that can segfault at interpreter shutdown. The library's own end-to-end tests work around this by running the mission in a subprocess that ``os._exit``s before any destructors run. The plugin does the same thing here, transparently. Only fires when: - The ``robot`` fixture was actually used (so we know the mock HAL has state to tear down). - All tests passed — on failure we want pytest's normal exit path so CI gets an accurate error code and any debugger post-mortem runs. - The user hasn't set ``RACCOON_TESTING_NO_EXIT_SHORTCUT=1`` to debug.