From ba5f0942fa7f0ebd5aad7b60b4d3f9cc8abfbd84 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Tue, 12 May 2026 01:45:03 -0400 Subject: [PATCH 1/8] [robotpy] Port wpilibc tests to python --- .../python/drive/test_differential_drive.py | 398 ++++++++++++++++++ .../test/python/drive/test_mecanum_drive.py | 212 ++++++++++ .../driverstation/test_driver_station.py | 53 +++ .../python/driverstation/test_generic_hid.py | 78 ++++ .../{ => driverstation}/test_joystick.py | 5 + .../driverstation/test_nids_ps4_controller.py | 206 +++++++++ .../driverstation/test_nids_ps5_controller.py | 206 +++++++++ .../test_nids_xbox_controller.py | 166 ++++++++ .../python/{ => event}/test_boolean_event.py | 0 .../src/test/python/event/test_event_loop.py | 23 + .../event/test_network_boolean_event.py | 39 ++ .../{ => framework}/test_opmode_robot.py | 1 - .../test/python/framework/test_timed_robot.py | 394 +++++++++++++++++ .../python/framework/test_timeslice_robot.py | 87 ++++ .../{ => hardware/imu}/test_onboard_imu.py | 0 .../pneumatic/test_double_solenoid.py | 115 +++++ .../hardware/pneumatic/test_solenoid.py | 83 ++++ .../python/hardware/range/test_sharp_ir.py | 18 + .../rotation/test_analog_potentiometer.py | 75 ++++ .../test_alert_sim.py} | 3 +- .../python/smartdashboard/test_mechanism2d.py | 89 ++++ .../test_sendable_chooser.py | 0 .../test_data_log_manager.py} | 0 .../test/python/{ => system}/test_notifier.py | 0 wpilibc/src/test/python/system/test_timer.py | 104 +++++ .../src/test/python/system/test_watchdog.py | 128 ++++++ wpilibc/src/test/python/test_mechanism2d.py | 11 - 27 files changed, 2480 insertions(+), 14 deletions(-) create mode 100644 wpilibc/src/test/python/drive/test_differential_drive.py create mode 100644 wpilibc/src/test/python/drive/test_mecanum_drive.py create mode 100644 wpilibc/src/test/python/driverstation/test_driver_station.py create mode 100644 wpilibc/src/test/python/driverstation/test_generic_hid.py rename wpilibc/src/test/python/{ => driverstation}/test_joystick.py (96%) create mode 100644 wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py create mode 100644 wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py create mode 100644 wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py rename wpilibc/src/test/python/{ => event}/test_boolean_event.py (100%) create mode 100644 wpilibc/src/test/python/event/test_event_loop.py create mode 100644 wpilibc/src/test/python/event/test_network_boolean_event.py rename wpilibc/src/test/python/{ => framework}/test_opmode_robot.py (99%) create mode 100644 wpilibc/src/test/python/framework/test_timed_robot.py create mode 100644 wpilibc/src/test/python/framework/test_timeslice_robot.py rename wpilibc/src/test/python/{ => hardware/imu}/test_onboard_imu.py (100%) create mode 100644 wpilibc/src/test/python/hardware/pneumatic/test_double_solenoid.py create mode 100644 wpilibc/src/test/python/hardware/pneumatic/test_solenoid.py create mode 100644 wpilibc/src/test/python/hardware/range/test_sharp_ir.py create mode 100644 wpilibc/src/test/python/hardware/rotation/test_analog_potentiometer.py rename wpilibc/src/test/python/{test_alert.py => simulation/test_alert_sim.py} (99%) create mode 100644 wpilibc/src/test/python/smartdashboard/test_mechanism2d.py rename wpilibc/src/test/python/{ => smartdashboard}/test_sendable_chooser.py (100%) rename wpilibc/src/test/python/{test_datalogmanager.py => system/test_data_log_manager.py} (100%) rename wpilibc/src/test/python/{ => system}/test_notifier.py (100%) create mode 100644 wpilibc/src/test/python/system/test_timer.py create mode 100644 wpilibc/src/test/python/system/test_watchdog.py delete mode 100644 wpilibc/src/test/python/test_mechanism2d.py diff --git a/wpilibc/src/test/python/drive/test_differential_drive.py b/wpilibc/src/test/python/drive/test_differential_drive.py new file mode 100644 index 00000000000..48d4f482a5e --- /dev/null +++ b/wpilibc/src/test/python/drive/test_differential_drive.py @@ -0,0 +1,398 @@ +import pytest + +from wpilib import DifferentialDrive + + +def _make_drive(): + outputs = {"left": 0.0, "right": 0.0} + drive = DifferentialDrive( + lambda x: outputs.__setitem__("left", x), + lambda x: outputs.__setitem__("right", x), + ) + drive.setDeadband(0.0) + return drive, outputs + + +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): + drive, outputs = _make_drive() + + drive.arcadeDrive(1.0, 0.0, False) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.arcadeDrive(0.5, 0.5, False) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(0.5) + + drive.arcadeDrive(0.5, -0.5, False) + assert outputs["left"] == pytest.approx(0.5) + assert outputs["right"] == pytest.approx(0.0) + + drive.arcadeDrive(-1.0, 0.0, False) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.arcadeDrive(-0.5, 0.5, False) + assert outputs["left"] == pytest.approx(-0.5) + assert outputs["right"] == pytest.approx(0.0) + + drive.arcadeDrive(-0.5, -0.5, False) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(-0.5) + + +def test_arcade_drive_squared(wpilib_state): + drive, outputs = _make_drive() + + drive.arcadeDrive(1.0, 0.0, True) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.arcadeDrive(0.5, 0.5, True) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(0.25) + + drive.arcadeDrive(0.5, -0.5, True) + assert outputs["left"] == pytest.approx(0.25) + assert outputs["right"] == pytest.approx(0.0) + + drive.arcadeDrive(-1.0, 0.0, True) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.arcadeDrive(-0.5, 0.5, True) + assert outputs["left"] == pytest.approx(-0.25) + assert outputs["right"] == pytest.approx(0.0) + + drive.arcadeDrive(-0.5, -0.5, True) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(-0.25) + + +def test_curvature_drive(wpilib_state): + drive, outputs = _make_drive() + + drive.curvatureDrive(1.0, 0.0, False) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.curvatureDrive(0.5, 0.5, False) + assert outputs["left"] == pytest.approx(0.25) + assert outputs["right"] == pytest.approx(0.75) + + drive.curvatureDrive(0.5, -0.5, False) + assert outputs["left"] == pytest.approx(0.75) + assert outputs["right"] == pytest.approx(0.25) + + drive.curvatureDrive(-1.0, 0.0, False) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.curvatureDrive(-0.5, 0.5, False) + assert outputs["left"] == pytest.approx(-0.75) + assert outputs["right"] == pytest.approx(-0.25) + + drive.curvatureDrive(-0.5, -0.5, False) + assert outputs["left"] == pytest.approx(-0.25) + assert outputs["right"] == pytest.approx(-0.75) + + +def test_curvature_drive_turn_in_place(wpilib_state): + drive, outputs = _make_drive() + + drive.curvatureDrive(1.0, 0.0, True) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.curvatureDrive(0.5, 0.5, True) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.curvatureDrive(0.5, -0.5, True) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(0.0) + + drive.curvatureDrive(-1.0, 0.0, True) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.curvatureDrive(-0.5, 0.5, True) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(0.0) + + drive.curvatureDrive(-0.5, -0.5, True) + assert outputs["left"] == pytest.approx(0.0) + assert outputs["right"] == pytest.approx(-1.0) + + +def test_tank_drive(wpilib_state): + drive, outputs = _make_drive() + + drive.tankDrive(1.0, 1.0, False) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.tankDrive(0.5, 1.0, False) + assert outputs["left"] == pytest.approx(0.5) + assert outputs["right"] == pytest.approx(1.0) + + drive.tankDrive(1.0, 0.5, False) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(0.5) + + drive.tankDrive(-1.0, -1.0, False) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.tankDrive(-0.5, -1.0, False) + assert outputs["left"] == pytest.approx(-0.5) + assert outputs["right"] == pytest.approx(-1.0) + + drive.tankDrive(-0.5, 1.0, False) + assert outputs["left"] == pytest.approx(-0.5) + assert outputs["right"] == pytest.approx(1.0) + + +def test_tank_drive_squared(wpilib_state): + drive, outputs = _make_drive() + + drive.tankDrive(1.0, 1.0, True) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(1.0) + + drive.tankDrive(0.5, 1.0, True) + assert outputs["left"] == pytest.approx(0.25) + assert outputs["right"] == pytest.approx(1.0) + + drive.tankDrive(1.0, 0.5, True) + assert outputs["left"] == pytest.approx(1.0) + assert outputs["right"] == pytest.approx(0.25) + + drive.tankDrive(-1.0, -1.0, True) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-1.0) + + drive.tankDrive(-0.5, -1.0, True) + assert outputs["left"] == pytest.approx(-0.25) + assert outputs["right"] == pytest.approx(-1.0) + + drive.tankDrive(-1.0, -0.5, True) + assert outputs["left"] == pytest.approx(-1.0) + assert outputs["right"] == pytest.approx(-0.25) diff --git a/wpilibc/src/test/python/drive/test_mecanum_drive.py b/wpilibc/src/test/python/drive/test_mecanum_drive.py new file mode 100644 index 00000000000..f0261e41a03 --- /dev/null +++ b/wpilibc/src/test/python/drive/test_mecanum_drive.py @@ -0,0 +1,212 @@ +import math + +import pytest +from wpimath import Rotation2d + +from wpilib import MecanumDrive + + +def _make_drive(): + outputs = {"fl": 0.0, "rl": 0.0, "fr": 0.0, "rr": 0.0} + drive = MecanumDrive( + lambda x: outputs.__setitem__("fl", x), + lambda x: outputs.__setitem__("rl", x), + lambda x: outputs.__setitem__("fr", x), + lambda x: outputs.__setitem__("rr", x), + ) + drive.setDeadband(0.0) + return drive, outputs + + +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): + drive, outputs = _make_drive() + + # Forward + drive.driveCartesian(1.0, 0.0, 0.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Left + drive.driveCartesian(0.0, -1.0, 0.0) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + # Right + drive.driveCartesian(0.0, 1.0, 0.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CCW + drive.driveCartesian(0.0, 0.0, -1.0) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CW + drive.driveCartesian(0.0, 0.0, 1.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + +def test_cartesian_gyro_90_cw(wpilib_state): + drive, outputs = _make_drive() + gyro = Rotation2d.fromDegrees(90) + + # Forward in global frame; left in robot frame + drive.driveCartesian(1.0, 0.0, 0.0, gyro) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + # Left in global frame; backward in robot frame + drive.driveCartesian(0.0, -1.0, 0.0, gyro) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + # Right in global frame; forward in robot frame + drive.driveCartesian(0.0, 1.0, 0.0, gyro) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CCW + drive.driveCartesian(0.0, 0.0, -1.0, gyro) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CW + drive.driveCartesian(0.0, 0.0, 1.0, gyro) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + +def test_polar(wpilib_state): + drive, outputs = _make_drive() + + # Forward + drive.drivePolar(1.0, Rotation2d.fromDegrees(0), 0.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Left + drive.drivePolar(1.0, Rotation2d.fromDegrees(-90), 0.0) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) + + # Right + drive.drivePolar(1.0, Rotation2d.fromDegrees(90), 0.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CCW + drive.drivePolar(0.0, Rotation2d.fromDegrees(0), -1.0) + assert outputs["fl"] == pytest.approx(-1.0) + assert outputs["fr"] == pytest.approx(1.0) + assert outputs["rl"] == pytest.approx(-1.0) + assert outputs["rr"] == pytest.approx(1.0) + + # Rotate CW + drive.drivePolar(0.0, Rotation2d.fromDegrees(0), 1.0) + assert outputs["fl"] == pytest.approx(1.0) + assert outputs["fr"] == pytest.approx(-1.0) + assert outputs["rl"] == pytest.approx(1.0) + assert outputs["rr"] == pytest.approx(-1.0) diff --git a/wpilibc/src/test/python/driverstation/test_driver_station.py b/wpilibc/src/test/python/driverstation/test_driver_station.py new file mode 100644 index 00000000000..bff5f9e8e5e --- /dev/null +++ b/wpilibc/src/test/python/driverstation/test_driver_station.py @@ -0,0 +1,53 @@ +import pytest + +from wpilib import DriverStationBackend +from wpilib.simulation import DriverStationSim + + +@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", + [ + (False, True, True), + (False, False, False), + (True, True, False), + (True, False, False), + ], +) +def test_joystick_connection_warnings( + wpilib_state, fms_attached, silenced, expected_silenced +): + from wpilib.simulation import stepTiming + + DriverStationSim.setFmsAttached(fms_attached) + DriverStationSim.notifyNewData() + DriverStationBackend.silenceJoystickConnectionWarning(silenced) + + from wpilib import Joystick + + joystick = Joystick(0) + joystick.getRawButton(1) + + stepTiming(1.0) + assert ( + DriverStationBackend.isJoystickConnectionWarningSilenced() == expected_silenced + ) diff --git a/wpilibc/src/test/python/driverstation/test_generic_hid.py b/wpilibc/src/test/python/driverstation/test_generic_hid.py new file mode 100644 index 00000000000..23f7de6351d --- /dev/null +++ b/wpilibc/src/test/python/driverstation/test_generic_hid.py @@ -0,0 +1,78 @@ +import pytest + +from wpilib import GenericHID +from wpilib.simulation import GenericHIDSim + +RumbleType = GenericHID.RumbleType +_EPSILON = 0.0001 + + +def test_rumble_range(wpilib_state): + hid = GenericHID(0) + sim = GenericHIDSim(0) + + for i in range(101): + value = i / 100.0 + + hid.setRumble(RumbleType.LEFT_RUMBLE, value) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(value, abs=_EPSILON) + + hid.setRumble(RumbleType.RIGHT_RUMBLE, value) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(value, abs=_EPSILON) + + hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, value) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + value, abs=_EPSILON + ) + + hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, value) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + value, abs=_EPSILON + ) + + +def test_rumble_types(wpilib_state): + hid = GenericHID(0) + sim = GenericHIDSim(0) + + # Make sure all are off + hid.setRumble(RumbleType.LEFT_RUMBLE, 0) + hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 0) + hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) + hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + + # Left only + hid.setRumble(RumbleType.LEFT_RUMBLE, 1) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(1, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + hid.setRumble(RumbleType.LEFT_RUMBLE, 0) + + # Right only + hid.setRumble(RumbleType.RIGHT_RUMBLE, 1) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(1, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) + + # Left trigger only + hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 1) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(1, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 0) + + # Right trigger only + hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 1) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(1, abs=_EPSILON) + hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) diff --git a/wpilibc/src/test/python/test_joystick.py b/wpilibc/src/test/python/driverstation/test_joystick.py similarity index 96% rename from wpilibc/src/test/python/test_joystick.py rename to wpilibc/src/test/python/driverstation/test_joystick.py index f58dcbaa6b1..6d7f1a43096 100644 --- a/wpilibc/src/test/python/test_joystick.py +++ b/wpilibc/src/test/python/driverstation/test_joystick.py @@ -4,6 +4,11 @@ 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) diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py new file mode 100644 index 00000000000..592ac217f96 --- /dev/null +++ b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py @@ -0,0 +1,206 @@ +import pytest + +from wpilib import NiDsPS4Controller +from wpilib.simulation import NiDsPS4ControllerSim + + +def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + get_pressed_fn() + get_released_fn() + + set_fn(True) + joysim.notifyNewData() + assert get_fn() + assert get_pressed_fn() + assert not get_released_fn() + + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + assert not get_pressed_fn() + assert get_released_fn() + + +def _axis_test(joy, joysim, set_fn, get_fn): + set_fn(0.35) + joysim.notifyNewData() + assert get_fn() == pytest.approx(0.35, abs=0.001) + + +def test_get_square_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setSquareButton, joy.getSquareButton, + joy.getSquareButtonPressed, joy.getSquareButtonReleased, + ) + + +def test_get_cross_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setCrossButton, joy.getCrossButton, + joy.getCrossButtonPressed, joy.getCrossButtonReleased, + ) + + +def test_get_circle_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setCircleButton, joy.getCircleButton, + joy.getCircleButtonPressed, joy.getCircleButtonReleased, + ) + + +def test_get_triangle_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setTriangleButton, joy.getTriangleButton, + joy.getTriangleButtonPressed, joy.getTriangleButtonReleased, + ) + + +def test_get_l1_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL1Button, joy.getL1Button, + joy.getL1ButtonPressed, joy.getL1ButtonReleased, + ) + + +def test_get_r1_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR1Button, joy.getR1Button, + joy.getR1ButtonPressed, joy.getR1ButtonReleased, + ) + + +def test_get_l2_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL2Button, joy.getL2Button, + joy.getL2ButtonPressed, joy.getL2ButtonReleased, + ) + + +def test_get_r2_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR2Button, joy.getR2Button, + joy.getR2ButtonPressed, joy.getR2ButtonReleased, + ) + + +def test_get_share_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setShareButton, joy.getShareButton, + joy.getShareButtonPressed, joy.getShareButtonReleased, + ) + + +def test_get_options_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setOptionsButton, joy.getOptionsButton, + joy.getOptionsButtonPressed, joy.getOptionsButtonReleased, + ) + + +def test_get_l3_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL3Button, joy.getL3Button, + joy.getL3ButtonPressed, joy.getL3ButtonReleased, + ) + + +def test_get_r3_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR3Button, joy.getR3Button, + joy.getR3ButtonPressed, joy.getR3ButtonReleased, + ) + + +def test_get_ps_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setPSButton, joy.getPSButton, + joy.getPSButtonPressed, joy.getPSButtonReleased, + ) + + +def test_get_touchpad_button(): + joy = NiDsPS4Controller(1) + joysim = NiDsPS4ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setTouchpadButton, joy.getTouchpadButton, + joy.getTouchpadButtonPressed, joy.getTouchpadButtonReleased, + ) + + +def test_get_left_x(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) + + +def test_get_right_x(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) + + +def test_get_left_y(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) + + +def test_get_right_y(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) + + +def test_get_l2_axis(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setL2Axis, joy.getL2Axis) + + +def test_get_r2_axis(): + joy = NiDsPS4Controller(2) + joysim = NiDsPS4ControllerSim(joy) + _axis_test(joy, joysim, joysim.setR2Axis, joy.getR2Axis) diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py new file mode 100644 index 00000000000..0760080075e --- /dev/null +++ b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py @@ -0,0 +1,206 @@ +import pytest + +from wpilib import NiDsPS5Controller +from wpilib.simulation import NiDsPS5ControllerSim + + +def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + get_pressed_fn() + get_released_fn() + + set_fn(True) + joysim.notifyNewData() + assert get_fn() + assert get_pressed_fn() + assert not get_released_fn() + + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + assert not get_pressed_fn() + assert get_released_fn() + + +def _axis_test(joy, joysim, set_fn, get_fn): + set_fn(0.35) + joysim.notifyNewData() + assert get_fn() == pytest.approx(0.35, abs=0.001) + + +def test_get_square_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setSquareButton, joy.getSquareButton, + joy.getSquareButtonPressed, joy.getSquareButtonReleased, + ) + + +def test_get_cross_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setCrossButton, joy.getCrossButton, + joy.getCrossButtonPressed, joy.getCrossButtonReleased, + ) + + +def test_get_circle_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setCircleButton, joy.getCircleButton, + joy.getCircleButtonPressed, joy.getCircleButtonReleased, + ) + + +def test_get_triangle_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setTriangleButton, joy.getTriangleButton, + joy.getTriangleButtonPressed, joy.getTriangleButtonReleased, + ) + + +def test_get_l1_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL1Button, joy.getL1Button, + joy.getL1ButtonPressed, joy.getL1ButtonReleased, + ) + + +def test_get_r1_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR1Button, joy.getR1Button, + joy.getR1ButtonPressed, joy.getR1ButtonReleased, + ) + + +def test_get_l2_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL2Button, joy.getL2Button, + joy.getL2ButtonPressed, joy.getL2ButtonReleased, + ) + + +def test_get_r2_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR2Button, joy.getR2Button, + joy.getR2ButtonPressed, joy.getR2ButtonReleased, + ) + + +def test_get_create_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setCreateButton, joy.getCreateButton, + joy.getCreateButtonPressed, joy.getCreateButtonReleased, + ) + + +def test_get_options_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setOptionsButton, joy.getOptionsButton, + joy.getOptionsButtonPressed, joy.getOptionsButtonReleased, + ) + + +def test_get_l3_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setL3Button, joy.getL3Button, + joy.getL3ButtonPressed, joy.getL3ButtonReleased, + ) + + +def test_get_r3_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setR3Button, joy.getR3Button, + joy.getR3ButtonPressed, joy.getR3ButtonReleased, + ) + + +def test_get_ps_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setPSButton, joy.getPSButton, + joy.getPSButtonPressed, joy.getPSButtonReleased, + ) + + +def test_get_touchpad_button(): + joy = NiDsPS5Controller(1) + joysim = NiDsPS5ControllerSim(joy) + _button_test( + joy, joysim, + joysim.setTouchpadButton, joy.getTouchpadButton, + joy.getTouchpadButtonPressed, joy.getTouchpadButtonReleased, + ) + + +def test_get_left_x(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) + + +def test_get_right_x(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) + + +def test_get_left_y(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) + + +def test_get_right_y(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) + + +def test_get_l2_axis(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setL2Axis, joy.getL2Axis) + + +def test_get_r2_axis(): + joy = NiDsPS5Controller(2) + joysim = NiDsPS5ControllerSim(joy) + _axis_test(joy, joysim, joysim.setR2Axis, joy.getR2Axis) diff --git a/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py new file mode 100644 index 00000000000..8c3bdc55863 --- /dev/null +++ b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py @@ -0,0 +1,166 @@ +import pytest + +from wpilib import NiDsXboxController +from wpilib.simulation import NiDsXboxControllerSim + + +def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + get_pressed_fn() + get_released_fn() + + set_fn(True) + joysim.notifyNewData() + assert get_fn() + assert get_pressed_fn() + assert not get_released_fn() + + set_fn(False) + joysim.notifyNewData() + assert not get_fn() + assert not get_pressed_fn() + assert get_released_fn() + + +def _axis_test(joy, joysim, set_fn, get_fn): + set_fn(0.35) + joysim.notifyNewData() + assert get_fn() == pytest.approx(0.35, abs=0.001) + + +def test_get_left_bumper_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setLeftBumperButton, joy.getLeftBumperButton, + joy.getLeftBumperButtonPressed, joy.getLeftBumperButtonReleased, + ) + + +def test_get_right_bumper_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setRightBumperButton, joy.getRightBumperButton, + joy.getRightBumperButtonPressed, joy.getRightBumperButtonReleased, + ) + + +def test_get_left_stick_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setLeftStickButton, joy.getLeftStickButton, + joy.getLeftStickButtonPressed, joy.getLeftStickButtonReleased, + ) + + +def test_get_right_stick_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setRightStickButton, joy.getRightStickButton, + joy.getRightStickButtonPressed, joy.getRightStickButtonReleased, + ) + + +def test_get_a_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setAButton, joy.getAButton, + joy.getAButtonPressed, joy.getAButtonReleased, + ) + + +def test_get_b_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setBButton, joy.getBButton, + joy.getBButtonPressed, joy.getBButtonReleased, + ) + + +def test_get_x_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setXButton, joy.getXButton, + joy.getXButtonPressed, joy.getXButtonReleased, + ) + + +def test_get_y_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setYButton, joy.getYButton, + joy.getYButtonPressed, joy.getYButtonReleased, + ) + + +def test_get_back_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setBackButton, joy.getBackButton, + joy.getBackButtonPressed, joy.getBackButtonReleased, + ) + + +def test_get_start_button(): + joy = NiDsXboxController(1) + joysim = NiDsXboxControllerSim(joy) + _button_test( + joy, joysim, + joysim.setStartButton, joy.getStartButton, + joy.getStartButtonPressed, joy.getStartButtonReleased, + ) + + +def test_get_left_x(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) + + +def test_get_right_x(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) + + +def test_get_left_y(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) + + +def test_get_right_y(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) + + +def test_get_left_trigger_axis(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setLeftTriggerAxis, joy.getLeftTriggerAxis) + + +def test_get_right_trigger_axis(): + joy = NiDsXboxController(2) + joysim = NiDsXboxControllerSim(joy) + _axis_test(joy, joysim, joysim.setRightTriggerAxis, joy.getRightTriggerAxis) diff --git a/wpilibc/src/test/python/test_boolean_event.py b/wpilibc/src/test/python/event/test_boolean_event.py similarity index 100% rename from wpilibc/src/test/python/test_boolean_event.py rename to wpilibc/src/test/python/event/test_boolean_event.py diff --git a/wpilibc/src/test/python/event/test_event_loop.py b/wpilibc/src/test/python/event/test_event_loop.py new file mode 100644 index 00000000000..cddd4a910c7 --- /dev/null +++ b/wpilibc/src/test/python/event/test_event_loop.py @@ -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() diff --git a/wpilibc/src/test/python/event/test_network_boolean_event.py b/wpilibc/src/test/python/event/test_network_boolean_event.py new file mode 100644 index 00000000000..749f772b83d --- /dev/null +++ b/wpilibc/src/test/python/event/test_network_boolean_event.py @@ -0,0 +1,39 @@ +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] + + pub = nt_inst.getTable("TestTable").getBooleanTopic("Test").publish() + + NetworkBooleanEvent(loop, nt_inst, "TestTable", "Test").ifHigh( + lambda: counter.__setitem__(0, counter[0] + 1) + ) + + pub.set(False) + loop.poll() + assert counter[0] == 0 + + pub.set(True) + loop.poll() + assert counter[0] == 1 + + pub.set(False) + loop.poll() + assert counter[0] == 1 diff --git a/wpilibc/src/test/python/test_opmode_robot.py b/wpilibc/src/test/python/framework/test_opmode_robot.py similarity index 99% rename from wpilibc/src/test/python/test_opmode_robot.py rename to wpilibc/src/test/python/framework/test_opmode_robot.py index 1498ea11612..33bf3f304f6 100644 --- a/wpilibc/src/test/python/test_opmode_robot.py +++ b/wpilibc/src/test/python/framework/test_opmode_robot.py @@ -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 diff --git a/wpilibc/src/test/python/framework/test_timed_robot.py b/wpilibc/src/test/python/framework/test_timed_robot.py new file mode 100644 index 00000000000..68eb52cf869 --- /dev/null +++ b/wpilibc/src/test/python/framework/test_timed_robot.py @@ -0,0 +1,394 @@ +import threading + +import pytest + +from wpilib import TimedRobot +from wpilib.simulation import ( + DriverStationSim, + pauseTiming, + resumeTiming, + setProgramStarted, + stepTiming, + waitForProgramStart, +) +from hal._wpiHal 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] + robot.addPeriodic(lambda: callback_count.__setitem__(0, callback_count[0] + 1), _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] == 0 + + stepTiming(_PERIOD / 2.0) + + assert robot.disabled_init_count == 0 + assert robot.disabled_periodic_count == 0 + assert callback_count[0] == 1 + + stepTiming(_PERIOD / 2.0) + + assert robot.disabled_init_count == 1 + assert robot.disabled_periodic_count == 1 + assert callback_count[0] == 2 + + robot.endCompetition() + robot_thread.join() + + +def test_add_periodic_with_offset(): + robot = MockRobot() + callback_count = [0] + robot.addPeriodic( + lambda: callback_count.__setitem__(0, callback_count[0] + 1), + _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] == 0 + + stepTiming(_PERIOD * 3.0 / 8.0) + + assert robot.disabled_init_count == 0 + assert robot.disabled_periodic_count == 0 + assert callback_count[0] == 0 + + stepTiming(_PERIOD * 3.0 / 8.0) + + assert robot.disabled_init_count == 0 + assert robot.disabled_periodic_count == 0 + assert callback_count[0] == 1 + + stepTiming(_PERIOD / 4.0) + + assert robot.disabled_init_count == 1 + assert robot.disabled_periodic_count == 1 + assert callback_count[0] == 1 + + stepTiming(_PERIOD / 4.0) + + assert robot.disabled_init_count == 1 + assert robot.disabled_periodic_count == 1 + assert callback_count[0] == 2 + + robot.endCompetition() + robot_thread.join() diff --git a/wpilibc/src/test/python/framework/test_timeslice_robot.py b/wpilibc/src/test/python/framework/test_timeslice_robot.py new file mode 100644 index 00000000000..95a1ceddeb9 --- /dev/null +++ b/wpilibc/src/test/python/framework/test_timeslice_robot.py @@ -0,0 +1,87 @@ +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] + + robot.schedule(lambda: callback_count1.__setitem__(0, callback_count1[0] + 1), 0.0005) + robot.schedule(lambda: callback_count2.__setitem__(0, callback_count2[0] + 1), 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] == 0 + assert callback_count2[0] == 0 + + # Step to 1.5 ms into next period — nothing yet + stepTiming(0.0015) + assert robot.robot_periodic_count == 0 + assert callback_count1[0] == 0 + assert callback_count2[0] == 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[0] == 1 + assert callback_count2[0] == 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[0] == 1 + assert callback_count2[0] == 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() diff --git a/wpilibc/src/test/python/test_onboard_imu.py b/wpilibc/src/test/python/hardware/imu/test_onboard_imu.py similarity index 100% rename from wpilibc/src/test/python/test_onboard_imu.py rename to wpilibc/src/test/python/hardware/imu/test_onboard_imu.py diff --git a/wpilibc/src/test/python/hardware/pneumatic/test_double_solenoid.py b/wpilibc/src/test/python/hardware/pneumatic/test_double_solenoid.py new file mode 100644 index 00000000000..9df7f031952 --- /dev/null +++ b/wpilibc/src/test/python/hardware/pneumatic/test_double_solenoid.py @@ -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) diff --git a/wpilibc/src/test/python/hardware/pneumatic/test_solenoid.py b/wpilibc/src/test/python/hardware/pneumatic/test_solenoid.py new file mode 100644 index 00000000000..21f63fdb834 --- /dev/null +++ b/wpilibc/src/test/python/hardware/pneumatic/test_solenoid.py @@ -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() diff --git a/wpilibc/src/test/python/hardware/range/test_sharp_ir.py b/wpilibc/src/test/python/hardware/range/test_sharp_ir.py new file mode 100644 index 00000000000..fe2756caf6a --- /dev/null +++ b/wpilibc/src/test/python/hardware/range/test_sharp_ir.py @@ -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) diff --git a/wpilibc/src/test/python/hardware/rotation/test_analog_potentiometer.py b/wpilibc/src/test/python/hardware/rotation/test_analog_potentiometer.py new file mode 100644 index 00000000000..24dec806d3e --- /dev/null +++ b/wpilibc/src/test/python/hardware/rotation/test_analog_potentiometer.py @@ -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) diff --git a/wpilibc/src/test/python/test_alert.py b/wpilibc/src/test/python/simulation/test_alert_sim.py similarity index 99% rename from wpilibc/src/test/python/test_alert.py rename to wpilibc/src/test/python/simulation/test_alert_sim.py index 7b8c61dd60d..e352b1e3e74 100644 --- a/wpilibc/src/test/python/test_alert.py +++ b/wpilibc/src/test/python/simulation/test_alert_sim.py @@ -10,10 +10,9 @@ @pytest.fixture(scope="function") def group_name(request): - + AlertSim.resetData() group_name = f"AlertTest_{request.node.name}" yield group_name - AlertSim.resetData() diff --git a/wpilibc/src/test/python/smartdashboard/test_mechanism2d.py b/wpilibc/src/test/python/smartdashboard/test_mechanism2d.py new file mode 100644 index 00000000000..5c63d877eb0 --- /dev/null +++ b/wpilibc/src/test/python/smartdashboard/test_mechanism2d.py @@ -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)) diff --git a/wpilibc/src/test/python/test_sendable_chooser.py b/wpilibc/src/test/python/smartdashboard/test_sendable_chooser.py similarity index 100% rename from wpilibc/src/test/python/test_sendable_chooser.py rename to wpilibc/src/test/python/smartdashboard/test_sendable_chooser.py diff --git a/wpilibc/src/test/python/test_datalogmanager.py b/wpilibc/src/test/python/system/test_data_log_manager.py similarity index 100% rename from wpilibc/src/test/python/test_datalogmanager.py rename to wpilibc/src/test/python/system/test_data_log_manager.py diff --git a/wpilibc/src/test/python/test_notifier.py b/wpilibc/src/test/python/system/test_notifier.py similarity index 100% rename from wpilibc/src/test/python/test_notifier.py rename to wpilibc/src/test/python/system/test_notifier.py diff --git a/wpilibc/src/test/python/system/test_timer.py b/wpilibc/src/test/python/system/test_timer.py new file mode 100644 index 00000000000..c8cd048d630 --- /dev/null +++ b/wpilibc/src/test/python/system/test_timer.py @@ -0,0 +1,104 @@ +import pytest + +from wpilib import Timer +from wpilib.simulation import pauseTiming, restartTiming, resumeTiming, stepTiming + + +@pytest.fixture(autouse=True) +def timer_setup(): + pauseTiming() + restartTiming() + yield + resumeTiming() + + +def test_start_stop(): + timer = Timer() + + # Stopped timer doesn't advance + assert timer.get() == pytest.approx(0.0) + assert not timer.isRunning() + stepTiming(0.5) + assert timer.get() == pytest.approx(0.0) + assert not timer.isRunning() + + # Running timer advances + timer.start() + stepTiming(0.5) + assert timer.get() == pytest.approx(0.5) + assert timer.isRunning() + + # Stopped timer freezes + timer.stop() + stepTiming(0.5) + assert timer.get() == pytest.approx(0.5) + assert not timer.isRunning() + + +def test_reset(): + timer = Timer() + timer.start() + + assert timer.get() == pytest.approx(0.0) + stepTiming(0.5) + assert timer.get() == pytest.approx(0.5) + + timer.reset() + assert timer.get() == pytest.approx(0.0) + + stepTiming(0.5) + assert timer.get() == pytest.approx(0.5) + + # Reset while stopped keeps timer stopped + timer.stop() + timer.reset() + stepTiming(0.5) + assert timer.get() == pytest.approx(0.0) + + +def test_has_elapsed(): + timer = Timer() + + # 0 s elapsed since timer hasn't started + assert timer.hasElapsed(0.0) + + # Stopped timer doesn't accumulate elapsed time + stepTiming(0.5) + assert not timer.hasElapsed(0.4) + + timer.start() + + stepTiming(0.5) + assert timer.hasElapsed(0.4) + assert timer.hasElapsed(0.4) + + +def test_advance_if_elapsed(): + timer = Timer() + + # 0 s has elapsed + assert timer.advanceIfElapsed(0.0) + + # Stopped timer doesn't accumulate elapsed time + stepTiming(0.5) + assert not timer.advanceIfElapsed(0.4) + + timer.start() + + # First call returns True, second returns False (advanced past threshold) + stepTiming(0.5) + assert timer.advanceIfElapsed(0.4) + assert not timer.advanceIfElapsed(0.4) + + # After 1 more second, two 400 ms periods have elapsed + stepTiming(1.0) + assert timer.advanceIfElapsed(0.4) + assert timer.advanceIfElapsed(0.4) + assert not timer.advanceIfElapsed(0.4) + + +def test_get_monotonic_timestamp(): + start = Timer.getMonotonicTimestamp() + stepTiming(0.5) + end = Timer.getMonotonicTimestamp() + assert (end - start) == pytest.approx(0.5) diff --git a/wpilibc/src/test/python/system/test_watchdog.py b/wpilibc/src/test/python/system/test_watchdog.py new file mode 100644 index 00000000000..33f8e26ab69 --- /dev/null +++ b/wpilibc/src/test/python/system/test_watchdog.py @@ -0,0 +1,128 @@ +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] + watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + + # Run 1: disable before timeout + watchdog.enable() + stepTiming(0.2) + watchdog.disable() + assert counter[0] == 0 + + # Run 2: step past timeout + counter[0] = 0 + watchdog.enable() + stepTiming(0.4) + watchdog.disable() + assert counter[0] == 1 + + # Run 3: step well past timeout, only triggers once + counter[0] = 0 + watchdog.enable() + stepTiming(1.0) + watchdog.disable() + assert counter[0] == 1 + + +def test_reset(): + counter = [0] + watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + + watchdog.enable() + stepTiming(0.2) + watchdog.reset() + stepTiming(0.2) + watchdog.disable() + + assert counter[0] == 0 + + +def test_set_timeout(): + counter = [0] + watchdog = Watchdog(1.0, lambda: counter.__setitem__(0, counter[0] + 1)) + + watchdog.enable() + stepTiming(0.2) + watchdog.setTimeout(0.2) + + assert watchdog.getTimeout() == pytest.approx(0.2) + assert counter[0] == 0 + + stepTiming(0.3) + watchdog.disable() + + assert counter[0] == 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] + watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + + # 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] == 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] == 0 + + +def test_multi_watchdog(): + counter1 = [0] + counter2 = [0] + watchdog1 = Watchdog(0.2, lambda: counter1.__setitem__(0, counter1[0] + 1)) + watchdog2 = Watchdog(0.6, lambda: counter2.__setitem__(0, counter2[0] + 1)) + + watchdog2.enable() + stepTiming(0.25) + assert counter1[0] == 0 + assert counter2[0] == 0 + + # watchdog1 enabled later but has shorter timeout — expires first + watchdog1.enable() + stepTiming(0.25) + watchdog1.disable() + watchdog2.disable() + + assert counter1[0] == 1 + assert counter2[0] == 0 diff --git a/wpilibc/src/test/python/test_mechanism2d.py b/wpilibc/src/test/python/test_mechanism2d.py deleted file mode 100644 index 82a9abb5eba..00000000000 --- a/wpilibc/src/test/python/test_mechanism2d.py +++ /dev/null @@ -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? From 962a2eb9010ded9a0e39c263d29b202f6d3371a9 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Mon, 18 May 2026 04:04:05 -0400 Subject: [PATCH 2/8] Fixup some claude-isms --- .../python/drive/test_differential_drive.py | 208 ++++++++++-------- .../test/python/drive/test_mecanum_drive.py | 176 +++++++++------ 2 files changed, 225 insertions(+), 159 deletions(-) diff --git a/wpilibc/src/test/python/drive/test_differential_drive.py b/wpilibc/src/test/python/drive/test_differential_drive.py index 48d4f482a5e..b148a144949 100644 --- a/wpilibc/src/test/python/drive/test_differential_drive.py +++ b/wpilibc/src/test/python/drive/test_differential_drive.py @@ -2,15 +2,15 @@ from wpilib import DifferentialDrive +class MockMotorController: + def __init__(self): + self.throttle = 0 -def _make_drive(): - outputs = {"left": 0.0, "right": 0.0} - drive = DifferentialDrive( - lambda x: outputs.__setitem__("left", x), - lambda x: outputs.__setitem__("right", x), - ) - drive.setDeadband(0.0) - return drive, outputs + def setThrottle(self, throttle): + self.throttle = throttle + + def getThrottle(self): + return self.throttle def test_arcade_drive_ik(): @@ -231,168 +231,204 @@ def test_tank_drive_ik_squared(): def test_arcade_drive(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.arcadeDrive(1.0, 0.0, False) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.arcadeDrive(0.5, 0.5, False) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(0.5) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(0.5) drive.arcadeDrive(0.5, -0.5, False) - assert outputs["left"] == pytest.approx(0.5) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(0.5) + assert right.getThrottle() == pytest.approx(0.0) drive.arcadeDrive(-1.0, 0.0, False) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.arcadeDrive(-0.5, 0.5, False) - assert outputs["left"] == pytest.approx(-0.5) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(-0.5) + assert right.getThrottle() == pytest.approx(0.0) drive.arcadeDrive(-0.5, -0.5, False) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(-0.5) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(-0.5) def test_arcade_drive_squared(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.arcadeDrive(1.0, 0.0, True) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.arcadeDrive(0.5, 0.5, True) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(0.25) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(0.25) drive.arcadeDrive(0.5, -0.5, True) - assert outputs["left"] == pytest.approx(0.25) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(0.25) + assert right.getThrottle() == pytest.approx(0.0) drive.arcadeDrive(-1.0, 0.0, True) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.arcadeDrive(-0.5, 0.5, True) - assert outputs["left"] == pytest.approx(-0.25) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(-0.25) + assert right.getThrottle() == pytest.approx(0.0) drive.arcadeDrive(-0.5, -0.5, True) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(-0.25) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(-0.25) def test_curvature_drive(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.curvatureDrive(1.0, 0.0, False) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.curvatureDrive(0.5, 0.5, False) - assert outputs["left"] == pytest.approx(0.25) - assert outputs["right"] == pytest.approx(0.75) + assert left.getThrottle() == pytest.approx(0.25) + assert right.getThrottle() == pytest.approx(0.75) drive.curvatureDrive(0.5, -0.5, False) - assert outputs["left"] == pytest.approx(0.75) - assert outputs["right"] == pytest.approx(0.25) + assert left.getThrottle() == pytest.approx(0.75) + assert right.getThrottle() == pytest.approx(0.25) drive.curvatureDrive(-1.0, 0.0, False) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.curvatureDrive(-0.5, 0.5, False) - assert outputs["left"] == pytest.approx(-0.75) - assert outputs["right"] == pytest.approx(-0.25) + assert left.getThrottle() == pytest.approx(-0.75) + assert right.getThrottle() == pytest.approx(-0.25) drive.curvatureDrive(-0.5, -0.5, False) - assert outputs["left"] == pytest.approx(-0.25) - assert outputs["right"] == pytest.approx(-0.75) + assert left.getThrottle() == pytest.approx(-0.25) + assert right.getThrottle() == pytest.approx(-0.75) def test_curvature_drive_turn_in_place(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.curvatureDrive(1.0, 0.0, True) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.curvatureDrive(0.5, 0.5, True) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(1.0) drive.curvatureDrive(0.5, -0.5, True) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(0.0) drive.curvatureDrive(-1.0, 0.0, True) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.curvatureDrive(-0.5, 0.5, True) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(0.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(0.0) drive.curvatureDrive(-0.5, -0.5, True) - assert outputs["left"] == pytest.approx(0.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(0.0) + assert right.getThrottle() == pytest.approx(-1.0) def test_tank_drive(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.tankDrive(1.0, 1.0, False) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.tankDrive(0.5, 1.0, False) - assert outputs["left"] == pytest.approx(0.5) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(0.5) + assert right.getThrottle() == pytest.approx(1.0) drive.tankDrive(1.0, 0.5, False) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(0.5) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(0.5) drive.tankDrive(-1.0, -1.0, False) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.tankDrive(-0.5, -1.0, False) - assert outputs["left"] == pytest.approx(-0.5) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-0.5) + assert right.getThrottle() == pytest.approx(-1.0) drive.tankDrive(-0.5, 1.0, False) - assert outputs["left"] == pytest.approx(-0.5) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(-0.5) + assert right.getThrottle() == pytest.approx(1.0) def test_tank_drive_squared(wpilib_state): - drive, outputs = _make_drive() + left = MockMotorController() + right = MockMotorController() + drive = DifferentialDrive( + left.setThrottle, + right.setThrottle, + ) + drive.setDeadband(0.0) drive.tankDrive(1.0, 1.0, True) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(1.0) drive.tankDrive(0.5, 1.0, True) - assert outputs["left"] == pytest.approx(0.25) - assert outputs["right"] == pytest.approx(1.0) + assert left.getThrottle() == pytest.approx(0.25) + assert right.getThrottle() == pytest.approx(1.0) drive.tankDrive(1.0, 0.5, True) - assert outputs["left"] == pytest.approx(1.0) - assert outputs["right"] == pytest.approx(0.25) + assert left.getThrottle() == pytest.approx(1.0) + assert right.getThrottle() == pytest.approx(0.25) drive.tankDrive(-1.0, -1.0, True) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-1.0) drive.tankDrive(-0.5, -1.0, True) - assert outputs["left"] == pytest.approx(-0.25) - assert outputs["right"] == pytest.approx(-1.0) + assert left.getThrottle() == pytest.approx(-0.25) + assert right.getThrottle() == pytest.approx(-1.0) drive.tankDrive(-1.0, -0.5, True) - assert outputs["left"] == pytest.approx(-1.0) - assert outputs["right"] == pytest.approx(-0.25) + assert left.getThrottle() == pytest.approx(-1.0) + assert right.getThrottle() == pytest.approx(-0.25) diff --git a/wpilibc/src/test/python/drive/test_mecanum_drive.py b/wpilibc/src/test/python/drive/test_mecanum_drive.py index f0261e41a03..871e02d0d8f 100644 --- a/wpilibc/src/test/python/drive/test_mecanum_drive.py +++ b/wpilibc/src/test/python/drive/test_mecanum_drive.py @@ -6,16 +6,15 @@ from wpilib import MecanumDrive -def _make_drive(): - outputs = {"fl": 0.0, "rl": 0.0, "fr": 0.0, "rr": 0.0} - drive = MecanumDrive( - lambda x: outputs.__setitem__("fl", x), - lambda x: outputs.__setitem__("rl", x), - lambda x: outputs.__setitem__("fr", x), - lambda x: outputs.__setitem__("rr", x), - ) - drive.setDeadband(0.0) - return drive, outputs +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(): @@ -95,118 +94,149 @@ def test_cartesian_ik_gyro_90_cw(): def test_cartesian(wpilib_state): - drive, outputs = _make_drive() + 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 outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(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) # Left drive.driveCartesian(0.0, -1.0, 0.0) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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) # Right drive.driveCartesian(0.0, 1.0, 0.0) - assert outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(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 CCW drive.driveCartesian(0.0, 0.0, -1.0) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(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 outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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): - drive, outputs = _make_drive() + 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 outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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) # Left in global frame; backward in robot frame drive.driveCartesian(0.0, -1.0, 0.0, gyro) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(-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) # Right in global frame; forward in robot frame drive.driveCartesian(0.0, 1.0, 0.0, gyro) - assert outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(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 CCW drive.driveCartesian(0.0, 0.0, -1.0, gyro) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(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, gyro) - assert outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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_polar(wpilib_state): - drive, outputs = _make_drive() + 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 outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(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) # Left drive.drivePolar(1.0, Rotation2d.fromDegrees(-90), 0.0) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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) # Right drive.drivePolar(1.0, Rotation2d.fromDegrees(90), 0.0) - assert outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(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 CCW drive.drivePolar(0.0, Rotation2d.fromDegrees(0), -1.0) - assert outputs["fl"] == pytest.approx(-1.0) - assert outputs["fr"] == pytest.approx(1.0) - assert outputs["rl"] == pytest.approx(-1.0) - assert outputs["rr"] == pytest.approx(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 outputs["fl"] == pytest.approx(1.0) - assert outputs["fr"] == pytest.approx(-1.0) - assert outputs["rl"] == pytest.approx(1.0) - assert outputs["rr"] == pytest.approx(-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) From 5a340f8e94bfba62110adb8338cb732612ac5c02 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Mon, 18 May 2026 04:14:55 -0400 Subject: [PATCH 3/8] Prefer tests written by human for joysticks --- .../driverstation/joystick_test_macros.py | 45 ++++ .../driverstation/test_driver_station.py | 24 +- .../driverstation/test_nids_ps4_controller.py | 238 +++--------------- .../driverstation/test_nids_ps5_controller.py | 238 +++--------------- .../test_nids_xbox_controller.py | 193 +++----------- 5 files changed, 157 insertions(+), 581 deletions(-) create mode 100644 wpilibc/src/test/python/driverstation/joystick_test_macros.py diff --git a/wpilibc/src/test/python/driverstation/joystick_test_macros.py b/wpilibc/src/test/python/driverstation/joystick_test_macros.py new file mode 100644 index 00000000000..078088ea90b --- /dev/null +++ b/wpilibc/src/test/python/driverstation/joystick_test_macros.py @@ -0,0 +1,45 @@ +import pytest + + +def axis_test(controller_clazz, sim_clazz, axis_name): + joy = controller_clazz(2) + joysim = sim_clazz(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_clazz, sim_clazz, btn_name): + joy = controller_clazz(1) + joysim = sim_clazz(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() diff --git a/wpilibc/src/test/python/driverstation/test_driver_station.py b/wpilibc/src/test/python/driverstation/test_driver_station.py index bff5f9e8e5e..e86cbb43663 100644 --- a/wpilibc/src/test/python/driverstation/test_driver_station.py +++ b/wpilibc/src/test/python/driverstation/test_driver_station.py @@ -1,7 +1,7 @@ import pytest -from wpilib import DriverStationBackend -from wpilib.simulation import DriverStationSim +from wpilib import DriverStationBackend, Joystick +from wpilib.simulation import DriverStationSim, stepTiming @pytest.mark.parametrize( @@ -25,25 +25,21 @@ def test_is_joystick_connected(wpilib_state, axes_max, buttons_max, povs_max, ex @pytest.mark.parametrize( - "fms_attached, silenced, expected_silenced", + "fms_attached, silenced, expected_silenced, expected_warning", [ - (False, True, True), - (False, False, False), - (True, True, False), - (True, False, False), + (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 + wpilib_state, fms_attached, silenced, expected_silenced, expected_warning, capsys ): - from wpilib.simulation import stepTiming - DriverStationSim.setFmsAttached(fms_attached) DriverStationSim.notifyNewData() DriverStationBackend.silenceJoystickConnectionWarning(silenced) - from wpilib import Joystick - joystick = Joystick(0) joystick.getRawButton(1) @@ -51,3 +47,7 @@ def test_joystick_connection_warnings( assert ( DriverStationBackend.isJoystickConnectionWarningSilenced() == expected_silenced ) + + # Capture stderr to check for warnings + captured = capsys.readouterr() + assert expected_warning in captured.err \ No newline at end of file diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py index 592ac217f96..2f149481a25 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py @@ -1,206 +1,38 @@ -import pytest + from wpilib import NiDsPS4Controller from wpilib.simulation import NiDsPS4ControllerSim - - -def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - get_pressed_fn() - get_released_fn() - - set_fn(True) - joysim.notifyNewData() - assert get_fn() - assert get_pressed_fn() - assert not get_released_fn() - - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - assert not get_pressed_fn() - assert get_released_fn() - - -def _axis_test(joy, joysim, set_fn, get_fn): - set_fn(0.35) - joysim.notifyNewData() - assert get_fn() == pytest.approx(0.35, abs=0.001) - - -def test_get_square_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setSquareButton, joy.getSquareButton, - joy.getSquareButtonPressed, joy.getSquareButtonReleased, - ) - - -def test_get_cross_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setCrossButton, joy.getCrossButton, - joy.getCrossButtonPressed, joy.getCrossButtonReleased, - ) - - -def test_get_circle_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setCircleButton, joy.getCircleButton, - joy.getCircleButtonPressed, joy.getCircleButtonReleased, - ) - - -def test_get_triangle_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setTriangleButton, joy.getTriangleButton, - joy.getTriangleButtonPressed, joy.getTriangleButtonReleased, - ) - - -def test_get_l1_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL1Button, joy.getL1Button, - joy.getL1ButtonPressed, joy.getL1ButtonReleased, - ) - - -def test_get_r1_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR1Button, joy.getR1Button, - joy.getR1ButtonPressed, joy.getR1ButtonReleased, - ) - - -def test_get_l2_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL2Button, joy.getL2Button, - joy.getL2ButtonPressed, joy.getL2ButtonReleased, - ) - - -def test_get_r2_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR2Button, joy.getR2Button, - joy.getR2ButtonPressed, joy.getR2ButtonReleased, - ) - - -def test_get_share_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setShareButton, joy.getShareButton, - joy.getShareButtonPressed, joy.getShareButtonReleased, - ) - - -def test_get_options_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setOptionsButton, joy.getOptionsButton, - joy.getOptionsButtonPressed, joy.getOptionsButtonReleased, - ) - - -def test_get_l3_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL3Button, joy.getL3Button, - joy.getL3ButtonPressed, joy.getL3ButtonReleased, - ) - - -def test_get_r3_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR3Button, joy.getR3Button, - joy.getR3ButtonPressed, joy.getR3ButtonReleased, - ) - - -def test_get_ps_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setPSButton, joy.getPSButton, - joy.getPSButtonPressed, joy.getPSButtonReleased, - ) - - -def test_get_touchpad_button(): - joy = NiDsPS4Controller(1) - joysim = NiDsPS4ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setTouchpadButton, joy.getTouchpadButton, - joy.getTouchpadButtonPressed, joy.getTouchpadButtonReleased, - ) - - -def test_get_left_x(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) - - -def test_get_right_x(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) - - -def test_get_left_y(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) - - -def test_get_right_y(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) - - -def test_get_l2_axis(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setL2Axis, joy.getL2Axis) - - -def test_get_r2_axis(): - joy = NiDsPS4Controller(2) - joysim = NiDsPS4ControllerSim(joy) - _axis_test(joy, joysim, joysim.setR2Axis, joy.getR2Axis) +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") \ No newline at end of file diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py index 0760080075e..26f0cafc181 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py @@ -1,206 +1,38 @@ -import pytest + from wpilib import NiDsPS5Controller from wpilib.simulation import NiDsPS5ControllerSim - - -def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - get_pressed_fn() - get_released_fn() - - set_fn(True) - joysim.notifyNewData() - assert get_fn() - assert get_pressed_fn() - assert not get_released_fn() - - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - assert not get_pressed_fn() - assert get_released_fn() - - -def _axis_test(joy, joysim, set_fn, get_fn): - set_fn(0.35) - joysim.notifyNewData() - assert get_fn() == pytest.approx(0.35, abs=0.001) - - -def test_get_square_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setSquareButton, joy.getSquareButton, - joy.getSquareButtonPressed, joy.getSquareButtonReleased, - ) - - -def test_get_cross_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setCrossButton, joy.getCrossButton, - joy.getCrossButtonPressed, joy.getCrossButtonReleased, - ) - - -def test_get_circle_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setCircleButton, joy.getCircleButton, - joy.getCircleButtonPressed, joy.getCircleButtonReleased, - ) - - -def test_get_triangle_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setTriangleButton, joy.getTriangleButton, - joy.getTriangleButtonPressed, joy.getTriangleButtonReleased, - ) - - -def test_get_l1_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL1Button, joy.getL1Button, - joy.getL1ButtonPressed, joy.getL1ButtonReleased, - ) - - -def test_get_r1_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR1Button, joy.getR1Button, - joy.getR1ButtonPressed, joy.getR1ButtonReleased, - ) - - -def test_get_l2_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL2Button, joy.getL2Button, - joy.getL2ButtonPressed, joy.getL2ButtonReleased, - ) - - -def test_get_r2_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR2Button, joy.getR2Button, - joy.getR2ButtonPressed, joy.getR2ButtonReleased, - ) - - -def test_get_create_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setCreateButton, joy.getCreateButton, - joy.getCreateButtonPressed, joy.getCreateButtonReleased, - ) - - -def test_get_options_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setOptionsButton, joy.getOptionsButton, - joy.getOptionsButtonPressed, joy.getOptionsButtonReleased, - ) - - -def test_get_l3_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setL3Button, joy.getL3Button, - joy.getL3ButtonPressed, joy.getL3ButtonReleased, - ) - - -def test_get_r3_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setR3Button, joy.getR3Button, - joy.getR3ButtonPressed, joy.getR3ButtonReleased, - ) - - -def test_get_ps_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setPSButton, joy.getPSButton, - joy.getPSButtonPressed, joy.getPSButtonReleased, - ) - - -def test_get_touchpad_button(): - joy = NiDsPS5Controller(1) - joysim = NiDsPS5ControllerSim(joy) - _button_test( - joy, joysim, - joysim.setTouchpadButton, joy.getTouchpadButton, - joy.getTouchpadButtonPressed, joy.getTouchpadButtonReleased, - ) - - -def test_get_left_x(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) - - -def test_get_right_x(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) - - -def test_get_left_y(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) - - -def test_get_right_y(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) - - -def test_get_l2_axis(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setL2Axis, joy.getL2Axis) - - -def test_get_r2_axis(): - joy = NiDsPS5Controller(2) - joysim = NiDsPS5ControllerSim(joy) - _axis_test(joy, joysim, joysim.setR2Axis, joy.getR2Axis) +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") \ No newline at end of file diff --git a/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py index 8c3bdc55863..e7aa091a047 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py @@ -1,166 +1,33 @@ -import pytest + from wpilib import NiDsXboxController from wpilib.simulation import NiDsXboxControllerSim - - -def _button_test(joy, joysim, set_fn, get_fn, get_pressed_fn, get_released_fn): - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - get_pressed_fn() - get_released_fn() - - set_fn(True) - joysim.notifyNewData() - assert get_fn() - assert get_pressed_fn() - assert not get_released_fn() - - set_fn(False) - joysim.notifyNewData() - assert not get_fn() - assert not get_pressed_fn() - assert get_released_fn() - - -def _axis_test(joy, joysim, set_fn, get_fn): - set_fn(0.35) - joysim.notifyNewData() - assert get_fn() == pytest.approx(0.35, abs=0.001) - - -def test_get_left_bumper_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setLeftBumperButton, joy.getLeftBumperButton, - joy.getLeftBumperButtonPressed, joy.getLeftBumperButtonReleased, - ) - - -def test_get_right_bumper_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setRightBumperButton, joy.getRightBumperButton, - joy.getRightBumperButtonPressed, joy.getRightBumperButtonReleased, - ) - - -def test_get_left_stick_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setLeftStickButton, joy.getLeftStickButton, - joy.getLeftStickButtonPressed, joy.getLeftStickButtonReleased, - ) - - -def test_get_right_stick_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setRightStickButton, joy.getRightStickButton, - joy.getRightStickButtonPressed, joy.getRightStickButtonReleased, - ) - - -def test_get_a_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setAButton, joy.getAButton, - joy.getAButtonPressed, joy.getAButtonReleased, - ) - - -def test_get_b_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setBButton, joy.getBButton, - joy.getBButtonPressed, joy.getBButtonReleased, - ) - - -def test_get_x_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setXButton, joy.getXButton, - joy.getXButtonPressed, joy.getXButtonReleased, - ) - - -def test_get_y_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setYButton, joy.getYButton, - joy.getYButtonPressed, joy.getYButtonReleased, - ) - - -def test_get_back_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setBackButton, joy.getBackButton, - joy.getBackButtonPressed, joy.getBackButtonReleased, - ) - - -def test_get_start_button(): - joy = NiDsXboxController(1) - joysim = NiDsXboxControllerSim(joy) - _button_test( - joy, joysim, - joysim.setStartButton, joy.getStartButton, - joy.getStartButtonPressed, joy.getStartButtonReleased, - ) - - -def test_get_left_x(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftX, joy.getLeftX) - - -def test_get_right_x(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightX, joy.getRightX) - - -def test_get_left_y(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftY, joy.getLeftY) - - -def test_get_right_y(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightY, joy.getRightY) - - -def test_get_left_trigger_axis(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setLeftTriggerAxis, joy.getLeftTriggerAxis) - - -def test_get_right_trigger_axis(): - joy = NiDsXboxController(2) - joysim = NiDsXboxControllerSim(joy) - _axis_test(joy, joysim, joysim.setRightTriggerAxis, joy.getRightTriggerAxis) +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") \ No newline at end of file From fbaf2ddcb6dad945b9033ef26ba78b69929f8422 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Fri, 5 Jun 2026 23:21:53 -0400 Subject: [PATCH 4/8] Run black on examples --- .../python/drive/test_differential_drive.py | 1 + .../driverstation/joystick_test_macros.py | 1 - .../driverstation/test_driver_station.py | 27 ++++++++--- .../python/driverstation/test_generic_hid.py | 48 ++++++++++++++----- .../driverstation/test_nids_ps4_controller.py | 7 ++- .../driverstation/test_nids_ps5_controller.py | 7 ++- .../test_nids_xbox_controller.py | 9 ++-- .../test/python/framework/test_timed_robot.py | 4 +- .../python/framework/test_timeslice_robot.py | 8 +++- .../python/hardware/imu/test_onboard_imu.py | 9 ++-- 10 files changed, 82 insertions(+), 39 deletions(-) diff --git a/wpilibc/src/test/python/drive/test_differential_drive.py b/wpilibc/src/test/python/drive/test_differential_drive.py index b148a144949..5c5e3ba2e59 100644 --- a/wpilibc/src/test/python/drive/test_differential_drive.py +++ b/wpilibc/src/test/python/drive/test_differential_drive.py @@ -2,6 +2,7 @@ from wpilib import DifferentialDrive + class MockMotorController: def __init__(self): self.throttle = 0 diff --git a/wpilibc/src/test/python/driverstation/joystick_test_macros.py b/wpilibc/src/test/python/driverstation/joystick_test_macros.py index 078088ea90b..5f8784d30a6 100644 --- a/wpilibc/src/test/python/driverstation/joystick_test_macros.py +++ b/wpilibc/src/test/python/driverstation/joystick_test_macros.py @@ -36,7 +36,6 @@ def button_test(controller_clazz, sim_clazz, btn_name): assert True == joy_get_func() assert True == joy_get_pressed_func() assert False == joy_get_released_func() - sim_setter_func(False) joysim.notifyNewData() diff --git a/wpilibc/src/test/python/driverstation/test_driver_station.py b/wpilibc/src/test/python/driverstation/test_driver_station.py index e86cbb43663..6e89f30016c 100644 --- a/wpilibc/src/test/python/driverstation/test_driver_station.py +++ b/wpilibc/src/test/python/driverstation/test_driver_station.py @@ -25,12 +25,27 @@ def test_is_joystick_connected(wpilib_state, axes_max, buttons_max, povs_max, ex @pytest.mark.parametrize( - "fms_attached, silenced, expected_silenced, expected_warning", + "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"), + ( + 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( @@ -47,7 +62,7 @@ def test_joystick_connection_warnings( assert ( DriverStationBackend.isJoystickConnectionWarningSilenced() == expected_silenced ) - + # Capture stderr to check for warnings captured = capsys.readouterr() - assert expected_warning in captured.err \ No newline at end of file + assert expected_warning in captured.err diff --git a/wpilibc/src/test/python/driverstation/test_generic_hid.py b/wpilibc/src/test/python/driverstation/test_generic_hid.py index 23f7de6351d..97bbbeb3aec 100644 --- a/wpilibc/src/test/python/driverstation/test_generic_hid.py +++ b/wpilibc/src/test/python/driverstation/test_generic_hid.py @@ -15,10 +15,14 @@ def test_rumble_range(wpilib_state): value = i / 100.0 hid.setRumble(RumbleType.LEFT_RUMBLE, value) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(value, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx( + value, abs=_EPSILON + ) hid.setRumble(RumbleType.RIGHT_RUMBLE, value) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(value, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx( + value, abs=_EPSILON + ) hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, value) assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( @@ -41,38 +45,58 @@ def test_rumble_types(wpilib_state): hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) # Left only hid.setRumble(RumbleType.LEFT_RUMBLE, 1) assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(1, abs=_EPSILON) assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) hid.setRumble(RumbleType.LEFT_RUMBLE, 0) # Right only hid.setRumble(RumbleType.RIGHT_RUMBLE, 1) assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(1, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) # Left trigger only hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 1) assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(1, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + 1, abs=_EPSILON + ) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 0) # Right trigger only hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 1) assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx(1, abs=_EPSILON) + assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( + 0, abs=_EPSILON + ) + assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( + 1, abs=_EPSILON + ) hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py index 2f149481a25..3ad88b89617 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_ps4_controller.py @@ -1,13 +1,12 @@ - - 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) @@ -35,4 +34,4 @@ def ps4_axis_test(axis_name): ps4_axis_test("LeftY") ps4_axis_test("RightY") ps4_axis_test("L2Axis") - ps4_axis_test("R2Axis") \ No newline at end of file + ps4_axis_test("R2Axis") diff --git a/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py index 26f0cafc181..afa8b6e6ea3 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_ps5_controller.py @@ -1,13 +1,12 @@ - - 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) @@ -35,4 +34,4 @@ def ps5_axis_test(axis_name): ps5_axis_test("LeftY") ps5_axis_test("RightY") ps5_axis_test("L2Axis") - ps5_axis_test("R2Axis") \ No newline at end of file + ps5_axis_test("R2Axis") diff --git a/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py index e7aa091a047..da1ac1cf7dc 100644 --- a/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py +++ b/wpilibc/src/test/python/driverstation/test_nids_xbox_controller.py @@ -1,19 +1,18 @@ - - 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") @@ -30,4 +29,4 @@ def xbox_axis_test(axis_name): xbox_axis_test("RightY") xbox_axis_test("LeftTriggerAxis") - xbox_axis_test("RightTriggerAxis") \ No newline at end of file + xbox_axis_test("RightTriggerAxis") diff --git a/wpilibc/src/test/python/framework/test_timed_robot.py b/wpilibc/src/test/python/framework/test_timed_robot.py index 68eb52cf869..b123e9622ec 100644 --- a/wpilibc/src/test/python/framework/test_timed_robot.py +++ b/wpilibc/src/test/python/framework/test_timed_robot.py @@ -317,7 +317,9 @@ def test_mode_change(): def test_add_periodic(): robot = MockRobot() callback_count = [0] - robot.addPeriodic(lambda: callback_count.__setitem__(0, callback_count[0] + 1), _PERIOD / 2.0) + robot.addPeriodic( + lambda: callback_count.__setitem__(0, callback_count[0] + 1), _PERIOD / 2.0 + ) robot_thread = threading.Thread(target=robot.startCompetition, daemon=True) robot_thread.start() diff --git a/wpilibc/src/test/python/framework/test_timeslice_robot.py b/wpilibc/src/test/python/framework/test_timeslice_robot.py index 95a1ceddeb9..915aa34b28c 100644 --- a/wpilibc/src/test/python/framework/test_timeslice_robot.py +++ b/wpilibc/src/test/python/framework/test_timeslice_robot.py @@ -36,8 +36,12 @@ def test_schedule(): callback_count1 = [0] callback_count2 = [0] - robot.schedule(lambda: callback_count1.__setitem__(0, callback_count1[0] + 1), 0.0005) - robot.schedule(lambda: callback_count2.__setitem__(0, callback_count2[0] + 1), 0.001) + robot.schedule( + lambda: callback_count1.__setitem__(0, callback_count1[0] + 1), 0.0005 + ) + robot.schedule( + lambda: callback_count2.__setitem__(0, callback_count2[0] + 1), 0.001 + ) robot_thread = threading.Thread(target=robot.startCompetition, daemon=True) robot_thread.start() diff --git a/wpilibc/src/test/python/hardware/imu/test_onboard_imu.py b/wpilibc/src/test/python/hardware/imu/test_onboard_imu.py index 382a738c2dc..d872e29e179 100644 --- a/wpilibc/src/test/python/hardware/imu/test_onboard_imu.py +++ b/wpilibc/src/test/python/hardware/imu/test_onboard_imu.py @@ -1,6 +1,7 @@ from wpilib import OnboardIMU from wpilib.simulation import OnboardIMUSim + def test_sim_device() -> None: imu = OnboardIMU(OnboardIMU.MountOrientation.FLAT) @@ -15,15 +16,15 @@ def test_sim_device() -> None: assert 0.0 == imu.getAccelX() assert 0.0 == imu.getAccelY() assert 0.0 == imu.getAccelZ() - + sim.setAngleX(1) sim.setAngleY(2) sim.setAngleZ(3) - + sim.setGyroRateX(3.504) sim.setGyroRateY(1.91) sim.setGyroRateZ(22.9) - + sim.setAccelX(-1) sim.setAccelY(-2) sim.setAccelZ(-3) @@ -38,4 +39,4 @@ def test_sim_device() -> None: assert -1.0 == imu.getAccelX() assert -2.0 == imu.getAccelY() - assert -3.0 == imu.getAccelZ() \ No newline at end of file + assert -3.0 == imu.getAccelZ() From 583eb6aa84759502a3f1757b21c48b2074f9c7c2 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Sun, 7 Jun 2026 02:01:17 -0400 Subject: [PATCH 5/8] Fix all the __setitem__ things --- .../event/test_network_boolean_event.py | 16 ++-- .../test/python/framework/test_timed_robot.py | 40 ++++----- .../python/framework/test_timeslice_robot.py | 36 +++++---- .../src/test/python/system/test_watchdog.py | 81 +++++++++++++------ 4 files changed, 106 insertions(+), 67 deletions(-) diff --git a/wpilibc/src/test/python/event/test_network_boolean_event.py b/wpilibc/src/test/python/event/test_network_boolean_event.py index 749f772b83d..84175071aa9 100644 --- a/wpilibc/src/test/python/event/test_network_boolean_event.py +++ b/wpilibc/src/test/python/event/test_network_boolean_event.py @@ -18,22 +18,24 @@ def nt_inst(wpilib_state): def test_set(nt_inst): loop = EventLoop() - counter = [0] + 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( - lambda: counter.__setitem__(0, counter[0] + 1) - ) + NetworkBooleanEvent(loop, nt_inst, "TestTable", "Test").ifHigh(on_high) pub.set(False) loop.poll() - assert counter[0] == 0 + assert counter == 0 pub.set(True) loop.poll() - assert counter[0] == 1 + assert counter == 1 pub.set(False) loop.poll() - assert counter[0] == 1 + assert counter == 1 diff --git a/wpilibc/src/test/python/framework/test_timed_robot.py b/wpilibc/src/test/python/framework/test_timed_robot.py index b123e9622ec..81b66bb2248 100644 --- a/wpilibc/src/test/python/framework/test_timed_robot.py +++ b/wpilibc/src/test/python/framework/test_timed_robot.py @@ -316,10 +316,13 @@ def test_mode_change(): def test_add_periodic(): robot = MockRobot() - callback_count = [0] - robot.addPeriodic( - lambda: callback_count.__setitem__(0, callback_count[0] + 1), _PERIOD / 2.0 - ) + 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() @@ -330,19 +333,19 @@ def test_add_periodic(): assert robot.disabled_init_count == 0 assert robot.disabled_periodic_count == 0 - assert callback_count[0] == 0 + assert callback_count == 0 stepTiming(_PERIOD / 2.0) assert robot.disabled_init_count == 0 assert robot.disabled_periodic_count == 0 - assert callback_count[0] == 1 + assert callback_count == 1 stepTiming(_PERIOD / 2.0) assert robot.disabled_init_count == 1 assert robot.disabled_periodic_count == 1 - assert callback_count[0] == 2 + assert callback_count == 2 robot.endCompetition() robot_thread.join() @@ -350,12 +353,13 @@ def test_add_periodic(): def test_add_periodic_with_offset(): robot = MockRobot() - callback_count = [0] - robot.addPeriodic( - lambda: callback_count.__setitem__(0, callback_count[0] + 1), - _PERIOD / 2.0, - _PERIOD / 4.0, - ) + 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() @@ -366,31 +370,31 @@ def test_add_periodic_with_offset(): assert robot.disabled_init_count == 0 assert robot.disabled_periodic_count == 0 - assert callback_count[0] == 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] == 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] == 1 + assert callback_count == 1 stepTiming(_PERIOD / 4.0) assert robot.disabled_init_count == 1 assert robot.disabled_periodic_count == 1 - assert callback_count[0] == 1 + assert callback_count == 1 stepTiming(_PERIOD / 4.0) assert robot.disabled_init_count == 1 assert robot.disabled_periodic_count == 1 - assert callback_count[0] == 2 + assert callback_count == 2 robot.endCompetition() robot_thread.join() diff --git a/wpilibc/src/test/python/framework/test_timeslice_robot.py b/wpilibc/src/test/python/framework/test_timeslice_robot.py index 915aa34b28c..910db62a559 100644 --- a/wpilibc/src/test/python/framework/test_timeslice_robot.py +++ b/wpilibc/src/test/python/framework/test_timeslice_robot.py @@ -33,15 +33,19 @@ def robotPeriodic(self): def test_schedule(): robot = MockRobot() - callback_count1 = [0] - callback_count2 = [0] + callback_count1 = 0 + callback_count2 = 0 - robot.schedule( - lambda: callback_count1.__setitem__(0, callback_count1[0] + 1), 0.0005 - ) - robot.schedule( - lambda: callback_count2.__setitem__(0, callback_count2[0] + 1), 0.001 - ) + 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() @@ -53,26 +57,26 @@ def test_schedule(): # First 5 ms: no callbacks fired yet (delayed by one period) stepTiming(0.005) assert robot.robot_periodic_count == 0 - assert callback_count1[0] == 0 - assert callback_count2[0] == 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] == 0 - assert callback_count2[0] == 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[0] == 1 - assert callback_count2[0] == 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[0] == 1 - assert callback_count2[0] == 1 + assert callback_count1 == 1 + assert callback_count2 == 1 robot.endCompetition() robot_thread.join() diff --git a/wpilibc/src/test/python/system/test_watchdog.py b/wpilibc/src/test/python/system/test_watchdog.py index 33f8e26ab69..bcd4eba7491 100644 --- a/wpilibc/src/test/python/system/test_watchdog.py +++ b/wpilibc/src/test/python/system/test_watchdog.py @@ -12,33 +12,43 @@ def watchdog_setup(): def test_enable_disable(): - counter = [0] - watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + 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] == 0 + assert counter == 0 # Run 2: step past timeout - counter[0] = 0 + counter = 0 watchdog.enable() stepTiming(0.4) watchdog.disable() - assert counter[0] == 1 + assert counter == 1 # Run 3: step well past timeout, only triggers once - counter[0] = 0 + counter = 0 watchdog.enable() stepTiming(1.0) watchdog.disable() - assert counter[0] == 1 + assert counter == 1 def test_reset(): - counter = [0] - watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + counter = 0 + + def on_expire() -> None: + nonlocal counter + counter += 1 + + watchdog = Watchdog(0.4, on_expire) watchdog.enable() stepTiming(0.2) @@ -46,24 +56,29 @@ def test_reset(): stepTiming(0.2) watchdog.disable() - assert counter[0] == 0 + assert counter == 0 def test_set_timeout(): - counter = [0] - watchdog = Watchdog(1.0, lambda: counter.__setitem__(0, counter[0] + 1)) + 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] == 0 + assert counter == 0 stepTiming(0.3) watchdog.disable() - assert counter[0] == 1 + assert counter == 1 def test_is_expired(): @@ -83,8 +98,13 @@ def test_is_expired(): def test_epochs(): - counter = [0] - watchdog = Watchdog(0.4, lambda: counter.__setitem__(0, counter[0] + 1)) + counter = 0 + + def on_expire() -> None: + nonlocal counter + counter += 1 + + watchdog = Watchdog(0.4, on_expire) # Run 1: under timeout with epochs watchdog.enable() @@ -94,7 +114,7 @@ def test_epochs(): stepTiming(0.1) watchdog.addEpoch("Epoch 3") watchdog.disable() - assert counter[0] == 0 + assert counter == 0 # Run 2: reset mid-run keeps under timeout watchdog.enable() @@ -104,19 +124,28 @@ def test_epochs(): stepTiming(0.2) watchdog.addEpoch("Epoch 2") watchdog.disable() - assert counter[0] == 0 + assert counter == 0 def test_multi_watchdog(): - counter1 = [0] - counter2 = [0] - watchdog1 = Watchdog(0.2, lambda: counter1.__setitem__(0, counter1[0] + 1)) - watchdog2 = Watchdog(0.6, lambda: counter2.__setitem__(0, counter2[0] + 1)) + 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] == 0 - assert counter2[0] == 0 + assert counter1 == 0 + assert counter2 == 0 # watchdog1 enabled later but has shorter timeout — expires first watchdog1.enable() @@ -124,5 +153,5 @@ def test_multi_watchdog(): watchdog1.disable() watchdog2.disable() - assert counter1[0] == 1 - assert counter2[0] == 0 + assert counter1 == 1 + assert counter2 == 0 From 8aaa32723f93d4cf33eb70abf9321843c18ae908 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Sun, 7 Jun 2026 02:03:53 -0400 Subject: [PATCH 6/8] clazz -> class --- .../python/driverstation/joystick_test_macros.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wpilibc/src/test/python/driverstation/joystick_test_macros.py b/wpilibc/src/test/python/driverstation/joystick_test_macros.py index 5f8784d30a6..7f2b6111358 100644 --- a/wpilibc/src/test/python/driverstation/joystick_test_macros.py +++ b/wpilibc/src/test/python/driverstation/joystick_test_macros.py @@ -1,9 +1,9 @@ import pytest -def axis_test(controller_clazz, sim_clazz, axis_name): - joy = controller_clazz(2) - joysim = sim_clazz(joy) +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) @@ -13,9 +13,9 @@ def axis_test(controller_clazz, sim_clazz, axis_name): assert joy_get_func() == pytest.approx(0.35, abs=0.001) -def button_test(controller_clazz, sim_clazz, btn_name): - joy = controller_clazz(1) - joysim = sim_clazz(joy) +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") From 879efa51d6a86f8253441dec42070ec531cd9a8e Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Sat, 20 Jun 2026 17:57:02 -0400 Subject: [PATCH 7/8] Prefer existing generic_hid test --- .../python/driverstation/test_generic_hid.py | 137 +++++++----------- .../led}/test_addressable_led_buffer.py | 0 .../{ => hardware/led}/test_led_pattern.py | 0 wpilibc/src/test/python/test_generic_hid.py | 67 --------- 4 files changed, 51 insertions(+), 153 deletions(-) rename wpilibc/src/test/python/{ => hardware/led}/test_addressable_led_buffer.py (100%) rename wpilibc/src/test/python/{ => hardware/led}/test_led_pattern.py (100%) delete mode 100644 wpilibc/src/test/python/test_generic_hid.py diff --git a/wpilibc/src/test/python/driverstation/test_generic_hid.py b/wpilibc/src/test/python/driverstation/test_generic_hid.py index 97bbbeb3aec..a14d15d6f2e 100644 --- a/wpilibc/src/test/python/driverstation/test_generic_hid.py +++ b/wpilibc/src/test/python/driverstation/test_generic_hid.py @@ -1,102 +1,67 @@ import pytest -from wpilib import GenericHID -from wpilib.simulation import GenericHIDSim +from wpilib import DriverStation, DriverStationBackend, GenericHID +from wpilib.simulation import DriverStationSim, GenericHIDSim + RumbleType = GenericHID.RumbleType -_EPSILON = 0.0001 +RUMBLE_TYPES = ( + RumbleType.LEFT_RUMBLE, + RumbleType.RIGHT_RUMBLE, + RumbleType.LEFT_TRIGGER_RUMBLE, + RumbleType.RIGHT_TRIGGER_RUMBLE, +) + + +@pytest.fixture(autouse=True) +def reset_driver_station_sim(): + DriverStationSim.resetData() + yield + DriverStationBackend.resetCachedHIDData() + DriverStationSim.resetData() -def test_rumble_range(wpilib_state): - hid = GenericHID(0) +def test_rumble_range() -> None: + hid = DriverStationBackend.constructGenericHID(0) sim = GenericHIDSim(0) for i in range(101): - value = i / 100.0 + rumble_value = i / 100.0 - hid.setRumble(RumbleType.LEFT_RUMBLE, value) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx( - value, abs=_EPSILON - ) + for rumble_type in RUMBLE_TYPES: + hid.setRumble(rumble_type, rumble_value) + assert sim.getRumble(rumble_type) == pytest.approx( + rumble_value, abs=0.0001 + ) - hid.setRumble(RumbleType.RIGHT_RUMBLE, value) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx( - value, abs=_EPSILON - ) - hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, value) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - value, abs=_EPSILON - ) +def test_rumble_types() -> None: + hid = DriverStationBackend.constructGenericHID(0) + sim = GenericHIDSim(0) - hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, value) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - value, abs=_EPSILON - ) + for rumble_type in RUMBLE_TYPES: + hid.setRumble(rumble_type, 0) + for rumble_type in RUMBLE_TYPES: + assert sim.getRumble(rumble_type) == pytest.approx(0, abs=0.0001) -def test_rumble_types(wpilib_state): - hid = GenericHID(0) - sim = GenericHIDSim(0) + for active_rumble_type in RUMBLE_TYPES: + hid.setRumble(active_rumble_type, 1) + + for rumble_type in RUMBLE_TYPES: + expected = 1 if rumble_type == active_rumble_type else 0 + assert sim.getRumble(rumble_type) == pytest.approx( + expected, abs=0.0001 + ) + + hid.setRumble(active_rumble_type, 0) + + +def test_cached_hid_data_reset() -> None: + DriverStation.getGenericHID(0) + DriverStation.getGamepad(0) + + DriverStationBackend.resetCachedHIDData() - # Make sure all are off - hid.setRumble(RumbleType.LEFT_RUMBLE, 0) - hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 0) - hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) - hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - - # Left only - hid.setRumble(RumbleType.LEFT_RUMBLE, 1) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(1, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - hid.setRumble(RumbleType.LEFT_RUMBLE, 0) - - # Right only - hid.setRumble(RumbleType.RIGHT_RUMBLE, 1) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(1, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - hid.setRumble(RumbleType.RIGHT_RUMBLE, 0) - - # Left trigger only - hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 1) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - 1, abs=_EPSILON - ) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - hid.setRumble(RumbleType.LEFT_TRIGGER_RUMBLE, 0) - - # Right trigger only - hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 1) - assert sim.getRumble(RumbleType.LEFT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.RIGHT_RUMBLE) == pytest.approx(0, abs=_EPSILON) - assert sim.getRumble(RumbleType.LEFT_TRIGGER_RUMBLE) == pytest.approx( - 0, abs=_EPSILON - ) - assert sim.getRumble(RumbleType.RIGHT_TRIGGER_RUMBLE) == pytest.approx( - 1, abs=_EPSILON - ) - hid.setRumble(RumbleType.RIGHT_TRIGGER_RUMBLE, 0) + assert DriverStation.getGenericHID(0).getPort() == 0 + assert DriverStation.getGamepad(0).getPort() == 0 diff --git a/wpilibc/src/test/python/test_addressable_led_buffer.py b/wpilibc/src/test/python/hardware/led/test_addressable_led_buffer.py similarity index 100% rename from wpilibc/src/test/python/test_addressable_led_buffer.py rename to wpilibc/src/test/python/hardware/led/test_addressable_led_buffer.py diff --git a/wpilibc/src/test/python/test_led_pattern.py b/wpilibc/src/test/python/hardware/led/test_led_pattern.py similarity index 100% rename from wpilibc/src/test/python/test_led_pattern.py rename to wpilibc/src/test/python/hardware/led/test_led_pattern.py diff --git a/wpilibc/src/test/python/test_generic_hid.py b/wpilibc/src/test/python/test_generic_hid.py deleted file mode 100644 index a14d15d6f2e..00000000000 --- a/wpilibc/src/test/python/test_generic_hid.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest - -from wpilib import DriverStation, DriverStationBackend, GenericHID -from wpilib.simulation import DriverStationSim, GenericHIDSim - - -RumbleType = GenericHID.RumbleType -RUMBLE_TYPES = ( - RumbleType.LEFT_RUMBLE, - RumbleType.RIGHT_RUMBLE, - RumbleType.LEFT_TRIGGER_RUMBLE, - RumbleType.RIGHT_TRIGGER_RUMBLE, -) - - -@pytest.fixture(autouse=True) -def reset_driver_station_sim(): - DriverStationSim.resetData() - yield - DriverStationBackend.resetCachedHIDData() - DriverStationSim.resetData() - - -def test_rumble_range() -> None: - hid = DriverStationBackend.constructGenericHID(0) - sim = GenericHIDSim(0) - - for i in range(101): - rumble_value = i / 100.0 - - for rumble_type in RUMBLE_TYPES: - hid.setRumble(rumble_type, rumble_value) - assert sim.getRumble(rumble_type) == pytest.approx( - rumble_value, abs=0.0001 - ) - - -def test_rumble_types() -> None: - hid = DriverStationBackend.constructGenericHID(0) - sim = GenericHIDSim(0) - - for rumble_type in RUMBLE_TYPES: - hid.setRumble(rumble_type, 0) - - for rumble_type in RUMBLE_TYPES: - assert sim.getRumble(rumble_type) == pytest.approx(0, abs=0.0001) - - for active_rumble_type in RUMBLE_TYPES: - hid.setRumble(active_rumble_type, 1) - - for rumble_type in RUMBLE_TYPES: - expected = 1 if rumble_type == active_rumble_type else 0 - assert sim.getRumble(rumble_type) == pytest.approx( - expected, abs=0.0001 - ) - - hid.setRumble(active_rumble_type, 0) - - -def test_cached_hid_data_reset() -> None: - DriverStation.getGenericHID(0) - DriverStation.getGamepad(0) - - DriverStationBackend.resetCachedHIDData() - - assert DriverStation.getGenericHID(0).getPort() == 0 - assert DriverStation.getGamepad(0).getPort() == 0 From fdd965982c5e7a409628f3078fd5315cb11ae8a5 Mon Sep 17 00:00:00 2001 From: PJ Reiniger Date: Sat, 20 Jun 2026 18:15:42 -0400 Subject: [PATCH 8/8] Fix timed robot test --- .../test/python/framework/test_timed_robot.py | 14 +-- wpilibc/src/test/python/system/test_timer.py | 104 ------------------ 2 files changed, 7 insertions(+), 111 deletions(-) delete mode 100644 wpilibc/src/test/python/system/test_timer.py diff --git a/wpilibc/src/test/python/framework/test_timed_robot.py b/wpilibc/src/test/python/framework/test_timed_robot.py index 81b66bb2248..870445f081d 100644 --- a/wpilibc/src/test/python/framework/test_timed_robot.py +++ b/wpilibc/src/test/python/framework/test_timed_robot.py @@ -11,7 +11,7 @@ stepTiming, waitForProgramStart, ) -from hal._wpiHal import _RobotMode +from hal import RobotMode _PERIOD = 0.02 # 20 ms @@ -140,7 +140,7 @@ def test_autonomous_mode(): waitForProgramStart() DriverStationSim.setEnabled(True) - DriverStationSim.setRobotMode(_RobotMode.AUTONOMOUS) + DriverStationSim.setRobotMode(RobotMode.AUTONOMOUS) DriverStationSim.notifyNewData() assert robot.simulation_init_count == 1 @@ -182,7 +182,7 @@ def test_teleop_mode(): waitForProgramStart() DriverStationSim.setEnabled(True) - DriverStationSim.setRobotMode(_RobotMode.TELEOPERATED) + DriverStationSim.setRobotMode(RobotMode.TELEOPERATED) DriverStationSim.notifyNewData() assert robot.simulation_init_count == 1 @@ -219,7 +219,7 @@ def test_utility_mode(): waitForProgramStart() DriverStationSim.setEnabled(True) - DriverStationSim.setRobotMode(_RobotMode.UTILITY) + DriverStationSim.setRobotMode(RobotMode.UTILITY) DriverStationSim.notifyNewData() assert robot.simulation_init_count == 1 @@ -268,7 +268,7 @@ def test_mode_change(): assert robot.disabled_exit_count == 0 DriverStationSim.setEnabled(True) - DriverStationSim.setRobotMode(_RobotMode.AUTONOMOUS) + DriverStationSim.setRobotMode(RobotMode.AUTONOMOUS) DriverStationSim.notifyNewData() stepTiming(_PERIOD) @@ -279,7 +279,7 @@ def test_mode_change(): assert robot.disabled_exit_count == 1 assert robot.autonomous_exit_count == 0 - DriverStationSim.setRobotMode(_RobotMode.TELEOPERATED) + DriverStationSim.setRobotMode(RobotMode.TELEOPERATED) DriverStationSim.notifyNewData() stepTiming(_PERIOD) @@ -289,7 +289,7 @@ def test_mode_change(): assert robot.autonomous_exit_count == 1 assert robot.teleop_exit_count == 0 - DriverStationSim.setRobotMode(_RobotMode.UTILITY) + DriverStationSim.setRobotMode(RobotMode.UTILITY) DriverStationSim.notifyNewData() stepTiming(_PERIOD) diff --git a/wpilibc/src/test/python/system/test_timer.py b/wpilibc/src/test/python/system/test_timer.py deleted file mode 100644 index c8cd048d630..00000000000 --- a/wpilibc/src/test/python/system/test_timer.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest - -from wpilib import Timer -from wpilib.simulation import pauseTiming, restartTiming, resumeTiming, stepTiming - - -@pytest.fixture(autouse=True) -def timer_setup(): - pauseTiming() - restartTiming() - yield - resumeTiming() - - -def test_start_stop(): - timer = Timer() - - # Stopped timer doesn't advance - assert timer.get() == pytest.approx(0.0) - assert not timer.isRunning() - stepTiming(0.5) - assert timer.get() == pytest.approx(0.0) - assert not timer.isRunning() - - # Running timer advances - timer.start() - stepTiming(0.5) - assert timer.get() == pytest.approx(0.5) - assert timer.isRunning() - - # Stopped timer freezes - timer.stop() - stepTiming(0.5) - assert timer.get() == pytest.approx(0.5) - assert not timer.isRunning() - - -def test_reset(): - timer = Timer() - timer.start() - - assert timer.get() == pytest.approx(0.0) - stepTiming(0.5) - assert timer.get() == pytest.approx(0.5) - - timer.reset() - assert timer.get() == pytest.approx(0.0) - - stepTiming(0.5) - assert timer.get() == pytest.approx(0.5) - - # Reset while stopped keeps timer stopped - timer.stop() - timer.reset() - stepTiming(0.5) - assert timer.get() == pytest.approx(0.0) - - -def test_has_elapsed(): - timer = Timer() - - # 0 s elapsed since timer hasn't started - assert timer.hasElapsed(0.0) - - # Stopped timer doesn't accumulate elapsed time - stepTiming(0.5) - assert not timer.hasElapsed(0.4) - - timer.start() - - stepTiming(0.5) - assert timer.hasElapsed(0.4) - assert timer.hasElapsed(0.4) - - -def test_advance_if_elapsed(): - timer = Timer() - - # 0 s has elapsed - assert timer.advanceIfElapsed(0.0) - - # Stopped timer doesn't accumulate elapsed time - stepTiming(0.5) - assert not timer.advanceIfElapsed(0.4) - - timer.start() - - # First call returns True, second returns False (advanced past threshold) - stepTiming(0.5) - assert timer.advanceIfElapsed(0.4) - assert not timer.advanceIfElapsed(0.4) - - # After 1 more second, two 400 ms periods have elapsed - stepTiming(1.0) - assert timer.advanceIfElapsed(0.4) - assert timer.advanceIfElapsed(0.4) - assert not timer.advanceIfElapsed(0.4) - - -def test_get_monotonic_timestamp(): - start = Timer.getMonotonicTimestamp() - stepTiming(0.5) - end = Timer.getMonotonicTimestamp() - assert (end - start) == pytest.approx(0.5)