[robotpy] Port more wpilibc tests to python (#8903)

I tasked Claude with converting the existing C++ tests to python for
wpilib. I gave it a decent review for comparison to the existing tests,
and it seems to have covered everything. I did do some small cleanup in
a couple of places. One notable test that is missing is the LED
patterns, but that is getting handled in
[mostrobotpy](https://github.com/robotpy/mostrobotpy/pull/254) land.
This commit is contained in:
PJ Reiniger
2026-06-21 22:33:50 -04:00
committed by GitHub
parent 45d4ed5bca
commit 6bc7051e23
29 changed files with 1997 additions and 14 deletions

View File

@@ -0,0 +1,435 @@
import pytest
from wpilib import DifferentialDrive
class MockMotorController:
def __init__(self):
self.throttle = 0
def setThrottle(self, throttle):
self.throttle = throttle
def getThrottle(self):
return self.throttle
def test_arcade_drive_ik():
# Forward
v = DifferentialDrive.arcadeDriveIK(1.0, 0.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
# Forward left turn
v = DifferentialDrive.arcadeDriveIK(0.5, 0.5, False)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(0.5)
# Forward right turn
v = DifferentialDrive.arcadeDriveIK(0.5, -0.5, False)
assert v.left == pytest.approx(0.5)
assert v.right == pytest.approx(0.0)
# Backward
v = DifferentialDrive.arcadeDriveIK(-1.0, 0.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
# Backward left turn
v = DifferentialDrive.arcadeDriveIK(-0.5, 0.5, False)
assert v.left == pytest.approx(-0.5)
assert v.right == pytest.approx(0.0)
# Backward right turn
v = DifferentialDrive.arcadeDriveIK(-0.5, -0.5, False)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(-0.5)
# Left turn (negative sign xSpeed)
v = DifferentialDrive.arcadeDriveIK(-0.0, 1.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(1.0)
# Left turn (positive sign xSpeed)
v = DifferentialDrive.arcadeDriveIK(0.0, 1.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(1.0)
# Right turn (negative sign xSpeed)
v = DifferentialDrive.arcadeDriveIK(-0.0, -1.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(-1.0)
# Right turn (positive sign xSpeed)
v = DifferentialDrive.arcadeDriveIK(0.0, -1.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(-1.0)
def test_arcade_drive_ik_squared():
# Forward
v = DifferentialDrive.arcadeDriveIK(1.0, 0.0, True)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
# Forward left turn
v = DifferentialDrive.arcadeDriveIK(0.5, 0.5, True)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(0.25)
# Forward right turn
v = DifferentialDrive.arcadeDriveIK(0.5, -0.5, True)
assert v.left == pytest.approx(0.25)
assert v.right == pytest.approx(0.0)
# Backward
v = DifferentialDrive.arcadeDriveIK(-1.0, 0.0, True)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
# Backward left turn
v = DifferentialDrive.arcadeDriveIK(-0.5, 0.5, True)
assert v.left == pytest.approx(-0.25)
assert v.right == pytest.approx(0.0)
# Backward right turn
v = DifferentialDrive.arcadeDriveIK(-0.5, -0.5, True)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(-0.25)
# Rotation-only cases same as unsquared
v = DifferentialDrive.arcadeDriveIK(-0.0, 1.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.arcadeDriveIK(0.0, 1.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.arcadeDriveIK(-0.0, -1.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(-1.0)
v = DifferentialDrive.arcadeDriveIK(0.0, -1.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(-1.0)
def test_curvature_drive_ik():
# Forward
v = DifferentialDrive.curvatureDriveIK(1.0, 0.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
# Forward left turn
v = DifferentialDrive.curvatureDriveIK(0.5, 0.5, False)
assert v.left == pytest.approx(0.25)
assert v.right == pytest.approx(0.75)
# Forward right turn
v = DifferentialDrive.curvatureDriveIK(0.5, -0.5, False)
assert v.left == pytest.approx(0.75)
assert v.right == pytest.approx(0.25)
# Backward
v = DifferentialDrive.curvatureDriveIK(-1.0, 0.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
# Backward left turn
v = DifferentialDrive.curvatureDriveIK(-0.5, 0.5, False)
assert v.left == pytest.approx(-0.75)
assert v.right == pytest.approx(-0.25)
# Backward right turn
v = DifferentialDrive.curvatureDriveIK(-0.5, -0.5, False)
assert v.left == pytest.approx(-0.25)
assert v.right == pytest.approx(-0.75)
def test_curvature_drive_ik_turn_in_place():
# Forward
v = DifferentialDrive.curvatureDriveIK(1.0, 0.0, True)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
# Forward left turn
v = DifferentialDrive.curvatureDriveIK(0.5, 0.5, True)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(1.0)
# Forward right turn
v = DifferentialDrive.curvatureDriveIK(0.5, -0.5, True)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(0.0)
# Backward
v = DifferentialDrive.curvatureDriveIK(-1.0, 0.0, True)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
# Backward left turn
v = DifferentialDrive.curvatureDriveIK(-0.5, 0.5, True)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(0.0)
# Backward right turn
v = DifferentialDrive.curvatureDriveIK(-0.5, -0.5, True)
assert v.left == pytest.approx(0.0)
assert v.right == pytest.approx(-1.0)
def test_tank_drive_ik():
v = DifferentialDrive.tankDriveIK(1.0, 1.0, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.tankDriveIK(0.5, 1.0, False)
assert v.left == pytest.approx(0.5)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.tankDriveIK(1.0, 0.5, False)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(0.5)
v = DifferentialDrive.tankDriveIK(-1.0, -1.0, False)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
v = DifferentialDrive.tankDriveIK(-0.5, -1.0, False)
assert v.left == pytest.approx(-0.5)
assert v.right == pytest.approx(-1.0)
v = DifferentialDrive.tankDriveIK(-0.5, 1.0, False)
assert v.left == pytest.approx(-0.5)
assert v.right == pytest.approx(1.0)
def test_tank_drive_ik_squared():
v = DifferentialDrive.tankDriveIK(1.0, 1.0, True)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.tankDriveIK(0.5, 1.0, True)
assert v.left == pytest.approx(0.25)
assert v.right == pytest.approx(1.0)
v = DifferentialDrive.tankDriveIK(1.0, 0.5, True)
assert v.left == pytest.approx(1.0)
assert v.right == pytest.approx(0.25)
v = DifferentialDrive.tankDriveIK(-1.0, -1.0, True)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-1.0)
v = DifferentialDrive.tankDriveIK(-0.5, -1.0, True)
assert v.left == pytest.approx(-0.25)
assert v.right == pytest.approx(-1.0)
v = DifferentialDrive.tankDriveIK(-1.0, -0.5, True)
assert v.left == pytest.approx(-1.0)
assert v.right == pytest.approx(-0.25)
def test_arcade_drive(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.arcadeDrive(1.0, 0.0, False)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.arcadeDrive(0.5, 0.5, False)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(0.5)
drive.arcadeDrive(0.5, -0.5, False)
assert left.getThrottle() == pytest.approx(0.5)
assert right.getThrottle() == pytest.approx(0.0)
drive.arcadeDrive(-1.0, 0.0, False)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.arcadeDrive(-0.5, 0.5, False)
assert left.getThrottle() == pytest.approx(-0.5)
assert right.getThrottle() == pytest.approx(0.0)
drive.arcadeDrive(-0.5, -0.5, False)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(-0.5)
def test_arcade_drive_squared(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.arcadeDrive(1.0, 0.0, True)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.arcadeDrive(0.5, 0.5, True)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(0.25)
drive.arcadeDrive(0.5, -0.5, True)
assert left.getThrottle() == pytest.approx(0.25)
assert right.getThrottle() == pytest.approx(0.0)
drive.arcadeDrive(-1.0, 0.0, True)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.arcadeDrive(-0.5, 0.5, True)
assert left.getThrottle() == pytest.approx(-0.25)
assert right.getThrottle() == pytest.approx(0.0)
drive.arcadeDrive(-0.5, -0.5, True)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(-0.25)
def test_curvature_drive(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.curvatureDrive(1.0, 0.0, False)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.curvatureDrive(0.5, 0.5, False)
assert left.getThrottle() == pytest.approx(0.25)
assert right.getThrottle() == pytest.approx(0.75)
drive.curvatureDrive(0.5, -0.5, False)
assert left.getThrottle() == pytest.approx(0.75)
assert right.getThrottle() == pytest.approx(0.25)
drive.curvatureDrive(-1.0, 0.0, False)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.curvatureDrive(-0.5, 0.5, False)
assert left.getThrottle() == pytest.approx(-0.75)
assert right.getThrottle() == pytest.approx(-0.25)
drive.curvatureDrive(-0.5, -0.5, False)
assert left.getThrottle() == pytest.approx(-0.25)
assert right.getThrottle() == pytest.approx(-0.75)
def test_curvature_drive_turn_in_place(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.curvatureDrive(1.0, 0.0, True)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.curvatureDrive(0.5, 0.5, True)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.curvatureDrive(0.5, -0.5, True)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(0.0)
drive.curvatureDrive(-1.0, 0.0, True)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.curvatureDrive(-0.5, 0.5, True)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(0.0)
drive.curvatureDrive(-0.5, -0.5, True)
assert left.getThrottle() == pytest.approx(0.0)
assert right.getThrottle() == pytest.approx(-1.0)
def test_tank_drive(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.tankDrive(1.0, 1.0, False)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.tankDrive(0.5, 1.0, False)
assert left.getThrottle() == pytest.approx(0.5)
assert right.getThrottle() == pytest.approx(1.0)
drive.tankDrive(1.0, 0.5, False)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(0.5)
drive.tankDrive(-1.0, -1.0, False)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.tankDrive(-0.5, -1.0, False)
assert left.getThrottle() == pytest.approx(-0.5)
assert right.getThrottle() == pytest.approx(-1.0)
drive.tankDrive(-0.5, 1.0, False)
assert left.getThrottle() == pytest.approx(-0.5)
assert right.getThrottle() == pytest.approx(1.0)
def test_tank_drive_squared(wpilib_state):
left = MockMotorController()
right = MockMotorController()
drive = DifferentialDrive(
left.setThrottle,
right.setThrottle,
)
drive.setDeadband(0.0)
drive.tankDrive(1.0, 1.0, True)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(1.0)
drive.tankDrive(0.5, 1.0, True)
assert left.getThrottle() == pytest.approx(0.25)
assert right.getThrottle() == pytest.approx(1.0)
drive.tankDrive(1.0, 0.5, True)
assert left.getThrottle() == pytest.approx(1.0)
assert right.getThrottle() == pytest.approx(0.25)
drive.tankDrive(-1.0, -1.0, True)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-1.0)
drive.tankDrive(-0.5, -1.0, True)
assert left.getThrottle() == pytest.approx(-0.25)
assert right.getThrottle() == pytest.approx(-1.0)
drive.tankDrive(-1.0, -0.5, True)
assert left.getThrottle() == pytest.approx(-1.0)
assert right.getThrottle() == pytest.approx(-0.25)

View File

@@ -0,0 +1,242 @@
import math
import pytest
from wpimath import Rotation2d
from wpilib import MecanumDrive
class MockMotorController:
def __init__(self):
self.throttle = 0
def setThrottle(self, throttle):
self.throttle = throttle
def getThrottle(self):
return self.throttle
def test_cartesian_ik():
# Forward
v = MecanumDrive.driveCartesianIK(1.0, 0.0, 0.0)
assert v.frontLeft == pytest.approx(1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(1.0)
# Left
v = MecanumDrive.driveCartesianIK(0.0, -1.0, 0.0)
assert v.frontLeft == pytest.approx(-1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(-1.0)
# Right
v = MecanumDrive.driveCartesianIK(0.0, 1.0, 0.0)
assert v.frontLeft == pytest.approx(1.0)
assert v.frontRight == pytest.approx(-1.0)
assert v.rearLeft == pytest.approx(-1.0)
assert v.rearRight == pytest.approx(1.0)
# Rotate CCW
v = MecanumDrive.driveCartesianIK(0.0, 0.0, -1.0)
assert v.frontLeft == pytest.approx(-1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(-1.0)
assert v.rearRight == pytest.approx(1.0)
# Rotate CW
v = MecanumDrive.driveCartesianIK(0.0, 0.0, 1.0)
assert v.frontLeft == pytest.approx(1.0)
assert v.frontRight == pytest.approx(-1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(-1.0)
def test_cartesian_ik_gyro_90_cw():
gyro = Rotation2d.fromDegrees(90)
# Forward in global frame; left in robot frame
v = MecanumDrive.driveCartesianIK(1.0, 0.0, 0.0, gyro)
assert v.frontLeft == pytest.approx(-1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(-1.0)
# Left in global frame; backward in robot frame
v = MecanumDrive.driveCartesianIK(0.0, -1.0, 0.0, gyro)
assert v.frontLeft == pytest.approx(-1.0)
assert v.frontRight == pytest.approx(-1.0)
assert v.rearLeft == pytest.approx(-1.0)
assert v.rearRight == pytest.approx(-1.0)
# Right in global frame; forward in robot frame
v = MecanumDrive.driveCartesianIK(0.0, 1.0, 0.0, gyro)
assert v.frontLeft == pytest.approx(1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(1.0)
# Rotate CCW
v = MecanumDrive.driveCartesianIK(0.0, 0.0, -1.0, gyro)
assert v.frontLeft == pytest.approx(-1.0)
assert v.frontRight == pytest.approx(1.0)
assert v.rearLeft == pytest.approx(-1.0)
assert v.rearRight == pytest.approx(1.0)
# Rotate CW
v = MecanumDrive.driveCartesianIK(0.0, 0.0, 1.0, gyro)
assert v.frontLeft == pytest.approx(1.0)
assert v.frontRight == pytest.approx(-1.0)
assert v.rearLeft == pytest.approx(1.0)
assert v.rearRight == pytest.approx(-1.0)
def test_cartesian(wpilib_state):
fl = MockMotorController()
rl = MockMotorController()
fr = MockMotorController()
rr = MockMotorController()
drive = MecanumDrive(
fl.setThrottle,
rl.setThrottle,
fr.setThrottle,
rr.setThrottle,
)
drive.setDeadband(0.0)
# Forward
drive.driveCartesian(1.0, 0.0, 0.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Left
drive.driveCartesian(0.0, -1.0, 0.0)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
# Right
drive.driveCartesian(0.0, 1.0, 0.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CCW
drive.driveCartesian(0.0, 0.0, -1.0)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CW
drive.driveCartesian(0.0, 0.0, 1.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
def test_cartesian_gyro_90_cw(wpilib_state):
fl = MockMotorController()
rl = MockMotorController()
fr = MockMotorController()
rr = MockMotorController()
drive = MecanumDrive(
fl.setThrottle,
rl.setThrottle,
fr.setThrottle,
rr.setThrottle,
)
drive.setDeadband(0.0)
gyro = Rotation2d.fromDegrees(90)
# Forward in global frame; left in robot frame
drive.driveCartesian(1.0, 0.0, 0.0, gyro)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
# Left in global frame; backward in robot frame
drive.driveCartesian(0.0, -1.0, 0.0, gyro)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
# Right in global frame; forward in robot frame
drive.driveCartesian(0.0, 1.0, 0.0, gyro)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CCW
drive.driveCartesian(0.0, 0.0, -1.0, gyro)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CW
drive.driveCartesian(0.0, 0.0, 1.0, gyro)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
def test_polar(wpilib_state):
fl = MockMotorController()
rl = MockMotorController()
fr = MockMotorController()
rr = MockMotorController()
drive = MecanumDrive(
fl.setThrottle,
rl.setThrottle,
fr.setThrottle,
rr.setThrottle,
)
drive.setDeadband(0.0)
# Forward
drive.drivePolar(1.0, Rotation2d.fromDegrees(0), 0.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Left
drive.drivePolar(1.0, Rotation2d.fromDegrees(-90), 0.0)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)
# Right
drive.drivePolar(1.0, Rotation2d.fromDegrees(90), 0.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CCW
drive.drivePolar(0.0, Rotation2d.fromDegrees(0), -1.0)
assert fl.getThrottle() == pytest.approx(-1.0)
assert fr.getThrottle() == pytest.approx(1.0)
assert rl.getThrottle() == pytest.approx(-1.0)
assert rr.getThrottle() == pytest.approx(1.0)
# Rotate CW
drive.drivePolar(0.0, Rotation2d.fromDegrees(0), 1.0)
assert fl.getThrottle() == pytest.approx(1.0)
assert fr.getThrottle() == pytest.approx(-1.0)
assert rl.getThrottle() == pytest.approx(1.0)
assert rr.getThrottle() == pytest.approx(-1.0)

View File

@@ -0,0 +1,44 @@
import pytest
def axis_test(controller_class, sim_class, axis_name):
joy = controller_class(2)
joysim = sim_class(joy)
sim_setter_func = getattr(joysim, "set" + axis_name)
joy_get_func = getattr(joy, "get" + axis_name)
sim_setter_func(0.35)
joysim.notifyNewData()
assert joy_get_func() == pytest.approx(0.35, abs=0.001)
def button_test(controller_class, sim_class, btn_name):
joy = controller_class(1)
joysim = sim_class(joy)
joy_get_func = getattr(joy, "get" + btn_name)
joy_get_pressed_func = getattr(joy, "get" + btn_name + "Pressed")
joy_get_released_func = getattr(joy, "get" + btn_name + "Released")
sim_setter_func = getattr(joysim, "set" + btn_name)
sim_setter_func(False)
joysim.notifyNewData()
assert False == joy_get_func()
# need to call pressed and released to clear flags
joy_get_pressed_func()
joy_get_released_func()
sim_setter_func(True)
joysim.notifyNewData()
assert True == joy_get_func()
assert True == joy_get_pressed_func()
assert False == joy_get_released_func()
sim_setter_func(False)
joysim.notifyNewData()
assert False == joy_get_func()
assert False == joy_get_pressed_func()
assert True == joy_get_released_func()

View File

@@ -0,0 +1,68 @@
import pytest
from wpilib import DriverStationBackend, Joystick
from wpilib.simulation import DriverStationSim, stepTiming
@pytest.mark.parametrize(
"axes_max, buttons_max, povs_max, expected",
[
(0, 0, 0, False),
(1, 0, 0, True),
(0, 1, 0, True),
(0, 0, 1, True),
(1, 1, 1, True),
(4, 10, 1, True),
],
)
def test_is_joystick_connected(wpilib_state, axes_max, buttons_max, povs_max, expected):
DriverStationSim.setJoystickAxesMaximumIndex(1, axes_max)
DriverStationSim.setJoystickButtonsMaximumIndex(1, buttons_max)
DriverStationSim.setJoystickPOVsMaximumIndex(1, povs_max)
DriverStationSim.notifyNewData()
assert DriverStationBackend.isJoystickConnected(1) == expected
@pytest.mark.parametrize(
"fms_attached, silenced, expected_silenced, expected_warning",
[
(False, True, True, ""),
(
False,
False,
False,
"Warning: Joystick on port 0 not available, check if all controllers are plugged in",
),
(
True,
True,
False,
"Warning: Joystick on port 0 not available, check if all controllers are plugged in",
), # FMS overrides silence
(
True,
False,
False,
"Warning: Joystick on port 0 not available, check if all controllers are plugged in",
),
],
)
def test_joystick_connection_warnings(
wpilib_state, fms_attached, silenced, expected_silenced, expected_warning, capsys
):
DriverStationSim.setFmsAttached(fms_attached)
DriverStationSim.notifyNewData()
DriverStationBackend.silenceJoystickConnectionWarning(silenced)
joystick = Joystick(0)
joystick.getRawButton(1)
stepTiming(1.0)
assert (
DriverStationBackend.isJoystickConnectionWarningSilenced() == expected_silenced
)
# Capture stderr to check for warnings
captured = capsys.readouterr()
assert expected_warning in captured.err

View File

@@ -4,6 +4,11 @@ from wpilib import Joystick
from wpilib.simulation import JoystickSim
def test_fast_deconstruction(wpilib_state):
# https://github.com/wpilibsuite/allwpilib/issues/1550
Joystick(0)
def test_getX() -> None:
joy = Joystick(1)
joysim = JoystickSim(joy)

View File

@@ -0,0 +1,37 @@
from wpilib import NiDsPS4Controller
from wpilib.simulation import NiDsPS4ControllerSim
from driverstation.joystick_test_macros import button_test, axis_test
def test_buttons():
def ps4_button_test(btn_name):
button_test(NiDsPS4Controller, NiDsPS4ControllerSim, btn_name)
def ps4_axis_test(axis_name):
axis_test(NiDsPS4Controller, NiDsPS4ControllerSim, axis_name)
ps4_button_test("SquareButton")
ps4_button_test("CrossButton")
ps4_button_test("CircleButton")
ps4_button_test("TriangleButton")
ps4_button_test("L1Button")
ps4_button_test("R1Button")
ps4_button_test("L2Button")
ps4_button_test("R2Button")
ps4_button_test("ShareButton")
ps4_button_test("OptionsButton")
ps4_button_test("L3Button")
ps4_button_test("R3Button")
ps4_button_test("PSButton")
ps4_button_test("TouchpadButton")
ps4_axis_test("LeftX")
ps4_axis_test("RightX")
ps4_axis_test("LeftY")
ps4_axis_test("RightY")
ps4_axis_test("L2Axis")
ps4_axis_test("R2Axis")

View File

@@ -0,0 +1,37 @@
from wpilib import NiDsPS5Controller
from wpilib.simulation import NiDsPS5ControllerSim
from driverstation.joystick_test_macros import button_test, axis_test
def test_buttons():
def ps5_button_test(btn_name):
button_test(NiDsPS5Controller, NiDsPS5ControllerSim, btn_name)
def ps5_axis_test(axis_name):
axis_test(NiDsPS5Controller, NiDsPS5ControllerSim, axis_name)
ps5_button_test("SquareButton")
ps5_button_test("CrossButton")
ps5_button_test("CircleButton")
ps5_button_test("TriangleButton")
ps5_button_test("L1Button")
ps5_button_test("R1Button")
ps5_button_test("L2Button")
ps5_button_test("R2Button")
ps5_button_test("CreateButton")
ps5_button_test("OptionsButton")
ps5_button_test("L3Button")
ps5_button_test("R3Button")
ps5_button_test("PSButton")
ps5_button_test("TouchpadButton")
ps5_axis_test("LeftX")
ps5_axis_test("RightX")
ps5_axis_test("LeftY")
ps5_axis_test("RightY")
ps5_axis_test("L2Axis")
ps5_axis_test("R2Axis")

View File

@@ -0,0 +1,32 @@
from wpilib import NiDsXboxController
from wpilib.simulation import NiDsXboxControllerSim
from driverstation.joystick_test_macros import button_test, axis_test
def test_buttons():
def xbox_button_test(btn_name):
button_test(NiDsXboxController, NiDsXboxControllerSim, btn_name)
def xbox_axis_test(axis_name):
axis_test(NiDsXboxController, NiDsXboxControllerSim, axis_name)
xbox_button_test("LeftBumperButton")
xbox_button_test("RightBumperButton")
xbox_button_test("LeftStickButton")
xbox_button_test("RightStickButton")
xbox_button_test("AButton")
xbox_button_test("BButton")
xbox_button_test("XButton")
xbox_button_test("YButton")
xbox_button_test("BackButton")
xbox_button_test("StartButton")
xbox_axis_test("LeftX")
xbox_axis_test("RightX")
xbox_axis_test("LeftY")
xbox_axis_test("RightY")
xbox_axis_test("LeftTriggerAxis")
xbox_axis_test("RightTriggerAxis")

View File

@@ -0,0 +1,23 @@
import pytest
from wpilib import EventLoop
def test_concurrent_modification():
loop = EventLoop()
def bind_during_poll():
with pytest.raises(RuntimeError):
loop.bind(lambda: None)
loop.bind(bind_during_poll)
loop.poll()
loop.clear()
def clear_during_poll():
with pytest.raises(RuntimeError):
loop.clear()
loop.bind(clear_during_poll)
loop.poll()

View File

@@ -0,0 +1,41 @@
import pytest
from wpilib import EventLoop, NetworkBooleanEvent
@pytest.fixture
def nt_inst(wpilib_state):
import ntcore
inst = ntcore.NetworkTableInstance.getDefault()
inst.startLocal()
try:
yield inst
finally:
inst.stopLocal()
inst._reset()
def test_set(nt_inst):
loop = EventLoop()
counter = 0
def on_high() -> None:
nonlocal counter
counter += 1
pub = nt_inst.getTable("TestTable").getBooleanTopic("Test").publish()
NetworkBooleanEvent(loop, nt_inst, "TestTable", "Test").ifHigh(on_high)
pub.set(False)
loop.poll()
assert counter == 0
pub.set(True)
loop.poll()
assert counter == 1
pub.set(False)
loop.poll()
assert counter == 1

View File

@@ -1,7 +1,6 @@
import pytest
import threading
from wpilib import simulation as wsim
from wpimath.units import seconds
from wpilib.opmoderobot import OpModeRobot
from wpilib import OpMode, RobotState
from hal._wpiHal import RobotMode

View File

@@ -0,0 +1,400 @@
import threading
import pytest
from wpilib import TimedRobot
from wpilib.simulation import (
DriverStationSim,
pauseTiming,
resumeTiming,
setProgramStarted,
stepTiming,
waitForProgramStart,
)
from hal import RobotMode
_PERIOD = 0.02 # 20 ms
@pytest.fixture(autouse=True)
def timed_robot_setup(wpilib_state):
pauseTiming()
setProgramStarted(False)
yield
resumeTiming()
class MockRobot(TimedRobot):
def __init__(self):
super().__init__(_PERIOD)
self.simulation_init_count = 0
self.disabled_init_count = 0
self.autonomous_init_count = 0
self.teleop_init_count = 0
self.utility_init_count = 0
self.disabled_exit_count = 0
self.autonomous_exit_count = 0
self.teleop_exit_count = 0
self.utility_exit_count = 0
self.robot_periodic_count = 0
self.simulation_periodic_count = 0
self.disabled_periodic_count = 0
self.autonomous_periodic_count = 0
self.teleop_periodic_count = 0
self.utility_periodic_count = 0
def simulationInit(self):
self.simulation_init_count += 1
def disabledInit(self):
self.disabled_init_count += 1
def autonomousInit(self):
self.autonomous_init_count += 1
def teleopInit(self):
self.teleop_init_count += 1
def utilityInit(self):
self.utility_init_count += 1
def robotPeriodic(self):
self.robot_periodic_count += 1
def simulationPeriodic(self):
self.simulation_periodic_count += 1
def disabledPeriodic(self):
self.disabled_periodic_count += 1
def autonomousPeriodic(self):
self.autonomous_periodic_count += 1
def teleopPeriodic(self):
self.teleop_periodic_count += 1
def utilityPeriodic(self):
self.utility_periodic_count += 1
def disabledExit(self):
self.disabled_exit_count += 1
def autonomousExit(self):
self.autonomous_exit_count += 1
def teleopExit(self):
self.teleop_exit_count += 1
def utilityExit(self):
self.utility_exit_count += 1
def test_disabled_mode():
robot = MockRobot()
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
assert robot.simulation_init_count == 1
assert robot.disabled_init_count == 0
assert robot.robot_periodic_count == 0
assert robot.simulation_periodic_count == 0
assert robot.disabled_periodic_count == 0
stepTiming(_PERIOD)
assert robot.simulation_init_count == 1
assert robot.disabled_init_count == 1
assert robot.autonomous_init_count == 0
assert robot.teleop_init_count == 0
assert robot.utility_init_count == 0
assert robot.robot_periodic_count == 1
assert robot.simulation_periodic_count == 1
assert robot.disabled_periodic_count == 1
assert robot.autonomous_periodic_count == 0
assert robot.teleop_periodic_count == 0
assert robot.utility_periodic_count == 0
assert robot.disabled_exit_count == 0
stepTiming(_PERIOD)
assert robot.robot_periodic_count == 2
assert robot.simulation_periodic_count == 2
assert robot.disabled_periodic_count == 2
robot.endCompetition()
robot_thread.join()
def test_autonomous_mode():
robot = MockRobot()
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(True)
DriverStationSim.setRobotMode(RobotMode.AUTONOMOUS)
DriverStationSim.notifyNewData()
assert robot.simulation_init_count == 1
assert robot.disabled_init_count == 0
assert robot.autonomous_init_count == 0
assert robot.robot_periodic_count == 0
stepTiming(_PERIOD)
assert robot.simulation_init_count == 1
assert robot.disabled_init_count == 0
assert robot.autonomous_init_count == 1
assert robot.teleop_init_count == 0
assert robot.utility_init_count == 0
assert robot.robot_periodic_count == 1
assert robot.simulation_periodic_count == 1
assert robot.disabled_periodic_count == 0
assert robot.autonomous_periodic_count == 1
assert robot.teleop_periodic_count == 0
assert robot.utility_periodic_count == 0
assert robot.autonomous_exit_count == 0
stepTiming(_PERIOD)
assert robot.robot_periodic_count == 2
assert robot.simulation_periodic_count == 2
assert robot.autonomous_periodic_count == 2
robot.endCompetition()
robot_thread.join()
def test_teleop_mode():
robot = MockRobot()
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(True)
DriverStationSim.setRobotMode(RobotMode.TELEOPERATED)
DriverStationSim.notifyNewData()
assert robot.simulation_init_count == 1
assert robot.teleop_init_count == 0
assert robot.robot_periodic_count == 0
stepTiming(_PERIOD)
assert robot.disabled_init_count == 0
assert robot.autonomous_init_count == 0
assert robot.teleop_init_count == 1
assert robot.utility_init_count == 0
assert robot.robot_periodic_count == 1
assert robot.simulation_periodic_count == 1
assert robot.disabled_periodic_count == 0
assert robot.autonomous_periodic_count == 0
assert robot.teleop_periodic_count == 1
assert robot.utility_periodic_count == 0
stepTiming(_PERIOD)
assert robot.robot_periodic_count == 2
assert robot.teleop_periodic_count == 2
robot.endCompetition()
robot_thread.join()
def test_utility_mode():
robot = MockRobot()
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(True)
DriverStationSim.setRobotMode(RobotMode.UTILITY)
DriverStationSim.notifyNewData()
assert robot.simulation_init_count == 1
assert robot.utility_init_count == 0
assert robot.robot_periodic_count == 0
stepTiming(_PERIOD)
assert robot.utility_init_count == 1
assert robot.robot_periodic_count == 1
assert robot.utility_periodic_count == 1
stepTiming(_PERIOD)
assert robot.robot_periodic_count == 2
assert robot.utility_periodic_count == 2
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
stepTiming(_PERIOD)
assert robot.disabled_init_count == 1
assert robot.robot_periodic_count == 3
assert robot.disabled_periodic_count == 1
assert robot.utility_exit_count == 1
robot.endCompetition()
robot_thread.join()
def test_mode_change():
robot = MockRobot()
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
assert robot.disabled_init_count == 0
assert robot.disabled_exit_count == 0
stepTiming(_PERIOD)
assert robot.disabled_init_count == 1
assert robot.disabled_exit_count == 0
DriverStationSim.setEnabled(True)
DriverStationSim.setRobotMode(RobotMode.AUTONOMOUS)
DriverStationSim.notifyNewData()
stepTiming(_PERIOD)
assert robot.disabled_init_count == 1
assert robot.autonomous_init_count == 1
assert robot.teleop_init_count == 0
assert robot.utility_init_count == 0
assert robot.disabled_exit_count == 1
assert robot.autonomous_exit_count == 0
DriverStationSim.setRobotMode(RobotMode.TELEOPERATED)
DriverStationSim.notifyNewData()
stepTiming(_PERIOD)
assert robot.autonomous_init_count == 1
assert robot.teleop_init_count == 1
assert robot.utility_init_count == 0
assert robot.autonomous_exit_count == 1
assert robot.teleop_exit_count == 0
DriverStationSim.setRobotMode(RobotMode.UTILITY)
DriverStationSim.notifyNewData()
stepTiming(_PERIOD)
assert robot.utility_init_count == 1
assert robot.teleop_exit_count == 1
assert robot.utility_exit_count == 0
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
stepTiming(_PERIOD)
assert robot.disabled_init_count == 2
assert robot.autonomous_init_count == 1
assert robot.teleop_init_count == 1
assert robot.utility_init_count == 1
assert robot.disabled_exit_count == 1
assert robot.autonomous_exit_count == 1
assert robot.teleop_exit_count == 1
assert robot.utility_exit_count == 1
robot.endCompetition()
robot_thread.join()
def test_add_periodic():
robot = MockRobot()
callback_count = 0
def on_periodic() -> None:
nonlocal callback_count
callback_count += 1
robot.addPeriodic(on_periodic, _PERIOD / 2.0)
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
assert robot.disabled_init_count == 0
assert robot.disabled_periodic_count == 0
assert callback_count == 0
stepTiming(_PERIOD / 2.0)
assert robot.disabled_init_count == 0
assert robot.disabled_periodic_count == 0
assert callback_count == 1
stepTiming(_PERIOD / 2.0)
assert robot.disabled_init_count == 1
assert robot.disabled_periodic_count == 1
assert callback_count == 2
robot.endCompetition()
robot_thread.join()
def test_add_periodic_with_offset():
robot = MockRobot()
callback_count = 0
def on_periodic() -> None:
nonlocal callback_count
callback_count += 1
robot.addPeriodic(on_periodic, _PERIOD / 2.0, _PERIOD / 4.0)
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
assert robot.disabled_init_count == 0
assert robot.disabled_periodic_count == 0
assert callback_count == 0
stepTiming(_PERIOD * 3.0 / 8.0)
assert robot.disabled_init_count == 0
assert robot.disabled_periodic_count == 0
assert callback_count == 0
stepTiming(_PERIOD * 3.0 / 8.0)
assert robot.disabled_init_count == 0
assert robot.disabled_periodic_count == 0
assert callback_count == 1
stepTiming(_PERIOD / 4.0)
assert robot.disabled_init_count == 1
assert robot.disabled_periodic_count == 1
assert callback_count == 1
stepTiming(_PERIOD / 4.0)
assert robot.disabled_init_count == 1
assert robot.disabled_periodic_count == 1
assert callback_count == 2
robot.endCompetition()
robot_thread.join()

View File

@@ -0,0 +1,95 @@
import threading
import pytest
from wpilib import TimesliceRobot
from wpilib.simulation import (
DriverStationSim,
pauseTiming,
resumeTiming,
setProgramStarted,
stepTiming,
waitForProgramStart,
)
@pytest.fixture(autouse=True)
def timeslice_setup(wpilib_state):
pauseTiming()
setProgramStarted(False)
yield
resumeTiming()
class MockRobot(TimesliceRobot):
def __init__(self):
super().__init__(0.002, 0.005)
self.robot_periodic_count = 0
def robotPeriodic(self):
self.robot_periodic_count += 1
def test_schedule():
robot = MockRobot()
callback_count1 = 0
callback_count2 = 0
def callback1() -> None:
nonlocal callback_count1
callback_count1 += 1
def callback2() -> None:
nonlocal callback_count2
callback_count2 += 1
robot.schedule(callback1, 0.0005)
robot.schedule(callback2, 0.001)
robot_thread = threading.Thread(target=robot.startCompetition, daemon=True)
robot_thread.start()
waitForProgramStart()
DriverStationSim.setEnabled(False)
DriverStationSim.notifyNewData()
# First 5 ms: no callbacks fired yet (delayed by one period)
stepTiming(0.005)
assert robot.robot_periodic_count == 0
assert callback_count1 == 0
assert callback_count2 == 0
# Step to 1.5 ms into next period — nothing yet
stepTiming(0.0015)
assert robot.robot_periodic_count == 0
assert callback_count1 == 0
assert callback_count2 == 0
# Step to 2.25 ms — callback1 fires (offset 2 ms, period 0.5 ms)
stepTiming(0.00075)
assert robot.robot_periodic_count == 0
assert callback_count1 == 1
assert callback_count2 == 0
# Step to 2.75 ms — callback2 fires (offset 2.5 ms, period 1 ms)
stepTiming(0.0005)
assert robot.robot_periodic_count == 0
assert callback_count1 == 1
assert callback_count2 == 1
robot.endCompetition()
robot_thread.join()
def test_schedule_overrun():
robot = MockRobot()
robot.schedule(lambda: None, 0.0005)
robot.schedule(lambda: None, 0.001)
# offset = 2 ms + 0.5 ms + 1 ms = 3.5 ms; 3.5 ms + 3 ms = 6.5 ms > 5 ms max
with pytest.raises(RuntimeError):
robot.schedule(lambda: None, 0.003)
robot.endCompetition()

View File

@@ -0,0 +1,115 @@
import pytest
from wpilib import DoubleSolenoid, PneumaticsModuleType, Solenoid
def test_valid_initialization_ctre(wpilib_state):
solenoid = DoubleSolenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2, 3)
solenoid.set(DoubleSolenoid.Value.REVERSE)
assert solenoid.get() == DoubleSolenoid.Value.REVERSE
solenoid.set(DoubleSolenoid.Value.FORWARD)
assert solenoid.get() == DoubleSolenoid.Value.FORWARD
solenoid.set(DoubleSolenoid.Value.OFF)
assert solenoid.get() == DoubleSolenoid.Value.OFF
def test_throw_forward_port_already_initialized_ctre(wpilib_state):
s = Solenoid(0, 5, PneumaticsModuleType.CTRE_PCM, 2)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 5, PneumaticsModuleType.CTRE_PCM, 2, 3)
def test_throw_reverse_port_already_initialized_ctre(wpilib_state):
s = Solenoid(0, 6, PneumaticsModuleType.CTRE_PCM, 3)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 6, PneumaticsModuleType.CTRE_PCM, 2, 3)
def test_throw_both_ports_already_initialized_ctre(wpilib_state):
s0 = Solenoid(0, 6, PneumaticsModuleType.CTRE_PCM, 2)
s1 = Solenoid(0, 6, PneumaticsModuleType.CTRE_PCM, 3)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 6, PneumaticsModuleType.CTRE_PCM, 2, 3)
def test_toggle_ctre(wpilib_state):
solenoid = DoubleSolenoid(0, 4, PneumaticsModuleType.CTRE_PCM, 2, 3)
solenoid.set(DoubleSolenoid.Value.REVERSE)
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.FORWARD
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.REVERSE
solenoid.set(DoubleSolenoid.Value.OFF)
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.OFF
def test_invalid_forward_port_ctre(wpilib_state):
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 0, PneumaticsModuleType.CTRE_PCM, 100, 1)
def test_invalid_reverse_port_ctre(wpilib_state):
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 0, PneumaticsModuleType.CTRE_PCM, 0, 100)
def test_valid_initialization_rev(wpilib_state):
solenoid = DoubleSolenoid(0, 3, PneumaticsModuleType.REV_PH, 2, 3)
solenoid.set(DoubleSolenoid.Value.REVERSE)
assert solenoid.get() == DoubleSolenoid.Value.REVERSE
solenoid.set(DoubleSolenoid.Value.FORWARD)
assert solenoid.get() == DoubleSolenoid.Value.FORWARD
solenoid.set(DoubleSolenoid.Value.OFF)
assert solenoid.get() == DoubleSolenoid.Value.OFF
def test_throw_forward_port_already_initialized_rev(wpilib_state):
s = Solenoid(0, 5, PneumaticsModuleType.REV_PH, 2)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 5, PneumaticsModuleType.REV_PH, 2, 3)
def test_throw_reverse_port_already_initialized_rev(wpilib_state):
s = Solenoid(0, 6, PneumaticsModuleType.REV_PH, 3)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 6, PneumaticsModuleType.REV_PH, 2, 3)
def test_throw_both_ports_already_initialized_rev(wpilib_state):
s0 = Solenoid(0, 6, PneumaticsModuleType.REV_PH, 2)
s1 = Solenoid(0, 6, PneumaticsModuleType.REV_PH, 3)
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 6, PneumaticsModuleType.REV_PH, 2, 3)
def test_toggle_rev(wpilib_state):
solenoid = DoubleSolenoid(0, 4, PneumaticsModuleType.REV_PH, 2, 3)
solenoid.set(DoubleSolenoid.Value.REVERSE)
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.FORWARD
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.REVERSE
solenoid.set(DoubleSolenoid.Value.OFF)
solenoid.toggle()
assert solenoid.get() == DoubleSolenoid.Value.OFF
def test_invalid_forward_port_rev(wpilib_state):
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 0, PneumaticsModuleType.REV_PH, 100, 1)
def test_invalid_reverse_port_rev(wpilib_state):
with pytest.raises(RuntimeError):
DoubleSolenoid(0, 0, PneumaticsModuleType.REV_PH, 0, 100)

View File

@@ -0,0 +1,83 @@
import pytest
from wpilib import DoubleSolenoid, PneumaticsModuleType, Solenoid
def test_valid_initialization_ctre(wpilib_state):
solenoid = Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2)
assert solenoid.getChannel() == 2
solenoid.set(True)
assert solenoid.get()
solenoid.set(False)
assert not solenoid.get()
def test_double_initialization_ctre(wpilib_state):
s = Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2)
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2)
def test_double_initialization_from_double_solenoid_ctre(wpilib_state):
ds = DoubleSolenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2, 3)
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2)
def test_invalid_channel_ctre(wpilib_state):
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 100)
def test_toggle_ctre(wpilib_state):
solenoid = Solenoid(0, 3, PneumaticsModuleType.CTRE_PCM, 2)
solenoid.set(True)
assert solenoid.get()
solenoid.toggle()
assert not solenoid.get()
solenoid.toggle()
assert solenoid.get()
def test_valid_initialization_rev(wpilib_state):
solenoid = Solenoid(0, 3, PneumaticsModuleType.REV_PH, 2)
assert solenoid.getChannel() == 2
solenoid.set(True)
assert solenoid.get()
solenoid.set(False)
assert not solenoid.get()
def test_double_initialization_rev(wpilib_state):
s = Solenoid(0, 3, PneumaticsModuleType.REV_PH, 2)
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.REV_PH, 2)
def test_double_initialization_from_double_solenoid_rev(wpilib_state):
ds = DoubleSolenoid(0, 3, PneumaticsModuleType.REV_PH, 2, 3)
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.REV_PH, 2)
def test_invalid_channel_rev(wpilib_state):
with pytest.raises(RuntimeError):
Solenoid(0, 3, PneumaticsModuleType.REV_PH, 100)
def test_toggle_rev(wpilib_state):
solenoid = Solenoid(0, 3, PneumaticsModuleType.REV_PH, 2)
solenoid.set(True)
assert solenoid.get()
solenoid.toggle()
assert not solenoid.get()
solenoid.toggle()
assert solenoid.get()

View File

@@ -0,0 +1,18 @@
import pytest
from wpilib import SharpIR
from wpilib.simulation import SharpIRSim
def test_sim_devices(wpilib_state):
s = SharpIR.GP2Y0A02YK0F(1)
sim = SharpIRSim(s)
assert s.getRange() == pytest.approx(0.2)
sim.setRange(0.3)
assert s.getRange() == pytest.approx(0.3)
# Clamped to max range of 1.5 m for GP2Y0A02YK0F
sim.setRange(3.0)
assert s.getRange() == pytest.approx(1.5)

View File

@@ -0,0 +1,75 @@
import pytest
from wpilib import AnalogInput, AnalogPotentiometer
from wpilib.simulation import AnalogInputSim, RoboRioSim
def test_initialize_with_analog_input(wpilib_state):
ai = AnalogInput(0)
pot = AnalogPotentiometer(ai)
sim = AnalogInputSim(ai)
RoboRioSim.resetData()
sim.setVoltage(2.8)
assert pot.get() == pytest.approx(2.8 / 3.3)
def test_initialize_with_analog_input_and_scale(wpilib_state):
ai = AnalogInput(0)
pot = AnalogPotentiometer(ai, 270.0)
RoboRioSim.resetData()
sim = AnalogInputSim(ai)
sim.setVoltage(3.3)
assert pot.get() == pytest.approx(270.0)
sim.setVoltage(2.5)
assert pot.get() == pytest.approx((2.5 / 3.3) * 270.0)
sim.setVoltage(0.0)
assert pot.get() == pytest.approx(0.0)
def test_initialize_with_channel(wpilib_state):
pot = AnalogPotentiometer(1)
sim = AnalogInputSim(1)
sim.setVoltage(3.3)
assert pot.get() == pytest.approx(1.0)
def test_initialize_with_channel_and_scale(wpilib_state):
pot = AnalogPotentiometer(1, 180.0)
RoboRioSim.resetData()
sim = AnalogInputSim(1)
sim.setVoltage(3.3)
assert pot.get() == pytest.approx(180.0)
sim.setVoltage(0.0)
assert pot.get() == pytest.approx(0.0)
def test_with_modified_battery_voltage(wpilib_state):
pot = AnalogPotentiometer(1, 180.0, 90.0)
RoboRioSim.resetData()
sim = AnalogInputSim(1)
# Test at 3.3 V
sim.setVoltage(3.3)
assert pot.get() == pytest.approx(270.0)
sim.setVoltage(0.0)
assert pot.get() == pytest.approx(90.0)
# Simulate lower battery voltage
RoboRioSim.setUserVoltage3V3(2.5)
sim.setVoltage(2.5)
assert pot.get() == pytest.approx(270.0)
sim.setVoltage(2.0)
assert pot.get() == pytest.approx(234.0)
sim.setVoltage(0.0)
assert pot.get() == pytest.approx(90.0)

View File

@@ -10,10 +10,9 @@ Level = Alert.Level
@pytest.fixture(scope="function")
def group_name(request):
AlertSim.resetData()
group_name = f"AlertTest_{request.node.name}"
yield group_name
AlertSim.resetData()

View File

@@ -0,0 +1,89 @@
import pytest
from ntcore import NetworkTableInstance
from wpilib import Mechanism2d, MechanismLigament2d, MechanismRoot2d, SmartDashboard
from wpiutil import Color8Bit
def test_create_mechanism():
m = Mechanism2d(100, 100)
r1 = m.getRoot("r1", 10, 10)
l1 = r1.appendLigament("l1", 4, 3)
l2 = l1.appendLigament("l2", 4, 3)
assert l2 is not None
def test_canvas(nt: NetworkTableInstance):
mechanism = Mechanism2d(5, 10)
dims_entry = nt.getEntry("/SmartDashboard/mechanism/dims")
color_entry = nt.getEntry("/SmartDashboard/mechanism/backgroundColor")
SmartDashboard.putData("mechanism", mechanism)
SmartDashboard.updateValues()
dims = dims_entry.getDoubleArray([])
assert dims[0] == pytest.approx(5.0)
assert dims[1] == pytest.approx(10.0)
assert color_entry.getString("") == "#000020"
mechanism.setBackgroundColor(Color8Bit(255, 255, 255))
SmartDashboard.updateValues()
assert color_entry.getString("") == "#FFFFFF"
def test_root(nt: NetworkTableInstance):
mechanism = Mechanism2d(5, 10)
x_entry = nt.getEntry("/SmartDashboard/mechanism/root/x")
y_entry = nt.getEntry("/SmartDashboard/mechanism/root/y")
root = mechanism.getRoot("root", 1, 2)
SmartDashboard.putData("mechanism", mechanism)
SmartDashboard.updateValues()
assert x_entry.getDouble(0.0) == pytest.approx(1.0)
assert y_entry.getDouble(0.0) == pytest.approx(2.0)
root.setPosition(2, 4)
SmartDashboard.updateValues()
assert x_entry.getDouble(0.0) == pytest.approx(2.0)
assert y_entry.getDouble(0.0) == pytest.approx(4.0)
def test_ligament(nt: NetworkTableInstance):
mechanism = Mechanism2d(5, 10)
angle_entry = nt.getEntry("/SmartDashboard/mechanism/root/ligament/angle")
color_entry = nt.getEntry("/SmartDashboard/mechanism/root/ligament/color")
length_entry = nt.getEntry("/SmartDashboard/mechanism/root/ligament/length")
weight_entry = nt.getEntry("/SmartDashboard/mechanism/root/ligament/weight")
root = mechanism.getRoot("root", 1, 2)
ligament = root.appendLigament("ligament", 3, 90, 1, Color8Bit(255, 255, 255))
SmartDashboard.putData("mechanism", mechanism)
assert ligament.getAngle() == pytest.approx(angle_entry.getDouble(0.0))
assert ligament.getColor().hexString() == color_entry.getString("")
assert ligament.getLength() == pytest.approx(length_entry.getDouble(0.0))
assert ligament.getLineWeight() == pytest.approx(weight_entry.getDouble(0.0))
ligament.setAngle(45)
ligament.setColor(Color8Bit(0, 0, 0))
ligament.setLength(2)
ligament.setLineWeight(4)
SmartDashboard.updateValues()
assert ligament.getAngle() == pytest.approx(angle_entry.getDouble(0.0))
assert ligament.getColor().hexString() == color_entry.getString("")
assert ligament.getLength() == pytest.approx(length_entry.getDouble(0.0))
assert ligament.getLineWeight() == pytest.approx(weight_entry.getDouble(0.0))
angle_entry.setDouble(22.5)
color_entry.setString("#FF00FF")
length_entry.setDouble(4.0)
weight_entry.setDouble(6.0)
SmartDashboard.updateValues()
assert ligament.getAngle() == pytest.approx(angle_entry.getDouble(0.0))
assert ligament.getColor().hexString() == color_entry.getString("")
assert ligament.getLength() == pytest.approx(length_entry.getDouble(0.0))
assert ligament.getLineWeight() == pytest.approx(weight_entry.getDouble(0.0))

View File

@@ -0,0 +1,157 @@
import pytest
from wpilib import Watchdog
from wpilib.simulation import pauseTiming, resumeTiming, stepTiming
@pytest.fixture(autouse=True)
def watchdog_setup():
pauseTiming()
yield
resumeTiming()
def test_enable_disable():
counter = 0
def on_expire() -> None:
nonlocal counter
counter += 1
watchdog = Watchdog(0.4, on_expire)
# Run 1: disable before timeout
watchdog.enable()
stepTiming(0.2)
watchdog.disable()
assert counter == 0
# Run 2: step past timeout
counter = 0
watchdog.enable()
stepTiming(0.4)
watchdog.disable()
assert counter == 1
# Run 3: step well past timeout, only triggers once
counter = 0
watchdog.enable()
stepTiming(1.0)
watchdog.disable()
assert counter == 1
def test_reset():
counter = 0
def on_expire() -> None:
nonlocal counter
counter += 1
watchdog = Watchdog(0.4, on_expire)
watchdog.enable()
stepTiming(0.2)
watchdog.reset()
stepTiming(0.2)
watchdog.disable()
assert counter == 0
def test_set_timeout():
counter = 0
def on_expire() -> None:
nonlocal counter
counter += 1
watchdog = Watchdog(1.0, on_expire)
watchdog.enable()
stepTiming(0.2)
watchdog.setTimeout(0.2)
assert watchdog.getTimeout() == pytest.approx(0.2)
assert counter == 0
stepTiming(0.3)
watchdog.disable()
assert counter == 1
def test_is_expired():
watchdog = Watchdog(0.2, lambda: None)
assert not watchdog.isExpired()
watchdog.enable()
assert not watchdog.isExpired()
stepTiming(0.3)
assert watchdog.isExpired()
watchdog.disable()
assert watchdog.isExpired()
watchdog.reset()
assert not watchdog.isExpired()
def test_epochs():
counter = 0
def on_expire() -> None:
nonlocal counter
counter += 1
watchdog = Watchdog(0.4, on_expire)
# Run 1: under timeout with epochs
watchdog.enable()
watchdog.addEpoch("Epoch 1")
stepTiming(0.1)
watchdog.addEpoch("Epoch 2")
stepTiming(0.1)
watchdog.addEpoch("Epoch 3")
watchdog.disable()
assert counter == 0
# Run 2: reset mid-run keeps under timeout
watchdog.enable()
watchdog.addEpoch("Epoch 1")
stepTiming(0.2)
watchdog.reset()
stepTiming(0.2)
watchdog.addEpoch("Epoch 2")
watchdog.disable()
assert counter == 0
def test_multi_watchdog():
counter1 = 0
counter2 = 0
def on_expire1() -> None:
nonlocal counter1
counter1 += 1
def on_expire2() -> None:
nonlocal counter2
counter2 += 1
watchdog1 = Watchdog(0.2, on_expire1)
watchdog2 = Watchdog(0.6, on_expire2)
watchdog2.enable()
stepTiming(0.25)
assert counter1 == 0
assert counter2 == 0
# watchdog1 enabled later but has shorter timeout — expires first
watchdog1.enable()
stepTiming(0.25)
watchdog1.disable()
watchdog2.disable()
assert counter1 == 1
assert counter2 == 0

View File

@@ -1,11 +0,0 @@
from wpilib import Mechanism2d
def test_create_mechanism():
m = Mechanism2d(100, 100)
r1 = m.getRoot("r1", 10, 10)
l1 = r1.appendLigament("l1", 4, 3)
l2 = l1.appendLigament("l2", 4, 3)
assert l2 is not None
# TODO... check that they do something?