Skip to content

Commit ca1e7e1

Browse files
committed
Improving simulation.
Signed-off-by: Mike Stitt <[email protected]>
1 parent 4c2379c commit ca1e7e1

File tree

3 files changed

+202
-217
lines changed

3 files changed

+202
-217
lines changed

subprojects/robotpy-wpilib/tests/conftest.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@
55
import wpilib
66
from wpilib.simulation._simulation import _resetWpilibSimulationData
77

8+
from pathlib import Path
9+
10+
import gc
11+
12+
import weakref
13+
14+
import hal
15+
import hal.simulation
16+
import wpilib.shuffleboard
17+
from wpilib.simulation import DriverStationSim, pauseTiming, restartTiming
18+
import wpilib.simulation
19+
from pyfrc.test_support.controller import TestController
20+
from pyfrc.physics.core import PhysicsInterface
21+
22+
try:
23+
import commands2
24+
except ImportError:
25+
commands2 = None
26+
27+
828

929
@pytest.fixture
1030
def cfg_logging(caplog):
@@ -29,3 +49,127 @@ def nt(cfg_logging, wpilib_state):
2949
finally:
3050
instance.stopLocal()
3151
instance._reset()
52+
53+
@pytest.fixture(scope="class", autouse=True)
54+
def physics_and_decorated_robot_class(myrobot_class, robots_sim_enable_physics)->tuple:
55+
# attach physics
56+
57+
robotClass = myrobot_class
58+
physicsInterface = None
59+
if robots_sim_enable_physics:
60+
physicsInterface, robotClass = PhysicsInterface._create_and_attach(
61+
myrobot_class,
62+
Path(__file__).parent,
63+
)
64+
65+
if physicsInterface:
66+
physicsInterface.log_init_errors = False
67+
68+
# Tests need to know when robotInit is called, so override the robot
69+
# to do that
70+
class TestRobot(robotClass):
71+
def robotInit(self):
72+
try:
73+
super().robotInit()
74+
finally:
75+
self.__robotInitialized()
76+
77+
78+
TestRobot.__name__ = robotClass.__name__
79+
TestRobot.__module__ = robotClass.__module__
80+
TestRobot.__qualname__ = robotClass.__qualname__
81+
82+
return (physicsInterface, TestRobot)
83+
84+
@pytest.fixture(scope="function", autouse=True)
85+
def robot_with_sim_setup_teardown(physics_and_decorated_robot_class):
86+
"""
87+
Your robot instance
88+
89+
.. note:: RobotPy/WPILib testing infrastructure is really sensitive
90+
to ensuring that things get cleaned up properly. Make sure
91+
that you don't store references to your robot or other
92+
WPILib objects in a global or static context.
93+
"""
94+
95+
#
96+
# This function needs to do the same things that RobotBase.main does
97+
# plus some extra things needed for testing
98+
#
99+
# Previously this was separate from robot fixture, but we need to
100+
# ensure that the robot cleanup happens deterministically relative to
101+
# when handle cleanup/etc happens, otherwise unnecessary HAL errors will
102+
# bubble up to the user
103+
#
104+
105+
nt_inst = ntcore.NetworkTableInstance.getDefault()
106+
nt_inst.startLocal()
107+
108+
pauseTiming()
109+
restartTiming()
110+
111+
wpilib.DriverStation.silenceJoystickConnectionWarning(True)
112+
DriverStationSim.setAutonomous(False)
113+
DriverStationSim.setEnabled(False)
114+
DriverStationSim.notifyNewData()
115+
116+
robot = physics_and_decorated_robot_class[1]()
117+
118+
# Tests only get a proxy to ensure cleanup is more reliable
119+
yield weakref.proxy(robot)
120+
121+
# If running in separate processes, no need to do cleanup
122+
#if ISOLATED:
123+
# # .. and funny enough, in isolated mode we *don't* want the
124+
# # robot to be cleaned up, as that can deadlock
125+
# self._saved_robot = robot
126+
# return
127+
128+
# reset engine to ensure it gets cleaned up too
129+
# -> might be holding wpilib objects, or the robot
130+
if physics_and_decorated_robot_class[0]:
131+
physics_and_decorated_robot_class[0].engine = None
132+
133+
# HACK: avoid motor safety deadlock
134+
wpilib.simulation._simulation._resetMotorSafety()
135+
136+
del robot
137+
138+
if commands2 is not None:
139+
commands2.CommandScheduler.resetInstance()
140+
141+
# Double-check all objects are destroyed so that HAL handles are released
142+
gc.collect()
143+
144+
# shutdown networktables before other kinds of global cleanup
145+
# -> some reset functions will re-register listeners, so it's important
146+
# to do this before so that the listeners are active on the current
147+
# NetworkTables instance
148+
nt_inst.stopLocal()
149+
nt_inst._reset()
150+
151+
# Cleanup WPILib globals
152+
# -> preferences, SmartDashboard, Shuffleboard, LiveWindow, MotorSafety
153+
wpilib.simulation._simulation._resetWpilibSimulationData()
154+
wpilib._wpilib._clearSmartDashboardData()
155+
wpilib.shuffleboard._shuffleboard._clearShuffleboardData()
156+
157+
# Cancel all periodic callbacks
158+
hal.simulation.cancelAllSimPeriodicCallbacks()
159+
160+
# Reset the HAL handles
161+
hal.simulation.resetGlobalHandles()
162+
163+
# Reset the HAL data
164+
hal.simulation.resetAllSimData()
165+
166+
# Don't call HAL shutdown! This is only used to cleanup HAL extensions,
167+
# and functions will only be called the first time (unless re-registered)
168+
# hal.shutdown()
169+
170+
@pytest.fixture(scope="function")
171+
def control(reraise, robot_with_sim_setup_teardown: wpilib.RobotBase) -> TestController:
172+
"""
173+
A pytest fixture that provides control over your robot_with_sim_setup_teardown
174+
"""
175+
return TestController(reraise, robot_with_sim_setup_teardown)

0 commit comments

Comments
 (0)