Back to Repositories

Testing Fan Controller Temperature Response in OpenPilot

This test suite validates the fan controller functionality in the OpenPilot system, focusing on temperature-based fan speed control and operational modes. The tests ensure proper fan behavior under various conditions including hot temperatures, offroad limits, and wind-up/down scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of the TiciFanController implementation.

Key areas tested include:
  • Hot temperature response during onroad operation
  • Offroad fan speed limitations
  • Fan wear prevention at low temperatures
  • Maximum speed limitations
  • Wind-up speed behavior
Edge cases include temperature extremes and state transitions between onroad/offroad modes.

Implementation Analysis

The testing approach utilizes pytest’s parametrization feature to run identical tests across all controller implementations. Mock objects are employed to patch system calls, enabling isolated testing of the controller logic.

The implementation uses helper methods (wind_up/wind_down) to simulate temperature changes and repeated controller updates, demonstrating both gradual and extreme condition testing patterns.

Technical Details

Testing tools and configuration:
  • pytest framework for test organization
  • pytest-mock for system call mocking
  • Parametrized test cases for multiple controller types
  • Custom helper methods for temperature simulation
  • Assert statements for validation of fan speed responses

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of system calls, comprehensive edge case coverage, and reusable test utilities.

Notable practices:
  • Consistent test structure across all controller types
  • Mock usage for external dependencies
  • Clear test case naming and organization
  • Simulation of real-world usage patterns

commaai/openpilot

system/hardware/tests/test_fan_controller.py

            
import pytest

from openpilot.system.hardware.fan_controller import TiciFanController

ALL_CONTROLLERS = [TiciFanController]

def patched_controller(mocker, controller_class):
  mocker.patch("os.system", new=mocker.Mock())
  return controller_class()

class TestFanController:
  def wind_up(self, controller, ignition=True):
    for _ in range(1000):
      controller.update(100, ignition)

  def wind_down(self, controller, ignition=False):
    for _ in range(1000):
      controller.update(10, ignition)

  @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
  def test_hot_onroad(self, mocker, controller_class):
    controller = patched_controller(mocker, controller_class)
    self.wind_up(controller)
    assert controller.update(100, True) >= 70

  @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
  def test_offroad_limits(self, mocker, controller_class):
    controller = patched_controller(mocker, controller_class)
    self.wind_up(controller)
    assert controller.update(100, False) <= 30

  @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
  def test_no_fan_wear(self, mocker, controller_class):
    controller = patched_controller(mocker, controller_class)
    self.wind_down(controller)
    assert controller.update(10, False) == 0

  @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
  def test_limited(self, mocker, controller_class):
    controller = patched_controller(mocker, controller_class)
    self.wind_up(controller, True)
    assert controller.update(100, True) == 100

  @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS)
  def test_windup_speed(self, mocker, controller_class):
    controller = patched_controller(mocker, controller_class)
    self.wind_down(controller, True)
    for _ in range(10):
      controller.update(90, True)
    assert controller.update(90, True) >= 60