Skip to content

Commit 4c2379c

Browse files
committed
WIP still sims.
Signed-off-by: Mike Stitt <[email protected]>
1 parent 12c90b6 commit 4c2379c

File tree

1 file changed

+149
-137
lines changed

1 file changed

+149
-137
lines changed

subprojects/robotpy-wpilib/tests/test_poc_timedrobot.py

Lines changed: 149 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,26 @@
1616
from wpilib.timedrobotpy import TimedRobotPy
1717

1818

19-
from pyfrc.tests.basic import test_practice as _test_practice
19+
import gc
20+
21+
import weakref
22+
23+
import hal
24+
import hal.simulation
25+
import ntcore
26+
import wpilib
27+
import wpilib.shuffleboard
28+
from wpilib.simulation import DriverStationSim, pauseTiming, restartTiming
29+
import wpilib.simulation
30+
31+
# TODO: get rid of special-casing.. maybe should register a HAL shutdown hook or something
32+
try:
33+
import commands2
34+
except ImportError:
35+
commands2 = None
36+
37+
from pyfrc.test_support.controller import TestController
38+
from pyfrc.physics.core import PhysicsInterface
2039

2140

2241

@@ -73,188 +92,181 @@ def testInit(self):
7392
def testPeriodic(self):
7493
pass
7594

76-
import gc
77-
import pathlib
7895

79-
from typing import Type
96+
@pytest.fixture(scope="module", autouse=True)
97+
def myrobot_class()->type[MyRobot]:
98+
return MyRobot
8099

81-
import pytest
82-
import weakref
83100

84-
import hal
85-
import hal.simulation
86-
import ntcore
87-
import wpilib
88-
import wpilib.shuffleboard
89-
from wpilib.simulation import DriverStationSim, pauseTiming, restartTiming
90-
import wpilib.simulation
101+
@pytest.fixture(scope="module", autouse=True)
102+
def physics_and_decorated_robot_class(myrobot_class)->tuple:
103+
# attach physics
104+
PHYSICS, ROBOT_CLASS = PhysicsInterface._create_and_attach(
105+
myrobot_class,
106+
Path(__file__).parent,
107+
)
91108

92-
# TODO: get rid of special-casing.. maybe should register a HAL shutdown hook or something
93-
try:
94-
import commands2
95-
except ImportError:
96-
commands2 = None
109+
if PHYSICS:
110+
PHYSICS.log_init_errors = False
97111

98-
from pyfrc.test_support.controller import TestController
99-
from pyfrc.physics.core import PhysicsInterface
112+
# Tests need to know when robotInit is called, so override the robot
113+
# to do that
114+
class TestRobot(ROBOT_CLASS):
115+
def robotInit(self):
116+
try:
117+
super().robotInit()
118+
finally:
119+
self.__robotInitialized()
100120

101-
ROBOT_CLASS = MyRobot
102-
ISOLATED = True
103-
ROBOT_FILE = Path(__file__)
104121

105-
# attach physics
106-
PHYSICS, ROBOT_CLASS = PhysicsInterface._create_and_attach(
107-
ROBOT_CLASS,
108-
ROBOT_FILE.parent,
109-
)
122+
TestRobot.__name__ = ROBOT_CLASS.__name__
123+
TestRobot.__module__ = ROBOT_CLASS.__module__
124+
TestRobot.__qualname__ = ROBOT_CLASS.__qualname__
110125

126+
return (PHYSICS, TestRobot)
111127

112-
# Tests need to know when robotInit is called, so override the robot
113-
# to do that
114-
class TestRobot(ROBOT_CLASS):
115-
def robotInit(self):
116-
try:
117-
super().robotInit()
118-
finally:
119-
self.__robotInitialized()
120128

121129

122-
TestRobot.__name__ = ROBOT_CLASS.__name__
123-
TestRobot.__module__ = ROBOT_CLASS.__module__
124-
TestRobot.__qualname__ = ROBOT_CLASS.__qualname__
125130

126-
ROBOT_CLASS = TestRobot
127131

132+
@pytest.fixture(scope="function", autouse=True)
133+
def robot_with_sim_setup_teardown(physics_and_decorated_robot_class):
134+
"""
135+
Your robot instance
128136
129-
if PHYSICS:
130-
PHYSICS.log_init_errors = False
137+
.. note:: RobotPy/WPILib testing infrastructure is really sensitive
138+
to ensuring that things get cleaned up properly. Make sure
139+
that you don't store references to your robot or other
140+
WPILib objects in a global or static context.
141+
"""
131142

143+
#
144+
# This function needs to do the same things that RobotBase.main does
145+
# plus some extra things needed for testing
146+
#
147+
# Previously this was separate from robot fixture, but we need to
148+
# ensure that the robot cleanup happens deterministically relative to
149+
# when handle cleanup/etc happens, otherwise unnecessary HAL errors will
150+
# bubble up to the user
151+
#
132152

133-
class PyFrcNotPlugin:
153+
nt_inst = ntcore.NetworkTableInstance.getDefault()
154+
nt_inst.startLocal()
134155

135-
@pytest.fixture(scope="function", autouse=True)
136-
def robot(self):
137-
"""
138-
Your robot instance
156+
pauseTiming()
157+
restartTiming()
139158

140-
.. note:: RobotPy/WPILib testing infrastructure is really sensitive
141-
to ensuring that things get cleaned up properly. Make sure
142-
that you don't store references to your robot or other
143-
WPILib objects in a global or static context.
144-
"""
159+
wpilib.DriverStation.silenceJoystickConnectionWarning(True)
160+
DriverStationSim.setAutonomous(False)
161+
DriverStationSim.setEnabled(False)
162+
DriverStationSim.notifyNewData()
145163

146-
#
147-
# This function needs to do the same things that RobotBase.main does
148-
# plus some extra things needed for testing
149-
#
150-
# Previously this was separate from robot fixture, but we need to
151-
# ensure that the robot cleanup happens deterministically relative to
152-
# when handle cleanup/etc happens, otherwise unnecessary HAL errors will
153-
# bubble up to the user
154-
#
164+
robot = physics_and_decorated_robot_class[1]()
155165

156-
nt_inst = ntcore.NetworkTableInstance.getDefault()
157-
nt_inst.startLocal()
166+
# Tests only get a proxy to ensure cleanup is more reliable
167+
yield weakref.proxy(robot)
158168

159-
pauseTiming()
160-
restartTiming()
169+
# If running in separate processes, no need to do cleanup
170+
#if ISOLATED:
171+
# # .. and funny enough, in isolated mode we *don't* want the
172+
# # robot to be cleaned up, as that can deadlock
173+
# self._saved_robot = robot
174+
# return
161175

162-
wpilib.DriverStation.silenceJoystickConnectionWarning(True)
163-
DriverStationSim.setAutonomous(False)
164-
DriverStationSim.setEnabled(False)
165-
DriverStationSim.notifyNewData()
176+
# reset engine to ensure it gets cleaned up too
177+
# -> might be holding wpilib objects, or the robot
178+
if physics_and_decorated_robot_class[0]:
179+
physics_and_decorated_robot_class[0].engine = None
166180

167-
robot = ROBOT_CLASS()
181+
# HACK: avoid motor safety deadlock
182+
wpilib.simulation._simulation._resetMotorSafety()
168183

169-
# Tests only get a proxy to ensure cleanup is more reliable
170-
yield weakref.proxy(robot)
184+
del robot
171185

172-
# If running in separate processes, no need to do cleanup
173-
if ISOLATED:
174-
# .. and funny enough, in isolated mode we *don't* want the
175-
# robot to be cleaned up, as that can deadlock
176-
self._saved_robot = robot
177-
return
186+
if commands2 is not None:
187+
commands2.CommandScheduler.resetInstance()
178188

179-
# reset engine to ensure it gets cleaned up too
180-
# -> might be holding wpilib objects, or the robot
181-
if PHYSICS:
182-
PHYSICS.engine = None
189+
# Double-check all objects are destroyed so that HAL handles are released
190+
gc.collect()
183191

184-
# HACK: avoid motor safety deadlock
185-
wpilib.simulation._simulation._resetMotorSafety()
192+
# shutdown networktables before other kinds of global cleanup
193+
# -> some reset functions will re-register listeners, so it's important
194+
# to do this before so that the listeners are active on the current
195+
# NetworkTables instance
196+
nt_inst.stopLocal()
197+
nt_inst._reset()
186198

187-
del robot
199+
# Cleanup WPILib globals
200+
# -> preferences, SmartDashboard, Shuffleboard, LiveWindow, MotorSafety
201+
wpilib.simulation._simulation._resetWpilibSimulationData()
202+
wpilib._wpilib._clearSmartDashboardData()
203+
wpilib.shuffleboard._shuffleboard._clearShuffleboardData()
188204

189-
if commands2 is not None:
190-
commands2.CommandScheduler.resetInstance()
205+
# Cancel all periodic callbacks
206+
hal.simulation.cancelAllSimPeriodicCallbacks()
191207

192-
# Double-check all objects are destroyed so that HAL handles are released
193-
gc.collect()
208+
# Reset the HAL handles
209+
hal.simulation.resetGlobalHandles()
194210

195-
# shutdown networktables before other kinds of global cleanup
196-
# -> some reset functions will re-register listeners, so it's important
197-
# to do this before so that the listeners are active on the current
198-
# NetworkTables instance
199-
nt_inst.stopLocal()
200-
nt_inst._reset()
211+
# Reset the HAL data
212+
hal.simulation.resetAllSimData()
201213

202-
# Cleanup WPILib globals
203-
# -> preferences, SmartDashboard, Shuffleboard, LiveWindow, MotorSafety
204-
wpilib.simulation._simulation._resetWpilibSimulationData()
205-
wpilib._wpilib._clearSmartDashboardData()
206-
wpilib.shuffleboard._shuffleboard._clearShuffleboardData()
214+
# Don't call HAL shutdown! This is only used to cleanup HAL extensions,
215+
# and functions will only be called the first time (unless re-registered)
216+
# hal.shutdown()
207217

208-
# Cancel all periodic callbacks
209-
hal.simulation.cancelAllSimPeriodicCallbacks()
218+
@pytest.fixture(scope="function")
219+
def control(reraise, robot_with_sim_setup_teardown: wpilib.RobotBase) -> TestController:
220+
"""
221+
A pytest fixture that provides control over your robot_with_sim_setup_teardown
222+
"""
223+
return TestController(reraise, robot_with_sim_setup_teardown)
210224

211-
# Reset the HAL handles
212-
hal.simulation.resetGlobalHandles()
225+
#@pytest.fixture()
226+
#def robot_file() -> pathlib.Path:
227+
# """The absolute filename your robot code is started from"""
228+
# return ROBOT_FILE
213229

214-
# Reset the HAL data
215-
hal.simulation.resetAllSimData()
230+
#@pytest.fixture()
231+
#def robot_path() -> pathlib.Path:
232+
# """The absolute directory that your robot code is located at"""
233+
# return ROBOT_FILE.parent
216234

217-
# Don't call HAL shutdown! This is only used to cleanup HAL extensions,
218-
# and functions will only be called the first time (unless re-registered)
219-
# hal.shutdown()
220235

221-
@pytest.fixture(scope="function")
222-
def control(self, reraise, robot: wpilib.RobotBase) -> TestController:
223-
"""
224-
A pytest fixture that provides control over your robot
225-
"""
226-
return TestController(reraise, robot)
236+
def run_practice(control: "TestController"):
237+
"""Runs through the entire span of a practice match"""
227238

228-
@pytest.fixture()
229-
def robot_file(self) -> pathlib.Path:
230-
"""The absolute filename your robot code is started from"""
231-
return ROBOT_FILE
239+
with control.run_robot():
240+
# Run disabled for a short period
241+
control.step_timing(seconds=0.5, autonomous=True, enabled=False)
232242

233-
@pytest.fixture()
234-
def robot_path(self) -> pathlib.Path:
235-
"""The absolute directory that your robot code is located at"""
236-
return ROBOT_FILE.parent
243+
# Run autonomous + enabled for 15 seconds
244+
control.step_timing(seconds=15, autonomous=True, enabled=True)
237245

246+
# Disabled for another short period
247+
control.step_timing(seconds=0.5, autonomous=False, enabled=False)
238248

249+
# Run teleop + enabled for 2 minutes
250+
control.step_timing(seconds=120, autonomous=False, enabled=True)
239251

240-
@pytest.mark.usefixtures("control","robot_file","robot_path")
241-
class TestThings(PyFrcNotPlugin):
252+
@pytest.mark.filterwarnings("ignore")
253+
class TestThings():
242254

243255

244-
def test_iterative(self, control, robot):
256+
def test_iterative(self, control, robot_with_sim_setup_teardown):
245257
"""Ensure that all states of the iterative robot run"""
246-
assert robot.robotInitialized == False
247-
assert robot.robotPeriodicCount == 0
248-
_test_practice(control)
258+
assert robot_with_sim_setup_teardown.robotInitialized == False
259+
assert robot_with_sim_setup_teardown.robotPeriodicCount == 0
260+
run_practice(control)
249261

250-
assert robot.robotInitialized == True
251-
assert robot.robotPeriodicCount > 0
262+
assert robot_with_sim_setup_teardown.robotInitialized == True
263+
assert robot_with_sim_setup_teardown.robotPeriodicCount > 0
252264

253-
def test_iterative_again(self, control, robot):
265+
def test_iterative_again(self, control, robot_with_sim_setup_teardown):
254266
"""Ensure that all states of the iterative robot run"""
255-
assert robot.robotInitialized == False
256-
assert robot.robotPeriodicCount == 0
257-
_test_practice(control)
267+
assert robot_with_sim_setup_teardown.robotInitialized == False
268+
assert robot_with_sim_setup_teardown.robotPeriodicCount == 0
269+
run_practice(control)
258270

259-
assert robot.robotInitialized == True
260-
assert robot.robotPeriodicCount > 0
271+
assert robot_with_sim_setup_teardown.robotInitialized == True
272+
assert robot_with_sim_setup_teardown.robotPeriodicCount > 0

0 commit comments

Comments
 (0)