Source code for pyfrc.test_support.pytest_plugin

import gc
import pathlib

from typing import Type

import pytest
import weakref

import hal
import hal.simulation
import ntcore
import wpilib
import wpilib.shuffleboard
from wpilib.simulation import DriverStationSim, pauseTiming, restartTiming
import wpilib.simulation

# TODO: get rid of special-casing.. maybe should register a HAL shutdown hook or something
try:
    import commands2
except ImportError:
    commands2 = None

from .controller import TestController
from ..physics.core import PhysicsInterface


[docs] class PyFrcPlugin: """ Pytest plugin. Each documented member function name can be an argument to your test functions, and the data that these functions return will be passed to your test function. """ def __init__(self, robot_class: Type[wpilib.RobotBase], robot_file: pathlib.Path): # attach physics physics, robot_class = PhysicsInterface._create_and_attach( robot_class, robot_file.parent, ) # Tests need to know when robotInit is called, so override the robot # to do that class TestRobot(robot_class): def robotInit(self): try: super().robotInit() finally: self.__robotInitialized() TestRobot.__name__ = robot_class.__name__ TestRobot.__module__ = robot_class.__module__ TestRobot.__qualname__ = robot_class.__qualname__ self._robot_file = robot_file self._robot_class = TestRobot self._physics = physics if physics: physics.log_init_errors = False # # Fixtures # # Each one of these can be arguments to your test, and the result of the # corresponding function will be passed to your test as that argument. #
[docs] @pytest.fixture(scope="function", autouse=True) def robot(self): """ Your robot instance .. note:: RobotPy/WPILib testing infrastructure is really sensitive to ensuring that things get cleaned up properly. Make sure that you don't store references to your robot or other WPILib objects in a global or static context. """ # # This function needs to do the same things that RobotBase.main does # plus some extra things needed for testing # # Previously this was separate from robot fixture, but we need to # ensure that the robot cleanup happens deterministically relative to # when handle cleanup/etc happens, otherwise unnecessary HAL errors will # bubble up to the user # nt_inst = ntcore.NetworkTableInstance.getDefault() nt_inst.startLocal() pauseTiming() restartTiming() wpilib.DriverStation.silenceJoystickConnectionWarning(True) DriverStationSim.setAutonomous(False) DriverStationSim.setEnabled(False) DriverStationSim.notifyNewData() robot = self._robot_class() # Tests only get a proxy to ensure cleanup is more reliable yield weakref.proxy(robot) # reset engine to ensure it gets cleaned up too # -> might be holding wpilib objects, or the robot if self._physics: self._physics.engine = None # HACK: avoid motor safety deadlock wpilib.simulation._simulation._resetMotorSafety() del robot # Double-check all objects are destroyed so that HAL handles are released gc.collect() if commands2 is not None: commands2.CommandScheduler.resetInstance() # shutdown networktables before other kinds of global cleanup # -> some reset functions will re-register listeners, so it's important # to do this before so that the listeners are active on the current # NetworkTables instance nt_inst.stopLocal() nt_inst._reset() # Cleanup WPILib globals # -> preferences, SmartDashboard, Shuffleboard, LiveWindow, MotorSafety wpilib.simulation._simulation._resetWpilibSimulationData() wpilib._wpilib._clearSmartDashboardData() wpilib.shuffleboard._shuffleboard._clearShuffleboardData() # Cancel all periodic callbacks hal.simulation.cancelAllSimPeriodicCallbacks() # Reset the HAL handles hal.simulation.resetGlobalHandles() # Reset the HAL data hal.simulation.resetAllSimData()
# Don't call HAL shutdown! This is only used to cleanup HAL extensions, # and functions will only be called the first time (unless re-registered) # hal.shutdown()
[docs] @pytest.fixture(scope="function") def control(self, reraise, robot) -> TestController: """ A pytest fixture that provides control over your robot """ controller = TestController(reraise, robot) yield controller del controller
[docs] @pytest.fixture() def robot_file(self) -> pathlib.Path: """The absolute filename your robot code is started from""" return self._robot_file
[docs] @pytest.fixture() def robot_path(self) -> pathlib.Path: """The absolute directory that your robot code is located at""" return self._robot_file.parent