5
5
import wpilib
6
6
from wpilib .simulation ._simulation import _resetWpilibSimulationData
7
7
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
+
8
28
9
29
@pytest .fixture
10
30
def cfg_logging (caplog ):
@@ -29,3 +49,127 @@ def nt(cfg_logging, wpilib_state):
29
49
finally :
30
50
instance .stopLocal ()
31
51
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