> ## 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.

# Fault Injection

> Drop messages, delay topics, kill nodes, and corrupt payloads under a context manager. Test E-stops, comms loss, and sensor failures the way an auditor expects.

# Fault injection

Functional tests prove the robot works. **Fault tests prove it fails safely.** Both are required by every safety standard you'll certify against (ISO 13849, IEC 61508, EU MR 2023/1230 Annex III). Roboticks ships four fault-injection primitives that operate over the rclpy DDS layer without modifying the system under test.

<Warning>
  Fault injection runs **between** rclpy and the node — it manipulates the DDS layer for the test process. It does not require root, does not need a custom DDS vendor, and does not patch your node.
</Warning>

## The four primitives

| Primitive                       | What it does                                   | Use for                                                   |
| ------------------------------- | ---------------------------------------------- | --------------------------------------------------------- |
| `drop_messages(topic, rate)`    | Probabilistically drops messages on a topic    | Sensor stalls, lossy WiFi, partial packet loss            |
| `delay_messages(topic, ms)`     | Adds latency to every message on a topic       | Network jitter, slow sensors, perception lag              |
| `kill_node(name)`               | SIGTERMs a node, then re-asserts it stays down | Node crash recovery, watchdog tests                       |
| `corrupt_topic(topic, mutator)` | Mutates message payloads with a callable       | Bit-flip simulation, out-of-range values, malformed input |

All four are **context managers**. The fault is scoped to the `with` block. On exit, the original behaviour is restored, even if the test asserts inside the block.

```python theme={null}
from roboticks.fault_injection import (
    drop_messages, delay_messages, kill_node, corrupt_topic,
)
```

Full signatures in [SDK fault injection reference](/sdk/fault-injection).

## When to use injection vs. mocks

|                             | Mock               | Fault injection                                   |
| --------------------------- | ------------------ | ------------------------------------------------- |
| Speed                       | Fast (μs)          | Real-time (ms-s)                                  |
| Realism                     | Unit-level fiction | The actual DDS round-trip with the fault overlaid |
| Catches integration bugs    | No                 | Yes                                               |
| Catches QoS misconfig       | No                 | Yes                                               |
| Required by safety auditors | Sometimes accepted | **Always preferred**                              |

Rule of thumb: **mock until the node is built; inject once it's running.** Roboticks's evidence pack tags fault-injection tests separately so the auditor sees them without you having to label by hand.

## Example: E-stop survives comms loss

The safety requirement is "E-stop functions even if the supervisor's heartbeat is lost." A passing test asserts the robot enters a safe stop state when `/supervisor/heartbeat` stops arriving.

```python theme={null}
import pytest
from roboticks import confirms, deadline
from roboticks.fault_injection import drop_messages
from roboticks.assertions import assert_topic_published
from std_msgs.msg import Bool

@confirms("REQ-014", "REQ-015")
@deadline(milliseconds=500)
def test_estop_engages_on_heartbeat_loss(ros_context, robot_node):
    with drop_messages("/supervisor/heartbeat", rate=1.0):  # 100% drop
        engaged = assert_topic_published(
            topic="/safety/estop_engaged",
            msg_type=Bool,
            within=0.3,
            predicate=lambda m: m.data is True,
        )
    assert engaged.data
```

## Example: navigation handles a slow LIDAR

```python theme={null}
from roboticks import confirms, requires_sim
from roboticks.fault_injection import delay_messages
from roboticks.assertions import assert_topic_published
from geometry_msgs.msg import Twist

@confirms("REQ-061")
@requires_sim("gazebo")
def test_nav_degrades_gracefully_with_lidar_lag(ros_context, nav_stack):
    with delay_messages("/scan", ms=400):
        msg = assert_topic_published("/cmd_vel", Twist, within=10.0)
        # Robot should slow down, not crash, not stop entirely
        assert 0.0 < msg.linear.x < 0.3
```

## Example: watchdog restarts a dead planner

```python theme={null}
from roboticks import confirms
from roboticks.fault_injection import kill_node
from roboticks.assertions import assert_topic_published
from lifecycle_msgs.msg import TransitionEvent

@confirms("REQ-098")
def test_planner_respawned_by_watchdog(ros_context):
    with kill_node("nav2_planner"):
        # Watchdog should observe the death and republish a transition
        evt = assert_topic_published(
            "/nav2_planner/transition_event",
            TransitionEvent,
            within=5.0,
            predicate=lambda m: m.transition.label == "configure",
        )
        assert evt is not None
```

## Example: sensor corruption

```python theme={null}
from roboticks import confirms
from roboticks.fault_injection import corrupt_topic
from sensor_msgs.msg import LaserScan

def clamp_high(msg: LaserScan) -> LaserScan:
    msg.ranges = [float("inf")] * len(msg.ranges)
    return msg

@confirms("REQ-072")
def test_planner_ignores_inf_only_scans(ros_context, nav_stack):
    with corrupt_topic("/scan", mutator=clamp_high):
        # Planner should not emit cmd_vel when LIDAR is all-infinity
        with pytest.raises(TimeoutError):
            assert_topic_published("/cmd_vel", Twist, within=2.0)
```

## Stacking faults

Context managers nest:

```python theme={null}
with delay_messages("/scan", ms=200), drop_messages("/odom", rate=0.1):
    ...
```

The DDS interceptor multiplexes both faults. Order is irrelevant; the per-topic state is isolated.

## What surfaces in the evidence pack

Fault-injection tests get a dedicated section in the [evidence pack](/evidence/generation) PDF: requirement → fault primitive → topic → result. Auditors quote that section verbatim when defending the safety case.

## Next

<CardGroup cols={2}>
  <Card title="Fault injection reference" icon="book" href="/sdk/fault-injection">
    Full signatures, rate semantics, lifecycle interaction.
  </Card>

  <Card title="MCAP capture" icon="floppy-disk" href="/testing/mcap-capture">
    Pair fault injection with bag-recording for failure forensics.
  </Card>

  <Card title="Launch testing" icon="rocket" href="/testing/launch-testing">
    Inject faults across a multi-node graph.
  </Card>

  <Card title="Standards mapping" icon="scale-balanced" href="/standards/overview">
    Which standards require which kind of fault test.
  </Card>
</CardGroup>
