diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c7d271178..8efd799e94 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -151,16 +151,15 @@ jobs: - uses: ./.github/actions/environment-setup - name: Simulated Test Run - # TODO (#2908): reenable once field tests can be ran as simulated - # //software/gameplay_tests/... \ run: | cd src bazel test //software/gameplay_tests:requirements_test bazel test --copt=-O3 --flaky_test_attempts=3 --show_timestamps \ - --test_arg="--ci_mode" \ - -- //software:unix_full_system \ + --test_arg="--ci_mode" \ + -- //software:unix_full_system \ -//software/gameplay_tests:requirements_test \ - //software/ai/hl/... \ + //software/simulation/... \ + //software/ai/hl/... \ //software/ai/navigator/... - name: Upload simulated test proto logs diff --git a/src/software/BUILD b/src/software/BUILD index d7e481d506..27110d2946 100644 --- a/src/software/BUILD +++ b/src/software/BUILD @@ -125,7 +125,7 @@ py_library( name = "conftest", srcs = ["conftest.py"], deps = [ - "//software/gameplay_tests:field_test_fixture", - "//software/gameplay_tests:simulated_test_fixture", + "//software/gameplay_tests:fixture", + "//software/gameplay_tests:util", ], ) diff --git a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py index ef4ffd84d5..44c7f51095 100644 --- a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py @@ -6,15 +6,12 @@ from proto.ssl_gc_common_pb2 import Team from proto.message_translation.tbots_protobuf import create_world_state from software.gameplay_tests.validation.ball_enters_region import ( - BallAlwaysStaysInRegion, BallEventuallyEntersRegion, ) from software.gameplay_tests.validation.robot_enters_region import ( RobotEventuallyExitsRegion, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -33,10 +30,10 @@ ], ) def test_two_ai_ball_placement( - simulated_test_runner, ball_start_point, ball_placement_point + gameplay_test_runner, ball_start_point, ball_placement_point ): run_ball_placement_scenario( - simulated_test_runner, ball_start_point, ball_placement_point + gameplay_test_runner, ball_start_point, ball_placement_point ) @@ -64,21 +61,21 @@ def test_two_ai_ball_placement( ], ) def test_robocup_technical_challenge_placement( - simulated_test_runner, ball_start_point, ball_placement_point + gameplay_test_runner, ball_start_point, ball_placement_point ): run_ball_placement_scenario( - simulated_test_runner, ball_start_point, ball_placement_point, blue_only=True + gameplay_test_runner, ball_start_point, ball_placement_point, blue_only=True ) def ball_placement_play_setup( - ball_start_point, ball_placement_point, simulated_test_runner, blue_only + ball_start_point, ball_placement_point, gameplay_test_runner, blue_only ): """Set up ball placement test by initializing bot positions, ball placement targets, and test settings :param ball_start_point: Initial point of the ball :param ball_placement_point: Target point of the ball - :param simulated_test_runner: Simulated test runner + :param gameplay_test_runner: Simulated test runner :param blue_only: If True, only the blue team is active; the yellow team is ignored. """ # Setup blue robots @@ -113,7 +110,7 @@ def ball_placement_play_setup( ] # Create world state - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -123,28 +120,28 @@ def ball_placement_play_setup( ) # Game Controller Setup - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) # Pass in placement point here - not required for all play tests - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.BALL_PLACEMENT, team=Team.BLUE, final_ball_placement_point=ball_placement_point, ) # Force play override here - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.BallPlacementPlay, yellow_play=PlayName.HaltPlay ) def run_ball_placement_scenario( - simulated_test_runner, ball_start_point, ball_placement_point, blue_only=False + gameplay_test_runner, ball_start_point, ball_placement_point, blue_only=False ): """Runs a ball placement test scenario with the specified parameters. - :param simulated_test_runner: The test runner used to simulate robot and ball behavior. + :param gameplay_test_runner: The test runner used to simulate robot and ball behavior. :param ball_start_point: The initial position of the ball. :param ball_placement_point: The target position where the ball should be placed. :param blue_only: If True, only the blue team is active; the yellow team is ignored. @@ -156,23 +153,9 @@ def run_ball_placement_scenario( BallEventuallyEntersRegion( regions=[tbots_cpp.Circle(ball_placement_point, 0.15)] ), - ] - ] - - # Drop Ball Always Validation - drop_ball_always_validation_sequence_set = [ - [ - BallAlwaysStaysInRegion( - regions=[tbots_cpp.Circle(ball_placement_point, 0.15)] - ), - ] - ] - - # Drop Ball Eventually Validation - # Non free kick after ball placement, the robot must be 0.5 away from the ball after the placement - # See detailed rules here: https://robocup-ssl.github.io/ssl-rules/sslrules.html#_ball_placement - drop_ball_eventually_validation_sequence_set = [ - [ + # Drop Ball Eventually Validation + # Non free kick after ball placement, the robot must be 0.5 away from the ball after the placement + # See detailed rules here: https://robocup-ssl.github.io/ssl-rules/sslrules.html#_ball_placement RobotEventuallyExitsRegion( regions=[ tbots_cpp.Circle( @@ -183,33 +166,25 @@ def run_ball_placement_scenario( ] ] - simulated_test_runner.run_test( - setup=lambda test_setup_arg: ball_placement_play_setup( - test_setup_arg["ball_start_point"], - test_setup_arg["ball_placement_point"], - simulated_test_runner, + # Make sure ball never moves after robot moves away + # TODO (#3503): Enable this with an OrValidation checking robot is moving back + # drop_ball_always_validation_sequence_set = [ + # [ + # BallAlwaysStaysInRegion( + # regions=[tbots_cpp.Circle(ball_placement_point, 0.15)] + # ), + # ] + # ] + + gameplay_test_runner.run_test( + setup=lambda: ball_placement_play_setup( + ball_start_point, + ball_placement_point, + gameplay_test_runner, blue_only, ), - params=[ - { - "ball_start_point": ball_start_point, - "ball_placement_point": ball_placement_point, - } - ], - inv_always_validation_sequence_set=[[]], - inv_eventually_validation_sequence_set=placement_eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=placement_eventually_validation_sequence_set, - test_timeout_s=[30], - ) - - simulated_test_runner.run_test( - # setup argument isn't passed to preserve world state from previous test run - inv_always_validation_sequence_set=drop_ball_always_validation_sequence_set, - inv_eventually_validation_sequence_set=drop_ball_eventually_validation_sequence_set, - ag_always_validation_sequence_set=drop_ball_always_validation_sequence_set, - ag_eventually_validation_sequence_set=drop_ball_eventually_validation_sequence_set, - test_timeout_s=[10], + eventually_validation_sequence_set=placement_eventually_validation_sequence_set, + test_timeout_s=30, ) diff --git a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py index 5514eaf6a5..ffe948dded 100644 --- a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py +++ b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py @@ -3,9 +3,7 @@ from proto.import_all_protos import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from software.gameplay_tests.validation.robot_speed_threshold import ( RobotSpeedEventuallyBelowThreshold, ) @@ -15,12 +13,12 @@ from software.gameplay_tests.validation.delay_validation import DelayValidation -def test_crease_defense_play(simulated_test_runner): +def test_crease_defense_play(gameplay_test_runner): field = tbots_cpp.Field.createSSLDivisionBField() goalie_position = tbots_cpp.Point(-4.5, 0) - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ goalie_position, @@ -43,11 +41,11 @@ def setup(*args): ), ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.CreaseDefensePlay, yellow_play=PlayName.HaltPlay ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -70,10 +68,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index be1366c15d..fdf47e947c 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -8,9 +8,7 @@ ) from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -35,11 +33,11 @@ ) ], ) -def test_defense_play_ball_steal(simulated_test_runner, blue_bots, yellow_bots): - def setup(*args): +def test_defense_play_ball_steal(gameplay_test_runner, blue_bots, yellow_bots): + def setup(): ball_initial_pos = tbots_cpp.Point(0.93, 0) - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -48,32 +46,29 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.DefensePlay, yellow_play=PlayName.HaltPlay ) - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - params=[0, 1, 2, 3, 4], # The aggregate test runs 5 times - inv_always_validation_sequence_set=[ + always_validation_sequence_set=[ [ BallNeverEntersRegion( regions=[tbots_cpp.Field.createSSLDivisionBField().friendlyGoal()] ) ] ], - inv_eventually_validation_sequence_set=[ + eventually_validation_sequence_set=[ [FriendlyEventuallyHasBallPossession(tolerance=0.2)] ], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], test_timeout_s=20, ) @@ -100,11 +95,11 @@ def setup(*args): ) ], ) -def test_defense_play(simulated_test_runner, blue_bots, yellow_bots): - def setup(*args): +def test_defense_play(gameplay_test_runner, blue_bots, yellow_bots): + def setup(): ball_initial_pos = tbots_cpp.Point(0.9, 2.85) - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -113,32 +108,29 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.DefensePlay, yellow_play=PlayName.ShootOrPassPlay ) - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - params=[0, 1, 2, 3, 4], # The aggregate test runs 5 times - inv_always_validation_sequence_set=[ + always_validation_sequence_set=[ [ BallNeverEntersRegion( regions=[tbots_cpp.Field.createSSLDivisionBField().friendlyGoal()] ) ] ], - inv_eventually_validation_sequence_set=[ + eventually_validation_sequence_set=[ [FriendlyEventuallyHasBallPossession(tolerance=0.1)] ], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], test_timeout_s=30, ) diff --git a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py index 391380ab37..315314286f 100644 --- a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py @@ -3,9 +3,7 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import PlayName from software.gameplay_tests.validation.robot_enters_placement_region import * -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team @@ -20,9 +18,9 @@ ], ) def test_two_ai_ball_placement( - simulated_test_runner, ball_start_point, ball_placement_point + gameplay_test_runner, ball_start_point, ball_placement_point ): - def setup(*args): + def setup(): blue_bots = [ tbots_cpp.Point(-4.5, 0), tbots_cpp.Point(-4, 0.5), @@ -45,7 +43,7 @@ def setup(*args): .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -54,16 +52,16 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.BALL_PLACEMENT, team=Team.YELLOW, final_ball_placement_point=ball_placement_point, ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.EnemyBallPlacementPlay, yellow_play=PlayName.BallPlacementPlay, ) @@ -72,13 +70,10 @@ def setup(*args): [RobotNeverEntersPlacementRegion(ball_placement_point)] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - params=[0], - inv_always_validation_sequence_set=always_validation_sequence_set, - inv_eventually_validation_sequence_set=[[]], - ag_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=[[]], + always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=[[]], test_timeout_s=15, ) diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index 644a80a224..4eab23d230 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -13,9 +13,7 @@ ) from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -97,10 +95,10 @@ "Disabling this test because OrValidation is passed both an always validation and eventually validation" ) def test_enemy_free_kick_play( - simulated_test_runner, blue_bots, yellow_bots, ball_initial_pos + gameplay_test_runner, blue_bots, yellow_bots, ball_initial_pos ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -109,14 +107,14 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.YELLOW ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.EnemyFreeKickPlay, yellow_play=PlayName.FreeKickPlay ) @@ -141,13 +139,10 @@ def setup(*args): [RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 1)])] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - params=[0, 1, 2], - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=8, ) diff --git a/src/software/ai/hl/stp/play/example/example_play_test.py b/src/software/ai/hl/stp/play/example/example_play_test.py index df4db68c1c..e980d4b4c9 100644 --- a/src/software/ai/hl/stp/play/example/example_play_test.py +++ b/src/software/ai/hl/stp/play/example/example_play_test.py @@ -7,15 +7,13 @@ from proto.ssl_gc_common_pb2 import Team from proto.play_pb2 import PlayName from proto.import_all_protos import Command -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main -def test_example_play(simulated_test_runner): +def test_example_play(gameplay_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) - def setup(*args): + def setup(): blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), @@ -38,7 +36,7 @@ def setup(*args): .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -47,17 +45,17 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.ExamplePlay, yellow_play=PlayName.HaltPlay ) @@ -72,10 +70,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py index 63da9d1fa9..115939945c 100644 --- a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py @@ -16,9 +16,7 @@ from software.gameplay_tests.validation.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -31,8 +29,8 @@ (tbots_cpp.Point(1.5, 0.5), True), ], ) -def test_free_kick_play_friendly(ball_initial_pos, must_score, simulated_test_runner): - def setup(*args): +def test_free_kick_play_friendly(ball_initial_pos, must_score, gameplay_test_runner): + def setup(): blue_bots = [ tbots_cpp.Point(-4.5, 0), tbots_cpp.Point(-3, 1.5), @@ -53,7 +51,7 @@ def setup(*args): .enemyDefenseArea() .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_bots, yellow_robot_locations=yellow_bots, @@ -62,17 +60,17 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.FreeKickPlay, yellow_play=PlayName.HaltPlay ) @@ -89,10 +87,9 @@ def setup(*args): ], ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py index 0584b9ca01..2ae6433d92 100644 --- a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py +++ b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py @@ -2,15 +2,13 @@ from software.gameplay_tests.validation.robot_speed_threshold import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main # TODO issue #2599 - Remove Duration parameter from test # @pytest.mark.parametrize("run_enemy_ai,test_duration", [(False, 20), (True, 20)]) -def test_halt_play(simulated_test_runner): - def setup(*args): +def test_halt_play(gameplay_test_runner): + def setup(): ball_initial_pos = tbots_cpp.Point(0, 0) blue_bots = [ @@ -35,7 +33,7 @@ def setup(*args): .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -44,26 +42,18 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.UNKNOWN ) - # params just have to be a list of length 1 to ensure the test runs at least once - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - params=[0], - inv_always_validation_sequence_set=[[]], - inv_eventually_validation_sequence_set=[ - [RobotSpeedEventuallyBelowThreshold(1e-3)] - ], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[ - [RobotSpeedEventuallyBelowThreshold(1e-3)] - ], - ci_cmd_with_delay=[ + always_validation_sequence_set=[[]], + eventually_validation_sequence_set=[[RobotSpeedEventuallyBelowThreshold(1e-3)]], + gc_cmd_with_delay=[ (3, Command.Type.HALT, Team.BLUE), (3, Command.Type.HALT, Team.YELLOW), ], diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py index 6af37ba5ce..c357beb96e 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py @@ -1,5 +1,3 @@ -import threading - import software.python_bindings as tbots_cpp from proto.play_pb2 import PlayName @@ -13,16 +11,14 @@ from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import Command from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main -def test_kickoff_enemy_play(simulated_test_runner): +def test_kickoff_enemy_play(gameplay_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): + def setup(): blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-2.8, 2.5), @@ -41,7 +37,7 @@ def setup(*args): field.enemyDefenseArea().negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_bots, yellow_robot_locations=yellow_bots, @@ -50,22 +46,14 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.KICKOFF, team=Team.YELLOW ) - # Let robots get ready before starting kickoff - threading.Timer( - 4.0, - lambda: simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.NORMAL_START, team=Team.YELLOW - ), - ).start() - - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.KickoffEnemyPlay, yellow_play=PlayName.KickoffFriendlyPlay, ) @@ -138,13 +126,12 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, + gc_cmd_with_delay=[(4, Command.Type.NORMAL_START, Team.YELLOW)], ) diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py index 6ea8c4608e..e067c94bf2 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py @@ -1,5 +1,3 @@ -import threading - import software.python_bindings as tbots_cpp from proto.play_pb2 import PlayName @@ -17,15 +15,13 @@ from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from proto.import_all_protos import Command -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main -def test_kickoff_friendly_play(simulated_test_runner): +def test_kickoff_friendly_play(gameplay_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) - def setup(*args): + def setup(): field = tbots_cpp.Field.createSSLDivisionBField() blue_bots = [ @@ -46,7 +42,7 @@ def setup(*args): field.enemyDefenseArea().negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_bots, yellow_robot_locations=yellow_bots, @@ -55,22 +51,14 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.KICKOFF, team=Team.BLUE ) - # Let robots get ready before starting kickoff - threading.Timer( - 4.0, - lambda: simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.NORMAL_START, team=Team.BLUE - ), - ).start() - - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.KickoffFriendlyPlay, yellow_play=PlayName.KickoffEnemyPlay, ) @@ -138,13 +126,12 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=60, + gc_cmd_with_delay=[(4, Command.Type.NORMAL_START, Team.BLUE)], ) diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 5a5cd6ae3f..c024286e9b 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -7,17 +7,15 @@ from proto.import_all_protos import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from software.gameplay_tests.validation.or_validation import OrValidation @pytest.mark.parametrize("is_friendly_test", [True, False]) -def test_kickoff_play(simulated_test_runner, is_friendly_test): +def test_kickoff_play(gameplay_test_runner, is_friendly_test): ball_initial_pos = tbots_cpp.Point(0, 0) - def setup(*args): + def setup(): blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), @@ -40,7 +38,7 @@ def setup(*args): .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -49,28 +47,28 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) if is_friendly_test: - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.KICKOFF, team=Team.BLUE ) blue_play = PlayName.KickoffFriendlyPlay yellow_play = PlayName.KickoffEnemyPlay else: - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.KICKOFF, team=Team.YELLOW ) blue_play = PlayName.KickoffEnemyPlay yellow_play = PlayName.KickoffFriendlyPlay - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - simulated_test_runner.set_plays(blue_play=blue_play, yellow_play=yellow_play) + gameplay_test_runner.set_plays(blue_play=blue_play, yellow_play=yellow_play) # TODO (#3650): fix validation logic @@ -136,10 +134,10 @@ def setup(*args): ) ) - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 7d8a7e1990..630932d6b4 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -2,19 +2,16 @@ from proto.play_pb2 import PlayName from software.gameplay_tests.validation.friendly_team_scored import * from software.gameplay_tests.validation.ball_enters_region import * -from software.gameplay_tests.validation.friendly_has_ball_possession import * from software.gameplay_tests.validation.excessive_dribbling import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main -def test_offense_play(simulated_test_runner): - def setup(start_point): - ball_initial_pos = start_point +def test_offense_play(gameplay_test_runner): + start_point = tbots_cpp.Point(-4.4, 2.9) + def setup(): blue_bots = [ tbots_cpp.Point(-4.5, 3.0), tbots_cpp.Point(-2, 1.5), @@ -37,49 +34,44 @@ def setup(start_point): .negXPosYCorner(), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( - yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, + yellow_robot_locations=yellow_bots, + ball_location=start_point, ball_velocity=tbots_cpp.Vector(0, 0), ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.OffensePlay, yellow_play=PlayName.HaltPlay ) field = tbots_cpp.Field.createSSLDivisionBField() # Always Validation - inv_always_validation_sequence_set = [ + always_validation_sequence_set = [ [BallAlwaysStaysInRegion(regions=[field.fieldBoundary()])], [NeverExcessivelyDribbles()], ] - ag_always_validation_sequence_set = [[FriendlyAlwaysHasBallPossession()]] - # Eventually Validation - inv_eventually_validation_sequence_set = [[]] - ag_eventually_validation_sequence_set = [[FriendlyTeamEventuallyScored()]] + eventually_validation_sequence_set = [ + [FriendlyTeamEventuallyScored()], + ] - simulated_test_runner.run_test( - params=[tbots_cpp.Point(-4.4, 2.9)], + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=inv_eventually_validation_sequence_set, - inv_always_validation_sequence_set=inv_always_validation_sequence_set, - ag_eventually_validation_sequence_set=ag_eventually_validation_sequence_set, - ag_always_validation_sequence_set=ag_always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=15, - run_till_end=True, ) diff --git a/src/software/ai/hl/stp/play/passing_sim_test.py b/src/software/ai/hl/stp/play/passing_sim_test.py index 5326ae407a..bf9f9a44b0 100644 --- a/src/software/ai/hl/stp/play/passing_sim_test.py +++ b/src/software/ai/hl/stp/play/passing_sim_test.py @@ -1,9 +1,7 @@ import pytest import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state from software.gameplay_tests.validation.friendly_receives_ball_slow import ( FriendlyAlwaysReceivesBallSlow, @@ -20,42 +18,12 @@ ) -def setup_pass_and_robots( +def calculate_best_pass( ball_initial_position, ball_initial_velocity, - attacker_robot_position, - receiver_robot_positions, - friendly_orientations, - enemy_robot_positions, - receive_pass, - simulated_test_runner, + blue_robot_locations, + yellow_robot_locations, ): - """Sets up a test involving 1 robot passing the ball - With any number of friendly and enemy robots on the field - Can specify if the first friendly robot should receive the pass or not - :param ball_initial_position: the initial position of the ball - :param ball_initial_velocity: the initial velocity of the ball - :param attacker_robot_position: the position of the robot doing the pass - :param receiver_robot_positions: the positions of the friendly robots - :param friendly_orientations: the orientations of the friendly robots - :param enemy_robot_positions: the positions of the enemy robots - :param receive_pass: whether a friendly robot should try to receive the pass - :param simulated_test_runner: the test runner - :return: the best pass we generate - """ - blue_robot_locations = [attacker_robot_position, *receiver_robot_positions] - - # Setup the world state - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=enemy_robot_positions, - blue_robot_locations=blue_robot_locations, - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - blue_robot_orientations=friendly_orientations, - ), - ) - # construct a world object to match the one sent to the test runner world = tbots_cpp.World( tbots_cpp.Field.createSSLDivisionBField(), @@ -85,7 +53,7 @@ def setup_pass_and_robots( tbots_cpp.Angle(), tbots_cpp.Timestamp(), ) - for index, location in enumerate(enemy_robot_positions) + for index, location in enumerate(yellow_robot_locations) ] ), ) @@ -104,6 +72,51 @@ def setup_pass_and_robots( best_pass_with_score = pass_generator.getBestPass(world, robots_to_ignore) best_pass = best_pass_with_score.pass_value + return best_pass + + +def setup_pass_and_robots( + ball_initial_position, + ball_initial_velocity, + attacker_robot_position, + receiver_robot_positions, + friendly_orientations, + enemy_robot_positions, + receive_pass, + gameplay_test_runner, +): + """Sets up a test involving 1 robot passing the ball + With any number of friendly and enemy robots on the field + Can specify if the first friendly robot should receive the pass or not + :param ball_initial_position: the initial position of the ball + :param ball_initial_velocity: the initial velocity of the ball + :param attacker_robot_position: the position of the robot doing the pass + :param receiver_robot_positions: the positions of the friendly robots + :param friendly_orientations: the orientations of the friendly robots + :param enemy_robot_positions: the positions of the enemy robots + :param receive_pass: whether a friendly robot should try to receive the pass + :param gameplay_test_runner: the test runner + :return: the best pass we generate + """ + blue_robot_locations = [attacker_robot_position, *receiver_robot_positions] + + # Setup the world state + gameplay_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=enemy_robot_positions, + blue_robot_locations=blue_robot_locations, + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + blue_robot_orientations=friendly_orientations, + ), + ) + + best_pass = calculate_best_pass( + ball_initial_position, + ball_initial_velocity, + blue_robot_locations, + enemy_robot_positions, + ) kick_vec = best_pass.receiverPoint() - best_pass.passerPoint() # Setup the passer's tactic @@ -139,7 +152,7 @@ def setup_pass_and_robots( blue_tactics[1] = ReceiverTactic(**receiver_args) - simulated_test_runner.set_tactics(blue_tactics=blue_tactics, yellow_tactics=None) + gameplay_test_runner.set_tactics(blue_tactics=blue_tactics, yellow_tactics=None) return best_pass @@ -227,7 +240,7 @@ def test_passing_receive_speed( receiver_robot_positions, friendly_orientations, enemy_robot_positions, - simulated_test_runner, + gameplay_test_runner, ): # Eventually Validation eventually_validation_sequence_set = [ @@ -245,22 +258,20 @@ def test_passing_receive_speed( [FriendlyAlwaysReceivesBallSlow(robot_id=1, max_receive_speed=2.1)], ] - simulated_test_runner.run_test( - setup=lambda param: setup_pass_and_robots( + gameplay_test_runner.run_test( + setup=lambda: setup_pass_and_robots( ball_initial_position=ball_initial_position, ball_initial_velocity=ball_initial_velocity, attacker_robot_position=attacker_robot_position, receiver_robot_positions=receiver_robot_positions, friendly_orientations=friendly_orientations, enemy_robot_positions=enemy_robot_positions, - simulated_test_runner=simulated_test_runner, + gameplay_test_runner=gameplay_test_runner, receive_pass=True, ), - params=[0], - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, - run_till_end=False, ) @@ -342,20 +353,29 @@ def test_passing_no_backwards_passes( receiver_robot_positions, friendly_orientations, enemy_robot_positions, - simulated_test_runner, + gameplay_test_runner, ): - field = tbots_cpp.Field.createSSLDivisionBField() - best_pass = setup_pass_and_robots( - ball_initial_position=ball_initial_position, - ball_initial_velocity=ball_initial_velocity, - attacker_robot_position=attacker_robot_position, - receiver_robot_positions=receiver_robot_positions, - friendly_orientations=friendly_orientations, - enemy_robot_positions=enemy_robot_positions, - receive_pass=True, - simulated_test_runner=simulated_test_runner, + best_pass = calculate_best_pass( + ball_initial_position, + ball_initial_velocity, + [attacker_robot_position, *receiver_robot_positions], + enemy_robot_positions, ) + def setup(): + setup_pass_and_robots( + ball_initial_position=ball_initial_position, + ball_initial_velocity=ball_initial_velocity, + attacker_robot_position=attacker_robot_position, + receiver_robot_positions=receiver_robot_positions, + friendly_orientations=friendly_orientations, + enemy_robot_positions=enemy_robot_positions, + receive_pass=True, + gameplay_test_runner=gameplay_test_runner, + ) + + field = tbots_cpp.Field.createSSLDivisionBField() + # Eventually Validation eventually_validation_sequence_set = [ [ @@ -381,11 +401,11 @@ def test_passing_no_backwards_passes( ], ] - simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + gameplay_test_runner.run_test( + setup=setup, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, - run_till_end=False, ) diff --git a/src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.py b/src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.py index 77a8b74807..62fca09a07 100644 --- a/src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.py +++ b/src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.py @@ -16,9 +16,7 @@ from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import Command from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -97,13 +95,13 @@ def test_penalty_kick_enemy_play_setup( friendly_robot_positions, enemy_distance_behind_ball, - simulated_test_runner, + gameplay_test_runner, ): field = tbots_cpp.Field.createSSLDivisionBField() ball_initial_pos = field.enemyPenaltyMark() enemy_penalty_x = ball_initial_pos.x() - def setup(*args): + def setup(): # Enemy robots behind the penalty mark yellow_bots = [ tbots_cpp.Point(enemy_penalty_x + 0.3, 0), # kicker robot @@ -126,7 +124,7 @@ def setup(*args): ), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=friendly_robot_positions, yellow_robot_locations=yellow_bots, @@ -135,14 +133,14 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.HALT, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.PENALTY, team=Team.YELLOW ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.PenaltyKickEnemyPlay, yellow_play=PlayName.HaltPlay, ) @@ -171,10 +169,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=20, ) diff --git a/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py index 77a7b3c574..97bb09c0bb 100644 --- a/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py +++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py @@ -3,17 +3,15 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import PlayName -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.import_all_protos import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from proto.geometry_pb2 import Point, Vector, Angle, AngularVelocity -def test_shoot_or_chip_play(simulated_test_runner): - def setup(*args): +def test_shoot_or_chip_play(gameplay_test_runner): + def setup(): ball_initial_pos = tbots_cpp.Point(-1.4, 2) ball_initial_vel = tbots_cpp.Vector(0, 0) @@ -48,24 +46,23 @@ def setup(*args): world_state.yellow_robots[5].CopyFrom(last_robot) - simulated_test_runner.set_world_state(world_state) + gameplay_test_runner.set_world_state(world_state) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.ShootOrChipPlay, yellow_play=PlayName.HaltPlay ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) # TODO (#3651): add validations - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], + eventually_validation_sequence_set=[[]], test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py b/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py index 8f0105f3e6..b5558a95a1 100644 --- a/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py +++ b/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py @@ -8,17 +8,17 @@ from software.gameplay_tests.validation.friendly_team_scored import ( FriendlyTeamEventuallyScored, ) -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main @pytest.mark.skip( "Skipping test. TODO (#3233): attacker robot sometimes doesn't kick the ball towards the receiver" ) -def test_shoot_or_pass_play(simulated_test_runner): +def test_shoot_or_pass_play(gameplay_test_runner): field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ field.friendlyGoalCenter(), @@ -41,26 +41,24 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.ShootOrPassPlay, yellow_play=PlayName.HaltPlay ) # Eventually Validation eventually_validations = [[FriendlyTeamEventuallyScored()]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=15, - run_till_end=False, ) diff --git a/src/software/ai/hl/stp/play/stop_play_test.py b/src/software/ai/hl/stp/play/stop_play_test.py index c7ebcc57ba..5eee0982cd 100644 --- a/src/software/ai/hl/stp/play/stop_play_test.py +++ b/src/software/ai/hl/stp/play/stop_play_test.py @@ -12,7 +12,7 @@ RobotNeverEntersRegion, ) from software.gameplay_tests.validation.delay_validation import DelayValidation -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -117,11 +117,11 @@ # ), ], ) -def test_stop_play(ball_position, blue_robot_positions, simulated_test_runner): +def test_stop_play(ball_position, blue_robot_positions, gameplay_test_runner): field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_robot_positions, yellow_robot_locations=[ @@ -137,11 +137,11 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.StopPlay, yellow_play=PlayName.HaltPlay ) @@ -163,10 +163,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_always_validation_sequence_set=always_stop_play_rules, - ag_always_validation_sequence_set=always_stop_play_rules, + always_validation_sequence_set=always_stop_play_rules, test_timeout_s=6, ) diff --git a/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py b/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py index 13b91dd495..405ab78758 100644 --- a/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py @@ -19,7 +19,7 @@ from software.gameplay_tests.validation.robot_at_position import ( RobotEventuallyAtPosition, ) -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main def calculate_ball_velocity(passer_point, receiver_point, speed): @@ -119,10 +119,10 @@ def test_attacker_passing( robot_pos, ball_pos, ball_velocity, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -140,7 +140,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: AttackerTactic( best_pass_so_far=Pass( @@ -168,11 +168,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - run_till_end=False, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=7, ) @@ -221,7 +219,7 @@ def test_attacker_keep_away( ball_velocity, enemy_positions, ignore_score_checks, - simulated_test_runner, + gameplay_test_runner, ): # TODO (#3638): Port C++ validation functions that don't exist in Python yet # In C++ test: @@ -233,7 +231,7 @@ def test_attacker_keep_away( field_top_left = field.fieldLines().negXPosYCorner() # Keep away near field corner (second test case from C++) - def setup(*args): + def setup(): robot_pos = field_top_left ball_pos = tbots_cpp.Point(field_top_left.x() + 0.05, field_top_left.y() - 0.2) enemy_positions = [ @@ -243,7 +241,7 @@ def setup(*args): tbots_cpp.Point(-4, 2.75), ] - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -260,7 +258,7 @@ def setup(*args): ) receiver_point = tbots_cpp.Point(0, 0) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: AttackerTactic( best_pass_so_far=Pass( @@ -279,10 +277,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) @@ -350,12 +347,12 @@ def setup(*args): ], ) def test_attacker_shoot_goal( - ball_pos, ball_velocity, robot_pos, enemy_positions, simulated_test_runner + ball_pos, ball_velocity, robot_pos, enemy_positions, gameplay_test_runner ): field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[robot_pos], yellow_robot_locations=enemy_positions, @@ -364,7 +361,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: AttackerTactic( chip_target=tbots_cpp.createPointProto( @@ -380,11 +377,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - run_till_end=False, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=9, ) diff --git a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py index de41738ba1..8880ac87fb 100644 --- a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py @@ -9,9 +9,7 @@ from software.gameplay_tests.validation.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import ChipTactic @@ -48,12 +46,12 @@ ), ], ) -def test_chip(ball_offset_from_robot, angle_to_chip_at, simulated_test_runner): +def test_chip(ball_offset_from_robot, angle_to_chip_at, gameplay_test_runner): robot_position = tbots_cpp.Point(0, 0) ball_position = robot_position + ball_offset_from_robot - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -65,7 +63,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: ChipTactic( chip_origin=tbots_cpp.createPointProto(ball_position), @@ -84,10 +82,9 @@ def setup(*args): ], ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, ) diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index b09aee57d7..e54397ef1a 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -23,16 +23,14 @@ RobotEventuallyEntersRegion, RobotNeverEntersRegion, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main -def test_not_bumping_ball_towards_net(simulated_test_runner): +def test_not_bumping_ball_towards_net(gameplay_test_runner): enemy_threat_point = tbots_cpp.Point(3, 0) - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[tbots_cpp.Point(0, 0)], yellow_robot_locations=[tbots_cpp.Point(4, 0)], @@ -41,7 +39,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), @@ -52,10 +50,9 @@ def setup(*args): always_validations = [[BallSpeedAlwaysBelowThreshold(0.001)]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_always_validation_sequence_set=always_validations, - ag_always_validation_sequence_set=always_validations, + always_validation_sequence_set=always_validations, ) @@ -79,12 +76,12 @@ def setup(*args): ], ) def test_crease_region_positioning( - enemy_threat_point, crease_alignment, region_index, simulated_test_runner + enemy_threat_point, crease_alignment, region_index, gameplay_test_runner ): field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[tbots_cpp.Point(-3, 1.5)], yellow_robot_locations=[ @@ -100,7 +97,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), @@ -174,10 +171,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=5, ) @@ -211,10 +207,10 @@ def test_crease_positioning( yellow_bots, ball_initial_pos, ball_initial_velocity, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], @@ -223,7 +219,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -259,12 +255,10 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=4, ) @@ -321,10 +315,10 @@ def test_crease_autochip( ball_initial_pos, ball_initial_velocity, should_chip, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], @@ -333,7 +327,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -356,12 +350,10 @@ def setup(*args): always_validation_sequence_set = [[]] eventually_validation_sequence_set = [[BallIsEventuallyOffGround()]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=3, ) @@ -393,10 +385,10 @@ def test_crease_get_ball( ball_initial_pos, ball_initial_velocity, should_dribble, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], @@ -405,7 +397,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -437,12 +429,10 @@ def setup(*args): RobotNeverEntersRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 0.2)]) ) - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) diff --git a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py index aa4559ef30..79c2af67de 100644 --- a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py @@ -21,9 +21,7 @@ from software.gameplay_tests.validation.delay_validation import ( DelayValidation, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main def get_enemy_robot_positions(): @@ -129,10 +127,10 @@ def test_dribble( dribble_orientation, ball_pos, ball_vel, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -156,7 +154,7 @@ def setup(*args): tbots_cpp.createAngleProto(dribble_orientation) ) - simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) + gameplay_test_runner.set_tactics(blue_tactics={1: dribble_params}) eventually_validations = [[RobotEventuallyReceivedBall(robot_id=1)]] @@ -177,12 +175,10 @@ def setup(*args): # TODO (#2514): tune dribbling and re-enable # Robot always not excessively dribbling - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=25, - run_till_end=False, ) @@ -296,11 +292,11 @@ def test_excessive_dribbling_without_enemies( dribble_destination, final_dribble_orientation, should_excessively_dribble, - simulated_test_runner, + gameplay_test_runner, blue_robot_location, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_robot_location], yellow_robot_locations=[], @@ -309,7 +305,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), @@ -330,22 +326,20 @@ def setup(*args): always_validation_sequence_set = [[NeverExcessivelyDribbles()]] eventually_validation_sequence_set = [[]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) -def test_dribble_with_excessive_dribbling(simulated_test_runner): +def test_dribble_with_excessive_dribbling(gameplay_test_runner): dribble_destination = tbots_cpp.Point(3, 2) initial_position = tbots_cpp.Point(4.5, -3.0) dribble_orientation = tbots_cpp.Angle.half() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -357,7 +351,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), @@ -378,16 +372,15 @@ def setup(*args): [EventuallyStartsExcessivelyDribbling()], ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=10, ) def test_run_into_enemy_robot_knock_ball_away( - simulated_test_runner, + gameplay_test_runner, ): initial_position = tbots_cpp.Point(-2, 1.5) dribble_destination = tbots_cpp.Point(-1, 2) @@ -395,8 +388,8 @@ def test_run_into_enemy_robot_knock_ball_away( ball_pos = tbots_cpp.Point(2, -2) ball_vel = tbots_cpp.Vector(2, 4) - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -415,7 +408,7 @@ def setup(*args): final_dribble_orientation=tbots_cpp.createAngleProto(dribble_orientation), ) - simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) + gameplay_test_runner.set_tactics(blue_tactics={1: dribble_params}) eventually_validations = [ [ @@ -429,24 +422,23 @@ def setup(*args): # TODO (#2514): tune dribbling and re-enable # Robot always not excessively dribbling - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, test_timeout_s=10, ) def test_robot_not_bumping_ball_when_turning( - simulated_test_runner, + gameplay_test_runner, ): # The ball is placed right behind the friendly robot. Verify that the robot # does not bump the ball away when turning around to dribble it. robot_location = tbots_cpp.Point(-1, 0) ball_location = robot_location + tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0) - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[tbots_cpp.Point(-3, 2.5), robot_location], blue_robot_orientations=[ @@ -461,7 +453,7 @@ def setup(*args): ) dribble_params = DribbleTactic() - simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) + gameplay_test_runner.set_tactics(blue_tactics={1: dribble_params}) eventually_validations = [ [ @@ -475,12 +467,10 @@ def setup(*args): [BallAlwaysStaysInRegion([tbots_cpp.Circle(ball_location, 0.05)])] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, - inv_always_validation_sequence_set=always_validations, - ag_always_validation_sequence_set=always_validations, + eventually_validation_sequence_set=eventually_validations, + always_validation_sequence_set=always_validations, ) diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index b663a152b5..7a320f95b1 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -9,9 +9,7 @@ from software.gameplay_tests.validation.ball_speed_threshold import * from software.gameplay_tests.validation.robot_speed_threshold import * from software.gameplay_tests.validation.excessive_dribbling import * -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state @@ -112,10 +110,10 @@ def test_goalie_blocks_shot( ball_initial_position, ball_initial_velocity, robot_initial_position, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[robot_initial_position], @@ -124,7 +122,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: GoalieTactic( max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT @@ -155,12 +153,10 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) @@ -189,10 +185,10 @@ def setup(*args): def test_goalie_clears_from_dead_zone( ball_position, should_clear, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[ @@ -205,7 +201,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: GoalieTactic( max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT @@ -238,13 +234,11 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, test_timeout_s=8, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) diff --git a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py index 79b043b508..563df6975e 100644 --- a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py @@ -9,9 +9,7 @@ RobotSpeedEventuallyBelowThreshold, ) from software.gameplay_tests.validation.delay_validation import DelayValidation -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -31,9 +29,9 @@ ), ], ) -def test_robot_halt(blue_robot_locations, blue_robot_velocities, simulated_test_runner): - def setup(*args): - simulated_test_runner.set_world_state( +def test_robot_halt(blue_robot_locations, blue_robot_velocities, gameplay_test_runner): + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_robot_locations, blue_robot_velocities=blue_robot_velocities, @@ -43,7 +41,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics(blue_tactics={1: HaltTactic()}) + gameplay_test_runner.set_tactics(blue_tactics={1: HaltTactic()}) robot_stopped_validation = RobotSpeedEventuallyBelowThreshold(speed_threshold=0.001) @@ -61,10 +59,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, ) diff --git a/src/software/ai/hl/stp/tactic/kick/BUILD b/src/software/ai/hl/stp/tactic/kick/BUILD index 7d941081cd..a8e8feb39a 100644 --- a/src/software/ai/hl/stp/tactic/kick/BUILD +++ b/src/software/ai/hl/stp/tactic/kick/BUILD @@ -40,3 +40,15 @@ py_test( requirement("pytest"), ], ) + +py_test( + name = "passing_field_test", + srcs = [ + "passing_field_test.py", + ], + deps = [ + "//software:conftest", + "//software/gameplay_tests/validation:validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index 29a4a4c514..886bf0d711 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -7,9 +7,7 @@ from software.gameplay_tests.validation.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -38,12 +36,12 @@ (tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), tbots_cpp.Angle.zero()), ], ) -def test_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): +def test_kick(ball_offset_from_robot, angle_to_kick_at, gameplay_test_runner): robot_position = tbots_cpp.Point(0, 0) ball_position = robot_position + ball_offset_from_robot - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -55,7 +53,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: KickTactic( kick_origin=tbots_cpp.createPointProto(ball_position), @@ -71,10 +69,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validations, - ag_eventually_validation_sequence_set=eventually_validations, + eventually_validation_sequence_set=eventually_validations, ) diff --git a/src/software/ai/hl/stp/tactic/kick/passing_field_test.py b/src/software/ai/hl/stp/tactic/kick/passing_field_test.py new file mode 100644 index 0000000000..37a5e59938 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/kick/passing_field_test.py @@ -0,0 +1,94 @@ +import software.python_bindings as tbots_cpp + +from proto.import_all_protos import * +from proto.message_translation.tbots_protobuf import create_world_state +from software.gameplay_tests.util import pytest_main +from software.gameplay_tests.validation.friendly_receives_ball_slow import ( + FriendlyAlwaysReceivesBallSlow, +) + + +def test_passing(gameplay_test_runner): + passer_robot_id = 0 + receiver_robot_id = 1 + + should_receive_pass = True + passer_point = tbots_cpp.Point(0, 0) + receiver_point = tbots_cpp.Point(2, 0) + + def setup(): + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(1, 1), receiver_point], + yellow_robot_locations=[], + ball_location=passer_point, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + receive_speed_m_per_s = 2.0 + min_pass_speed_m_per_s = 1.0 + max_pass_speed_m_per_s = 4.0 + + pass_to_test = tbots_cpp.Pass.fromDestReceiveSpeed( + passer_point, + receiver_point, + receive_speed_m_per_s, + min_pass_speed_m_per_s, + max_pass_speed_m_per_s, + ) + + kick_vec = pass_to_test.receiverPoint() - pass_to_test.passerPoint() + + # Setup the passer's tactic + # We use KickTactic since AttackerTactic shoots towards the goal instead if open + # KickTactic just does the kick we want + blue_tactics = { + passer_robot_id: KickTactic( + kick_origin=Point( + x_meters=pass_to_test.passerPoint().x(), + y_meters=pass_to_test.passerPoint().y(), + ), + kick_direction=Angle(radians=kick_vec.orientation().toRadians()), + kick_speed_meters_per_second=pass_to_test.speed(), + ) + } + + # if we want a friendly robot to receive the pass + if should_receive_pass: + # arguments for a ReceiverTactic + receiver_args = { + "pass": Pass( + passer_point=tbots_cpp.createPointProto(pass_to_test.passerPoint()), + receiver_point=tbots_cpp.createPointProto( + pass_to_test.receiverPoint() + ), + pass_speed_m_per_s=pass_to_test.speed(), + ), + "disable_one_touch_shot": True, + } + + blue_tactics[receiver_robot_id] = ReceiverTactic(**receiver_args) + + gameplay_test_runner.set_tactics(blue_tactics=blue_tactics) + + # Validate that the ball is always received by the other robot + # slower than the max receive speed + # and also that the ball is not passed backwards over long distances + always_validation_sequence_set = [ + [ + FriendlyAlwaysReceivesBallSlow( + robot_id=receiver_robot_id, max_receive_speed=2.5 + ) + ] + ] + + gameplay_test_runner.run_test( + setup=setup, + always_validation_sequence_set=always_validation_sequence_set, + test_timeout_s=5, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/move/BUILD b/src/software/ai/hl/stp/tactic/move/BUILD index ad65d4701e..0b62e74a93 100644 --- a/src/software/ai/hl/stp/tactic/move/BUILD +++ b/src/software/ai/hl/stp/tactic/move/BUILD @@ -39,3 +39,15 @@ py_test( requirement("pytest"), ], ) + +py_test( + name = "move_tactic_field_test", + srcs = [ + "move_tactic_field_test.py", + ], + deps = [ + "//software:conftest", + "//software/gameplay_tests/validation:validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_field_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_field_test.py new file mode 100644 index 0000000000..9796815790 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_field_test.py @@ -0,0 +1,138 @@ +import math + +import pytest +import software.python_bindings as tbots_cpp + +from proto.import_all_protos import * +from proto.message_translation.tbots_protobuf import create_world_state +from software.gameplay_tests.util import pytest_main +from software.gameplay_tests.validation.duration_validation import DurationValidation +from software.gameplay_tests.validation.robot_at_orientation import ( + RobotEventuallyAtOrientation, +) +from software.gameplay_tests.validation.robot_enters_region import ( + RobotEventuallyEntersRegion, +) + + +@pytest.mark.parametrize( + "angle", + [0, 45, 90, 180, 270, 360], +) +def test_basic_rotation(angle, gameplay_test_runner): + target_angle = tbots_cpp.Angle.fromDegrees(angle) + start_position = tbots_cpp.Point(-1.5, 0.6) + robot_id = 2 + + def setup(): + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Point(0.0, 1.0), + start_position, + ], + yellow_robot_locations=[], + ball_location=tbots_cpp.Point(0, 0), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + move_tactic = MoveTactic( + destination=tbots_cpp.createPointProto(start_position), + dribbler_mode=DribblerMode.OFF, + final_orientation=tbots_cpp.createAngleProto(target_angle), + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + + gameplay_test_runner.set_tactics( + blue_tactics={ + robot_id: move_tactic, + }, + ) + + gameplay_test_runner.run_test( + setup=setup, + test_timeout_s=5, + eventually_validation_sequence_set=[ + [ + DurationValidation( + duration_s=1, + validation=RobotEventuallyAtOrientation(robot_id, target_angle), + ), + ], + ], + ) + + +@pytest.mark.parametrize( + "start_position, end_position", + [ + ( + tbots_cpp.Point(-1.5, 0.6), + tbots_cpp.Point(-0.3, 0.6), + ), + ( + tbots_cpp.Point(-0.3, 0.6), + tbots_cpp.Point(-0.3, -0.6), + ), + ( + tbots_cpp.Point(-0.3, -0.6), + tbots_cpp.Point(-1.5, -0.6), + ), + ( + tbots_cpp.Point(-1.5, -0.6), + tbots_cpp.Point(-1.5, 0.6), + ), + ], +) +def test_one_robots_square(start_position, end_position, gameplay_test_runner): + def setup(): + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + start_position, + ], + yellow_robot_locations=[], + ball_location=tbots_cpp.Point(0, 0), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + tactic = MoveTactic( + destination=tbots_cpp.createPointProto(end_position), + dribbler_mode=DribblerMode.OFF, + final_orientation=Angle(radians=-math.pi / 2), + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + + gameplay_test_runner.set_tactics( + blue_tactics={ + 0: tactic, + }, + ) + + gameplay_test_runner.run_test( + setup=setup, + test_timeout_s=4, + eventually_validation_sequence_set=[ + [ + DurationValidation( + duration_s=1, + validation=RobotEventuallyEntersRegion( + regions=[tbots_cpp.Circle(end_position, 0.05)] + ), + ), + ] + ], + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index d0dca6aa1e..aebb1bb6a2 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -17,20 +17,18 @@ RobotEventuallyAtAngularVelocity, ) from software.gameplay_tests.validation.duration_validation import DurationValidation -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import * -def test_move_across_field(simulated_test_runner): +def test_move_across_field(gameplay_test_runner): initial_position = tbots_cpp.Point(-3, 1.5) destination = tbots_cpp.Point(2.5, -1.1) field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -49,7 +47,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: MoveTactic( destination=tbots_cpp.createPointProto(destination), @@ -69,21 +67,20 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=5, ) -def test_autochip_move(simulated_test_runner): +def test_autochip_move(gameplay_test_runner): initial_position = tbots_cpp.Point(-3, 1.5) destination = tbots_cpp.Point(0, 1.5) field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -102,7 +99,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: MoveTactic( destination=tbots_cpp.createPointProto(destination), @@ -131,21 +128,20 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=10, ) -def test_autokick_move(simulated_test_runner): +def test_autokick_move(gameplay_test_runner): initial_position = tbots_cpp.Point(-1, -0.5) destination = tbots_cpp.Point(-1, -1) field = tbots_cpp.Field.createSSLDivisionBField() - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[initial_position], blue_robot_orientations=[tbots_cpp.Angle.threeQuarter()], @@ -162,7 +158,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: MoveTactic( destination=tbots_cpp.createPointProto(destination), @@ -190,10 +186,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=5, ) @@ -246,10 +241,10 @@ def setup(*args): ], ) def test_spinning_move( - orientation, initial_position, destination, angular_velocity, simulated_test_runner + orientation, initial_position, destination, angular_velocity, gameplay_test_runner ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[initial_position], yellow_robot_locations=[tbots_cpp.Point(4, 0)], @@ -258,7 +253,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: MoveTactic( destination=tbots_cpp.createPointProto(destination), @@ -290,20 +285,19 @@ def setup(*args): ], ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=5, ) -def test_move_across_x_axis(simulated_test_runner): +def test_move_across_x_axis(gameplay_test_runner): initial_position = tbots_cpp.Point(-4.4, 0) destination = tbots_cpp.Point(3, 0) - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ initial_position, @@ -314,7 +308,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: MoveTactic( destination=tbots_cpp.createPointProto(destination), @@ -334,10 +328,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, test_timeout_s=6, ) diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index 9c50a03231..9693f16ddc 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -9,9 +9,7 @@ from software.gameplay_tests.validation.ball_speed_threshold import * from software.gameplay_tests.validation.robot_speed_threshold import * from software.gameplay_tests.validation.excessive_dribbling import * -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from proto.message_translation.tbots_protobuf import create_world_state @@ -34,10 +32,10 @@ def test_ball_chipped_on_intercept( ball_initial_position, ball_initial_velocity, position_to_block_from, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[position_to_block_from], @@ -46,7 +44,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: PassDefenderTactic( position_to_block_from=tbots_cpp.createPointProto( @@ -73,12 +71,10 @@ def setup(*args): eventually_validation_sequence_set = [[BallSpeedEventuallyBelowThreshold(0.4)]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) @@ -107,10 +103,10 @@ def test_avoid_intercept_scenario( ball_initial_position, ball_initial_velocity, position_to_block_from, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[position_to_block_from], @@ -119,7 +115,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: PassDefenderTactic( position_to_block_from=tbots_cpp.createPointProto( @@ -164,12 +160,10 @@ def setup(*args): eventually_validation_sequence_set = [[BallSpeedEventuallyBelowThreshold(0.4)]] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) @@ -240,10 +234,10 @@ def test_steal_ball( position_to_block_from, enemy_kicker_position, should_steal, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[position_to_block_from], yellow_robot_locations=[enemy_kicker_position], @@ -252,7 +246,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 0: PassDefenderTactic( position_to_block_from=tbots_cpp.createPointProto( @@ -285,12 +279,10 @@ def setup(*args): FriendlyNeverHasBallPossession(tolerance=0.05) ) - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) diff --git a/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py index bea017ad1a..d3da86435d 100644 --- a/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py @@ -13,9 +13,7 @@ BallAlwaysMovesForward, ) from proto.message_translation.tbots_protobuf import create_world_state -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -51,16 +49,14 @@ @pytest.mark.skip( "Disabling this test because of poor dribbling controls, does not consistently score goal. TODO (#2232)" ) -def test_penalty_kick( - enemy_robot_location, enemy_robot_velocity, simulated_test_runner -): +def test_penalty_kick(enemy_robot_location, enemy_robot_velocity, gameplay_test_runner): field = tbots_cpp.Field.createSSLDivisionBField() ball_initial_pos = field.friendlyPenaltyMark() - def setup(*args): + def setup(): shooter_position = ball_initial_pos - tbots_cpp.Vector(0.1, 0) - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[shooter_position], yellow_robot_locations=[enemy_robot_location], @@ -69,7 +65,7 @@ def setup(*args): ), ) - simulated_test_runner.set_tactics(blue_tactics={0: PenaltyKickTactic()}) + gameplay_test_runner.set_tactics(blue_tactics={0: PenaltyKickTactic()}) eventually_validation_sequence_set = [ [ @@ -82,10 +78,10 @@ def setup(*args): [BallAlwaysMovesForward(ball_initial_pos), NeverExcessivelyDribbles()] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, ) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD index a248df127b..5fb40b0997 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD +++ b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD @@ -42,3 +42,15 @@ py_test( requirement("pytest"), ], ) + +py_test( + name = "pivot_kick_tactic_field_test", + srcs = [ + "pivot_kick_tactic_field_test.py", + ], + deps = [ + "//software:conftest", + "//software/gameplay_tests/validation:validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_field_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_field_test.py new file mode 100644 index 0000000000..7f86290b4f --- /dev/null +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_field_test.py @@ -0,0 +1,46 @@ +import software.python_bindings as tbots_cpp + +from proto.import_all_protos import * +from proto.message_translation.tbots_protobuf import create_world_state +from software.gameplay_tests.util import pytest_main +from software.gameplay_tests.validation.ball_kicked_in_direction import ( + BallEventuallyKickedInDirection, +) + + +def test_pivot_kick(gameplay_test_runner): + robot_id = 0 + + ball_location = tbots_cpp.Point(-1.13, 0.75) + angle_to_kick_at = tbots_cpp.Angle.threeQuarter() + + def setup(): + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(1, 1)], + yellow_robot_locations=[], + ball_location=ball_location, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + gameplay_test_runner.set_tactics( + blue_tactics={ + robot_id: PivotKickTactic( + kick_origin=tbots_cpp.createPointProto(ball_location), + kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), + ) + } + ) + + gameplay_test_runner.run_test( + setup=setup, + eventually_validation_sequence_set=[ + [BallEventuallyKickedInDirection(angle_to_kick_at)] + ], + test_timeout_s=15, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index 04663036b1..3527d4c071 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -7,9 +7,7 @@ from software.gameplay_tests.validation.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -36,12 +34,12 @@ @pytest.mark.skip( "Disabling this flaky test. TODO (#2859): the robot does not dribble far enough into the ball" ) -def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): +def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, gameplay_test_runner): robot_position = tbots_cpp.Point(0, 0) ball_position = robot_position + ball_offset_from_robot - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -53,7 +51,7 @@ def setup(*args): ) ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={ 1: PivotKickTactic( kick_origin=tbots_cpp.createPointProto(ball_position), @@ -69,12 +67,10 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=[[]], test_timeout_s=5, ) diff --git a/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py index a38dadf898..fc76fd49c3 100644 --- a/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py @@ -12,7 +12,7 @@ from software.gameplay_tests.validation.robot_received_ball import ( RobotEventuallyReceivedBall, ) -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main def calculate_ball_velocity(passer_point, receiver_point, speed): @@ -187,14 +187,14 @@ def test_receiver( robot_pos, robot_orientation, one_touch, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): + def setup(): ball_velocity = calculate_ball_velocity( passer_point, receiver_point, pass_speed ) - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -216,7 +216,7 @@ def setup(*args): "disable_one_touch_shot": not one_touch, } - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics={1: ReceiverTactic(**receiver_args)} ) @@ -232,11 +232,9 @@ def setup(*args): ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - run_till_end=False, + eventually_validation_sequence_set=eventually_validation_sequence_set, ) diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index 118ee44e77..5580206446 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -1,7 +1,5 @@ import pytest -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main from software.gameplay_tests.validation.avoid_collisions import * import software.python_bindings as tbots from software.py_constants import * @@ -9,7 +7,6 @@ import math from proto.import_all_protos import * from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import SimulatedTestRunner from software.gameplay_tests.validation.validation import ( create_validation_types, create_validation_geometry, @@ -138,7 +135,6 @@ def create_zig_zag_path_test_params( ], [], 20, - False, ) @@ -168,7 +164,7 @@ def hrvo_setup( friendly_robots_final_orientations: list[tbots.Angle], enemy_robots_positions: list[tbots.Point], enemy_robots_destinations: list[tbots.Point], - simulated_test_runner: SimulatedTestRunner, + gameplay_test_runner, ): """Setup for the hrvo tests @@ -177,7 +173,7 @@ def hrvo_setup( :param friendly_robots_final_orientations: final orientations of friendly robots :param enemy_robots_positions: starting positions of enemy robots :param enemy_robots_destinations: destinations of enemy robots - :param simulated_test_runner: the current test runner being used + :param gameplay_test_runner: the current test runner being used """ desired_orientation = tbots.Angle.fromRadians(0) @@ -185,7 +181,7 @@ def hrvo_setup( ball_initial_vel = tbots.Point(0, 0) - simulated_test_runner.set_world_state( + gameplay_test_runner.set_world_state( create_world_state( yellow_robot_locations=enemy_robots_positions, blue_robot_locations=friendly_robots_positions, @@ -194,13 +190,13 @@ def hrvo_setup( ) ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.BLUE ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.YELLOW ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) @@ -226,14 +222,14 @@ def hrvo_setup( tbots.Angle.fromRadians(0), ) - simulated_test_runner.set_tactics( + gameplay_test_runner.set_tactics( blue_tactics=blue_tactics, yellow_tactics=yellow_tactics ) @pytest.mark.parametrize( "friendly_robot_positions,friendly_robot_destinations,friendly_robots_final_orientations," - + "enemy_robots_positions,enemy_robots_destinations,timeout_s,run_till_end", + + "enemy_robots_positions,enemy_robots_destinations,timeout_s", [ # robot moving straight with no obstacles ( @@ -243,7 +239,6 @@ def hrvo_setup( [], [], 10, - False, ), # robot moving straight with no obstacles while turning from 0 to 180 degrees ( @@ -253,7 +248,6 @@ def hrvo_setup( [], [], 10, - False, ), # robot moving straight with a moving enemy robot behind it ( @@ -263,7 +257,6 @@ def hrvo_setup( [tbots.Point(-2.5, 0)], [tbots.Point(-2.1, 0)], 10, - False, ), # robot moving straight with a moving enemy robot to its side ( @@ -273,7 +266,6 @@ def hrvo_setup( [tbots.Point(-1, -0.8)], [tbots.Point(1, 0)], 10, - False, ), # robot moving straight with a moving enemy robot moving straight towards it ( @@ -283,7 +275,6 @@ def hrvo_setup( [tbots.Point(2.8, 0)], [tbots.Point(2.5, 0)], 10, - False, ), # robot moving with a stationary friendly robot in front of it ( @@ -293,7 +284,6 @@ def hrvo_setup( [], [], 12, - False, ), # robot moving with a stationary enemy robot in front of it ( @@ -303,7 +293,6 @@ def hrvo_setup( [tbots.Point(1, 0)], [], 12, - False, ), # robot moving straight with a moving friendly robot moving straight towards it ( @@ -313,7 +302,6 @@ def hrvo_setup( [], [], 10, - False, ), # robot moving straight with a moving friendly robot moving straight towards it # while both are turning from 0 to 180 degrees @@ -324,7 +312,6 @@ def hrvo_setup( [], [], 10, - False, ), # robot moving with a 3 enemy robot wall in front of it ( @@ -338,7 +325,6 @@ def hrvo_setup( ], [], 10, - False, ), # robot moving with various stationary enemy robots in front of it # walls generated by range(start_x, start_x + step * num_robots, step) @@ -349,7 +335,6 @@ def hrvo_setup( [tbots.Point(float(x_meters) / 10, 0) for x_meters in range(20, 32, 2)], [], 25, - True, ), # robot moving in a local minima (enemy robots in a curve around it) ( @@ -367,7 +352,6 @@ def hrvo_setup( ], [], 25, - False, ), # robot moving in a local minima (enemy robots in a curve around it) with an opening in the middle ( @@ -385,7 +369,6 @@ def hrvo_setup( ], [], 10, - False, ), # robot moving in a zig zag path around enemy robots create_zig_zag_path_test_params(-2, 0.3, 3, 5), @@ -397,7 +380,6 @@ def hrvo_setup( [], [], 15, - False, ), # friendly robots in a circle moving along each diameter # while turning from 0 to 180 degrees @@ -408,7 +390,6 @@ def hrvo_setup( [], [], 20, - False, ), # half enemy half friendly robots in a circle moving along each diameter ( @@ -448,7 +429,6 @@ def hrvo_setup( if index % 2 == 1 ], 10, - False, ), ], ids=[ @@ -472,41 +452,34 @@ def hrvo_setup( ], ) def test_robot_movement( - simulated_test_runner: SimulatedTestRunner, + gameplay_test_runner, friendly_robot_positions: list[tbots.Point], friendly_robot_destinations: list[tbots.Point], friendly_robots_final_orientations: list[tbots.Angle], enemy_robots_positions: list[tbots.Point], enemy_robots_destinations: list[tbots.Point], timeout_s: int, - run_till_end: bool, ): # Always Validation always_validation_sequence_set = [[RobotsDoNotCollide()]] # Eventually Validation - eventually_validation_sequence_set = ( - [[]] - if run_till_end - else get_reached_destination_validation(friendly_robot_destinations) + eventually_validation_sequence_set = get_reached_destination_validation( + friendly_robot_destinations ) - simulated_test_runner.run_test( - setup=lambda param: hrvo_setup( + gameplay_test_runner.run_test( + setup=lambda: hrvo_setup( friendly_robot_positions, friendly_robot_destinations, friendly_robots_final_orientations, enemy_robots_positions, enemy_robots_destinations, - simulated_test_runner, + gameplay_test_runner, ), - params=[0], - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=[[]], - ag_always_validation_sequence_set=[[]], + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=timeout_s, - run_till_end=run_till_end, ) diff --git a/src/software/conftest.py b/src/software/conftest.py index e625d1ec07..aed4da95be 100644 --- a/src/software/conftest.py +++ b/src/software/conftest.py @@ -1,6 +1,5 @@ import pytest # noqa: F401 -from software.gameplay_tests.simulated_test_fixture import simulated_test_runner # noqa: F401 -from software.gameplay_tests.field_test_fixture import field_test_runner # noqa: F401 +from software.gameplay_tests.fixture import gameplay_test_runner # noqa: F401 # Pytest requires that all tests fixtures shared across a package be defined # in a single conftest.py file in the parent directory of the package. diff --git a/src/software/gameplay_tests/BUILD b/src/software/gameplay_tests/BUILD index a70ceb7bd8..e20aa01667 100644 --- a/src/software/gameplay_tests/BUILD +++ b/src/software/gameplay_tests/BUILD @@ -34,9 +34,9 @@ py_library( ) py_library( - name = "simulated_test_fixture", + name = "field_test_runner", srcs = [ - "simulated_test_fixture.py", + "field_test_runner.py", ], data = [ "//software:py_constants.so", @@ -44,88 +44,54 @@ py_library( deps = [ ":tbots_test_runner", "//proto:import_all_protos", - "//software/gameplay_tests/validation:validations", "//software/logger:py_logger", - "//software/networking/unix:threaded_unix_listener_py", - "//software/networking/unix:threaded_unix_sender_py", "//software/thunderscope", - "//software/thunderscope/binary_context_managers:full_system", - "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:simulator", + requirement("pytest"), ], ) py_library( - name = "field_test_fixture", + name = "simulated_test_runner", srcs = [ - "field_test_fixture.py", + "simulated_test_runner.py", ], data = [ "//software:py_constants.so", ], deps = [ + ":tbots_test_runner", "//proto:import_all_protos", - "//software/gameplay_tests:tbots_test_runner", - "//software/gameplay_tests/validation:validations", "//software/logger:py_logger", - "//software/networking/unix:threaded_unix_listener_py", - "//software/networking/unix:threaded_unix_sender_py", "//software/thunderscope", - "//software/thunderscope:constants", - "//software/thunderscope:estop_helpers", - "//software/thunderscope:robot_communication", - "//software/thunderscope/binary_context_managers:full_system", - "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:simulator", - ], -) - -py_test( - name = "simulated_test_ball_model", - srcs = [ - "simulated_test_ball_model.py", - ], - deps = [ - "//software:conftest", - "//software/gameplay_tests/validation:validations", - requirement("pytest"), - ], -) - -py_test( - name = "movement_robot_field_test", - srcs = [ - "movement_robot_field_test.py", - ], - deps = [ - "//software:conftest", - "//software/gameplay_tests:tbots_test_runner", - "//software/gameplay_tests/validation:validations", requirement("pytest"), ], ) -py_test( - name = "pivot_kick_field_test", +py_library( + name = "fixture", srcs = [ - "pivot_kick_field_test.py", + "fixture.py", ], deps = [ - "//software:conftest", - "//software/gameplay_tests:tbots_test_runner", - "//software/gameplay_tests/validation:validations", + ":field_test_runner", + ":simulated_test_runner", + ":util", + "//software/logger:py_logger", + "//software/thunderscope", + "//software/thunderscope:estop_helpers", + "//software/thunderscope/binary_context_managers:full_system", + "//software/thunderscope/binary_context_managers:game_controller", + "//software/thunderscope/binary_context_managers:simulator", requirement("pytest"), ], ) -py_test( - name = "passing_field_test", +py_library( + name = "util", srcs = [ - "passing_field_test.py", + "util.py", ], deps = [ - "//software:conftest", - "//software/gameplay_tests/validation:validations", requirement("pytest"), ], ) diff --git a/src/software/gameplay_tests/field_test_fixture.py b/src/software/gameplay_tests/field_test_fixture.py deleted file mode 100644 index 0074d206e5..0000000000 --- a/src/software/gameplay_tests/field_test_fixture.py +++ /dev/null @@ -1,494 +0,0 @@ -import queue -import time -import os -import threading - -import pytest -import argparse -from proto.import_all_protos import * - -from software.gameplay_tests.validation import validation -from software.thunderscope.constants import EstopMode, IndividualRobotMode -from software.thunderscope.thunderscope import Thunderscope -from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.thunderscope.binary_context_managers.full_system import FullSystem -from software.thunderscope.binary_context_managers.game_controller import Gamecontroller -from software.thunderscope.wifi_communication_manager import WifiCommunicationManager -from software.logger.logger import create_logger - - -from software.thunderscope.thunderscope_config import configure_field_test_view -from software.gameplay_tests.tbots_test_runner import TbotsTestRunner -from software.thunderscope.robot_communication import RobotCommunication -from software.thunderscope.estop_helpers import get_estop_config -from software.py_constants import * -from typing import override - -logger = create_logger(__name__) - -WORLD_BUFFER_TIMEOUT = 5.0 -PROCESS_BUFFER_DELAY_S = 0.01 -PAUSE_AFTER_FAIL_DELAY_S = 3 -LAUNCH_DELAY_S = 0.1 - - -class FieldTestRunner(TbotsTestRunner): - """Run a field test""" - - def __init__( - self, - test_name, - thunderscope, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - robot_communication, - publish_validation_protos=True, - is_yellow_friendly=False, - ): - """Initialize the FieldTestRunner - - :param test_name: The name of the test to run - :param thunderscope: The Thunderscope to use, None if not used - :param blue_full_system_proto_unix_io: The blue full system proto unix io to use - :param yellow_full_system_proto_unix_io: The yellow full system proto unix io to use - :param gamecontroller: The gamecontroller context managed instance - :param robot_communication: The robot communication instance - :param publish_validation_protos: whether to publish validation protos - :param: is_yellow_friendly: if yellow is the friendly team - """ - super(FieldTestRunner, self).__init__( - test_name, - thunderscope, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - is_yellow_friendly, - ) - self.publish_validation_protos = publish_validation_protos - self.is_yellow_friendly = is_yellow_friendly - self.robot_communication = robot_communication - - logger.info("determining robots on field") - # survey field for available robot ids - survey_start_time = time.time() - self.friendly_robot_ids_field = [] - while time.time() - survey_start_time < WORLD_BUFFER_TIMEOUT: - try: - world = self.world_buffer.get(block=True, timeout=0.1) - self.initial_world = world - self.friendly_robot_ids_field = [ - robot.id for robot in world.friendly_team.team_robots - ] - - if len(self.friendly_robot_ids_field) > 0: - logger.info(f"friendly team ids {self.friendly_robot_ids_field}") - break - except queue.Empty: - continue - - if len(self.friendly_robot_ids_field) == 0: - raise Exception("no friendly robots found on field within timeout") - - def wait_for_estop_play(self): - """Blocks until the estop is in the PLAY state""" - if self.robot_communication.estop_is_playing: - return - - logger.info("\x1b[33m" + "Waiting for Estop to be in PLAY state..." + "\x1b[0m") - while not self.robot_communication.estop_is_playing: - # We must process events if Thunderscope is running to keep it responsive - if self.thunderscope: - from pyqtgraph.Qt import QtWidgets - - QtWidgets.QApplication.processEvents() - time.sleep(0.1) - logger.info( - "\x1b[32m" + "Estop is in PLAY state. Proceeding with test." + "\x1b[0m" - ) - - @override - def send_gamecontroller_command( - self, - gc_command: proto.ssl_gc_state_pb2.Command, - team: proto.ssl_gc_common_pb2.Team, - final_ball_placement_point=None, - ): - """Send a command to the gamecontroller - - :param gc_command: The command to send - :param team: The team which the command as attributed to - :param final_ball_placement_point: The ball placement point - """ - self.gamecontroller.send_gc_command( - gc_command=gc_command, - team=team, - final_ball_placement_point=final_ball_placement_point, - ) - - @override - def run_test( - self, - always_validation_sequence_set=[[]], - eventually_validation_sequence_set=[[]], - test_timeout_s=3, - ): - """Run a test. In a field test this means beginning validation. - - :param always_validation_sequence_set: Validation functions that should - hold on every tick - :param eventually_validation_sequence_set: Validation that should - eventually be true, before the test ends - :param test_timeout_s: The timeout for the test, if any eventually_validations - remain after the timeout, the test fails. - """ - - def stop_test(delay): - time.sleep(delay) - # We no longer close thunderscope here, because a test might call run_test multiple times. - # Thunderscope will be closed when the fixture is torn down. - - def __runner(): - time.sleep(LAUNCH_DELAY_S) - - test_end_time = time.time() + test_timeout_s - - # Keep track if we started with any eventually validations - has_eventually_validations = any( - len(seq) > 0 for seq in eventually_validation_sequence_set - ) - - while time.time() < test_end_time: - while True: - try: - world = self.world_buffer.get( - block=True, timeout=WORLD_BUFFER_TIMEOUT - ) - break - except queue.Empty: - # If we timeout, that means full_system missed the last - # wrapper and robot status, lets resend it. - logger.warning( - f"No World was received for {WORLD_BUFFER_TIMEOUT} seconds. Ending test early." - ) - - # Validate - ( - eventually_validation_proto_set, - always_validation_proto_set, - ) = validation.run_validation_sequence_sets( - world, - eventually_validation_sequence_set, - always_validation_sequence_set, - ) - - if self.publish_validation_protos: - # Set the test name - eventually_validation_proto_set.test_name = self.test_name - always_validation_proto_set.test_name = self.test_name - - # Send out the validation proto to thunderscope - self.blue_full_system_proto_unix_io.send_proto( - ValidationProtoSet, eventually_validation_proto_set - ) - self.blue_full_system_proto_unix_io.send_proto( - ValidationProtoSet, always_validation_proto_set - ) - - # Check that all always validations are always valid - validation.check_validation(always_validation_proto_set) - - # Break if eventually validation passes - if has_eventually_validations and all( - len(seq) == 0 for seq in eventually_validation_sequence_set - ): - break - - validation.check_validation(eventually_validation_proto_set) - - stop_test(delay=PROCESS_BUFFER_DELAY_S) - - def excepthook(args): - """This function is _critical_ for show_thunderscope to work. - If the test Thread will raises an exception we won't be able to close - the window from the main thread. - - :param args: The args passed in from the hook - """ - stop_test(delay=PAUSE_AFTER_FAIL_DELAY_S) - self.last_exception = args.exc_value - raise self.last_exception - - threading.excepthook = excepthook - - # If visualization is enabled, we need to be careful. - # Thunderscope.show() is blocking. - if self.thunderscope: - run_test_thread = threading.Thread(target=__runner, daemon=True) - run_test_thread.start() - - # Only call show if the window is not already open. - # If it IS open, it means we are ALREADY in the Qt event loop, - # which can only happen if we are running this run_test in a background thread. - if not self.thunderscope.is_open(): - self.thunderscope.show() - - run_test_thread.join() - - if self.last_exception: - pytest.fail(str(self.last_exception)) - - else: - __runner() - - -def get_runtime_dir(): - """Gets the base runtime directory for the test execution. - - TODO: Refactor #3744 - - Creates a new persistent directory for each test so that tests - running in parallel do not interfere with each other. - - :return: The path to the runtime directory. - """ - import uuid - - runtime_dir = os.path.join("/tmp", f"tbots_{uuid.uuid4().hex[:8]}") - os.makedirs(runtime_dir, exist_ok=True) - return runtime_dir - - -RUNTIME_DIR = get_runtime_dir() - - -def load_command_line_arguments(): - """Load in command-line arguments using argparse - - NOTE: Pytest has its own built in argument parser (conftest.py, pytest_addoption) - but it doesn't seem to play nicely with bazel. We just use argparse instead. - """ - parser = argparse.ArgumentParser(description="Run simulated or field pytests") - parser.add_argument( - "--simulator_runtime_dir", - type=str, - help="simulator runtime directory", - default=RUNTIME_DIR, - ) - parser.add_argument( - "--blue_full_system_runtime_dir", - type=str, - help="blue full_system runtime directory", - default=os.path.join(RUNTIME_DIR, "blue"), - ) - parser.add_argument( - "--yellow_full_system_runtime_dir", - type=str, - help="yellow full_system runtime directory", - default=os.path.join(RUNTIME_DIR, "yellow"), - ) - parser.add_argument( - "--layout", - action="store", - help="Which layout to run, if not specified the last layout will run", - ) - parser.add_argument( - "--debug_blue_full_system", - action="store_true", - default=False, - help="Debug blue full_system", - ) - parser.add_argument( - "--debug_yellow_full_system", - action="store_true", - default=False, - help="Debug yellow full_system", - ) - parser.add_argument( - "--debug_simulator", - action="store_true", - default=False, - help="Debug the simulator", - ) - parser.add_argument( - "--visualization_buffer_size", - action="store", - type=int, - default=5, - help="How many packets to buffer while rendering", - ) - parser.add_argument( - "--show_gamecontroller_logs", - action="store_true", - default=False, - help="How many packets to buffer while rendering", - ) - parser.add_argument( - "--run_field_test", - action="store_true", - default=False, - help="whether to run test as a field test instead of a simulated test", - ) - parser.add_argument( - "--test_filter", - action="store", - default="", - help="The test filter, if not specified all tests will run. " - + "See https://docs.pytest.org/en/latest/how-to/usage.html#specifying-tests-selecting-tests", - ) - - parser.add_argument( - "--interface", - action="store", - type=str, - default=None, - help="Which interface to communicate over", - ) - - parser.add_argument( - "--channel", - action="store", - type=int, - default=0, - help="Which channel to communicate over", - ) - - parser.add_argument( - "--estop_baudrate", - action="store", - type=int, - default=115200, - help="Estop Baudrate", - ) - - parser.add_argument( - "--run_yellow", - action="store_true", - default=False, - help="Run the test with friendly robots in yellow mode", - ) - - estop_group = parser.add_mutually_exclusive_group() - estop_group.add_argument( - "--keyboard_estop", - action="store_true", - default=False, - help="Allows the use of the spacebar as an estop instead of a physical one", - ) - estop_group.add_argument( - "--disable_communication", - action="store_true", - default=False, - help="Disables checking for estop plugged in (ONLY USE FOR LOCAL TESTING)", - ) - - return parser.parse_args() - - -@pytest.fixture -def field_test_runner(): - """Runs a field test - - :return: yields the runner to the test fixture - """ - simulator_proto_unix_io = ProtoUnixIO() - yellow_full_system_proto_unix_io = ProtoUnixIO() - blue_full_system_proto_unix_io = ProtoUnixIO() - args = load_command_line_arguments() - - # Grab the current test name to store the proto log for the test case - current_test = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0] - current_test = current_test.replace("]", "") - current_test = current_test.replace("[", "-") - - test_name = current_test.split("-")[0] - debug_full_sys = args.debug_blue_full_system - runtime_dir = f"{args.blue_full_system_runtime_dir}/test/{test_name}" - friendly_proto_unix_io = blue_full_system_proto_unix_io - - if args.run_yellow: - debug_full_sys = args.debug_yellow_full_system - runtime_dir = f"{args.yellow_full_system_runtime_dir}/test/{test_name}" - friendly_proto_unix_io = yellow_full_system_proto_unix_io - - estop_mode, estop_path = get_estop_config( - args.keyboard_estop, args.disable_communication - ) - - # Launch all binaries - with FullSystem( - "software/unix_full_system", - full_system_runtime_dir=runtime_dir, - debug_full_system=debug_full_sys, - friendly_colour_yellow=args.run_yellow, - should_restart_on_crash=False, - ) as friendly_fs, Gamecontroller( - # we would be using conventional port if and only if we are playing in robocup. - suppress_logs=(not args.show_gamecontroller_logs), - use_conventional_port=False, - ) as gamecontroller, WifiCommunicationManager( - current_proto_unix_io=friendly_proto_unix_io, - multicast_channel=getRobotMulticastChannel(args.channel), - should_setup_full_system=True, - interface=args.interface, - referee_port=gamecontroller.get_referee_port() - if gamecontroller - else SSL_REFEREE_PORT, - ) as wifi_communication_manager, RobotCommunication( - current_proto_unix_io=friendly_proto_unix_io, - communication_manager=wifi_communication_manager, - estop_mode=estop_mode, - estop_path=estop_path, - ) as rc_friendly: - friendly_fs.setup_proto_unix_io(friendly_proto_unix_io) - - gamecontroller.setup_proto_unix_io( - blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, - simulator_proto_unix_io=simulator_proto_unix_io, - ) - # Inject the proto unix ios into thunderscope and start the test - tscope = Thunderscope( - configure_field_test_view( - simulator_proto_unix_io=simulator_proto_unix_io, - blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, - yellow_is_friendly=args.run_yellow, - ), - layout_path=None, - ) - - # Set control mode for all robots to AI so that packets are sent to the robots - for robot_id in range(MAX_ROBOT_IDS_PER_SIDE): - rc_friendly.toggle_individual_robot_control_mode( - robot_id, - IndividualRobotMode.AI, - ) - - # connect the keyboard estop toggle to the key event if needed - if estop_mode == EstopMode.KEYBOARD_ESTOP: - tscope.keyboard_estop_shortcut.activated.connect( - rc_friendly.toggle_keyboard_estop - ) - # we call this method to enable estop automatically when a field test starts - rc_friendly.toggle_keyboard_estop() - logger.warning( - "\x1b[31;20m" - + "Keyboard Estop Enabled, robots will start moving automatically when test starts!" - + "\x1b[0m" - ) - - time.sleep(LAUNCH_DELAY_S) - runner = FieldTestRunner( - test_name=current_test, - blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, - gamecontroller=gamecontroller, - thunderscope=tscope, - robot_communication=rc_friendly, - is_yellow_friendly=args.run_yellow, - ) - - friendly_proto_unix_io.register_observer(World, runner.world_buffer) - - yield runner diff --git a/src/software/gameplay_tests/field_test_runner.py b/src/software/gameplay_tests/field_test_runner.py new file mode 100644 index 0000000000..b374fc2c36 --- /dev/null +++ b/src/software/gameplay_tests/field_test_runner.py @@ -0,0 +1,183 @@ +import queue +import time +from typing import override + +from proto.import_all_protos import ValidationProtoSet, WorldState +from software.gameplay_tests.tbots_test_runner import TbotsTestRunner +from software.gameplay_tests.validation import validation +from software.logger.logger import create_logger + +logger = create_logger(__name__) + +WORLD_BUFFER_TIMEOUT = 5.0 +LAUNCH_DELAY_S = 0.1 + + +class FieldTestRunner(TbotsTestRunner): + """Run a field test""" + + def __init__( + self, + test_name, + thunderscope, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + gamecontroller, + robot_communication, + is_yellow_friendly=False, + ): + """Initialize the FieldTestRunner + + :param test_name: The name of the test to run + :param thunderscope: The Thunderscope to use, None if not used + :param blue_full_system_proto_unix_io: The blue full system proto unix io to use + :param yellow_full_system_proto_unix_io: The yellow full system proto unix io to use + :param gamecontroller: The gamecontroller context managed instance + :param robot_communication: The robot communication instance + :param: is_yellow_friendly: if yellow is the friendly team + """ + super(FieldTestRunner, self).__init__( + test_name, + thunderscope, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + gamecontroller, + is_yellow_friendly, + ) + self.is_yellow_friendly = is_yellow_friendly + self.robot_communication = robot_communication + + # self._survey_field_robots() + + @override + def set_world_state(self, world_state: WorldState): + # TODO (#3369): add visualization for setup instead of just logging warning + logger.warning( + "set_world_state called in field test: " + "assuming robots are initialized according to the given parameters" + ) + + @override + def _runner( + self, + always_validation_sequence_set, + eventually_validation_sequence_set, + test_timeout_s, + gc_cmd_with_delay, + ): + self._wait_for_estop_play() + + time.sleep(LAUNCH_DELAY_S) + + time_elapsed_s = 0 + + while time_elapsed_s < test_timeout_s: + processing_start_time = time.time() + + # Check for new GC commands at this time step + for delay, cmd, team in gc_cmd_with_delay: + # If delay matches time + if delay <= time_elapsed_s: + # send command + self.gamecontroller.send_gc_command(gc_command=cmd, team=team) + # remove command from the list + gc_cmd_with_delay.remove((delay, cmd, team)) + + while True: + try: + world = self.world_buffer.get( + block=True, timeout=WORLD_BUFFER_TIMEOUT + ) + break + except queue.Empty: + # If we timeout, that means full_system missed the last + # wrapper and robot status, lets resend it. + logger.warning( + f"No World was received for {WORLD_BUFFER_TIMEOUT} seconds. Ending test early." + ) + + # Validate + ( + eventually_validation_proto_set, + always_validation_proto_set, + ) = validation.run_validation_sequence_sets( + world, + eventually_validation_sequence_set, + always_validation_sequence_set, + ) + + # Set the test name + eventually_validation_proto_set.test_name = self.test_name + always_validation_proto_set.test_name = self.test_name + + if self.is_yellow_friendly: + self.yellow_full_system_proto_unix_io.send_proto( + ValidationProtoSet, eventually_validation_proto_set + ) + self.yellow_full_system_proto_unix_io.send_proto( + ValidationProtoSet, always_validation_proto_set + ) + else: + self.blue_full_system_proto_unix_io.send_proto( + ValidationProtoSet, eventually_validation_proto_set + ) + self.blue_full_system_proto_unix_io.send_proto( + ValidationProtoSet, always_validation_proto_set + ) + + if len(always_validation_sequence_set) != 0: + # Check that all always validations are always valid + validation.check_validation(always_validation_proto_set) + else: + # If there are no always validations, check eventually validations to end test early + try: + validation.check_validation(eventually_validation_proto_set) + break + except AssertionError: + pass + + time_elapsed_s += time.time() - processing_start_time + + # Check that all eventually validations are eventually valid + validation.check_validation(eventually_validation_proto_set) + + self._stopper() + + def _wait_for_estop_play(self): + """Blocks until the estop is in the PLAY state""" + if self.robot_communication.estop_is_playing: + return + + logger.info("\x1b[33m" + "Waiting for Estop to be in PLAY state..." + "\x1b[0m") + while not self.robot_communication.estop_is_playing: + # We must process events if Thunderscope is running to keep it responsive + # if self.thunderscope: + # from pyqtgraph.Qt import QtWidgets + # + # QtWidgets.QApplication.processEvents() + time.sleep(0.1) + logger.info( + "\x1b[32m" + "Estop is in PLAY state. Proceeding with test." + "\x1b[0m" + ) + + def _survey_field_robots(self): + logger.info("determining robots on field") + # survey field for available robot ids + survey_start_time = time.time() + self.friendly_robot_ids_field = [] + while time.time() - survey_start_time < WORLD_BUFFER_TIMEOUT: + try: + world = self.world_buffer.get(block=True, timeout=0.1) + self.initial_world = world + self.friendly_robot_ids_field = [ + robot.id for robot in world.friendly_team.team_robots + ] + + if len(self.friendly_robot_ids_field) > 0: + logger.info(f"friendly team ids {self.friendly_robot_ids_field}") + break + except queue.Empty: + continue + + if len(self.friendly_robot_ids_field) == 0: + raise Exception("no friendly robots found on field within timeout") diff --git a/src/software/gameplay_tests/fixture.py b/src/software/gameplay_tests/fixture.py new file mode 100644 index 0000000000..bd059e2291 --- /dev/null +++ b/src/software/gameplay_tests/fixture.py @@ -0,0 +1,244 @@ +import time + +import pytest +from software.py_constants import ( + MAX_ROBOT_IDS_PER_SIDE, + SSL_REFEREE_PORT, + getRobotMulticastChannel, +) + +from proto.import_all_protos import World +from software.gameplay_tests.field_test_runner import FieldTestRunner +from software.gameplay_tests.simulated_test_runner import SimulatedTestRunner +from software.gameplay_tests.util import ( + load_command_line_arguments, + get_pytest_name, + get_pytest_path_name, +) +from software.logger.logger import create_logger +from software.thunderscope.binary_context_managers.full_system import FullSystem +from software.thunderscope.binary_context_managers.game_controller import Gamecontroller +from software.thunderscope.binary_context_managers.simulator import Simulator +from software.thunderscope.constants import EstopMode, IndividualRobotMode +from software.thunderscope.estop_helpers import get_estop_config +from software.thunderscope.proto_unix_io import ProtoUnixIO +from software.thunderscope.robot_communication import RobotCommunication +from software.thunderscope.thunderscope import Thunderscope +from software.thunderscope.thunderscope_config import ( + configure_field_test_view, + configure_simulated_test_view, +) +from software.thunderscope.wifi_communication_manager import WifiCommunicationManager + +logger = create_logger(__name__) + +LAUNCH_DELAY_S = 0.1 + + +@pytest.fixture +def gameplay_test_runner(): + """Dispatches to the field or simulated test runner based on --run_field_test. + + :yields: A SimulatedTestRunner or FieldTestRunner. + """ + args = load_command_line_arguments() + if args.run_field_test: + yield from field_test_runner(args) + else: + yield from simulated_test_runner(args) + + +def simulated_test_runner(args): + """Starts the simulator, blue/yellow full systems, and gamecontroller binaries. + + :param args: Parsed command-line arguments. + :yields: A SimulatedTestRunner. + """ + tscope = None + + simulator_proto_unix_io = ProtoUnixIO() + yellow_full_system_proto_unix_io = ProtoUnixIO() + blue_full_system_proto_unix_io = ProtoUnixIO() + + # Grab the current test name to store the proto log for the test case + test_name = get_pytest_name() + test_path_name = get_pytest_path_name() + + # Launch all binaries + with ( + Simulator( + f"{args.simulator_runtime_dir}/test/{test_path_name}", + args.debug_simulator, + args.enable_realism, + ) as simulator, + FullSystem( + "software/unix_full_system", + f"{args.blue_full_system_runtime_dir}/test/{test_path_name}", + args.debug_blue_full_system, + False, + should_restart_on_crash=False, + running_in_realtime=args.enable_thunderscope and not args.ci_mode, + ) as blue_fs, + FullSystem( + "software/unix_full_system", + f"{args.yellow_full_system_runtime_dir}/test/{test_path_name}", + args.debug_yellow_full_system, + True, + should_restart_on_crash=False, + running_in_realtime=args.enable_thunderscope and not args.ci_mode, + ) as yellow_fs, + ): + with Gamecontroller( + suppress_logs=(not args.show_gamecontroller_logs) + ) as gamecontroller: + blue_fs.setup_proto_unix_io(blue_full_system_proto_unix_io) + yellow_fs.setup_proto_unix_io(yellow_full_system_proto_unix_io) + simulator.setup_proto_unix_io( + simulator_proto_unix_io, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + ProtoUnixIO(), + ) + gamecontroller.setup_proto_unix_io( + blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, + simulator_proto_unix_io=simulator_proto_unix_io, + ) + + # If we want to run thunderscope, inject the proto unix ios + # and start the test + if args.enable_thunderscope: + tscope = Thunderscope( + configure_simulated_test_view( + blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, + simulator_proto_unix_io=simulator_proto_unix_io, + ), + layout_path=args.layout, + ) + + time.sleep(LAUNCH_DELAY_S) + + runner = SimulatedTestRunner( + test_name, + tscope, + simulator_proto_unix_io, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + gamecontroller, + args.ci_mode, + ) + + yield runner + + +def field_test_runner(args): + """Starts the friendly full system, gamecontroller, WiFi, and robot communication. + + :param args: Parsed command-line arguments. + :yields: A FieldTestRunner. + """ + simulator_proto_unix_io = ProtoUnixIO() + yellow_full_system_proto_unix_io = ProtoUnixIO() + blue_full_system_proto_unix_io = ProtoUnixIO() + + # Grab the current test name to store the proto log for the test case + test_name = get_pytest_name() + test_path_name = get_pytest_path_name() + + debug_full_sys = args.debug_blue_full_system + runtime_dir = f"{args.blue_full_system_runtime_dir}/test/{test_path_name}" + friendly_proto_unix_io = blue_full_system_proto_unix_io + + if args.run_yellow: + debug_full_sys = args.debug_yellow_full_system + runtime_dir = f"{args.yellow_full_system_runtime_dir}/test/{test_path_name}" + friendly_proto_unix_io = yellow_full_system_proto_unix_io + + estop_mode, estop_path = get_estop_config( + args.keyboard_estop, args.disable_communication + ) + + # Launch all binaries + with ( + FullSystem( + "software/unix_full_system", + full_system_runtime_dir=runtime_dir, + debug_full_system=debug_full_sys, + friendly_colour_yellow=args.run_yellow, + should_restart_on_crash=False, + ) as friendly_fs, + Gamecontroller( + # we would be using conventional port if and only if we are playing in robocup. + suppress_logs=(not args.show_gamecontroller_logs), + use_conventional_port=False, + ) as gamecontroller, + WifiCommunicationManager( + current_proto_unix_io=friendly_proto_unix_io, + multicast_channel=getRobotMulticastChannel(args.channel), + should_setup_full_system=True, + interface=args.interface, + referee_port=gamecontroller.get_referee_port() + if gamecontroller + else SSL_REFEREE_PORT, + ) as wifi_communication_manager, + RobotCommunication( + current_proto_unix_io=friendly_proto_unix_io, + communication_manager=wifi_communication_manager, + estop_mode=estop_mode, + estop_path=estop_path, + ) as rc_friendly, + ): + friendly_fs.setup_proto_unix_io(friendly_proto_unix_io) + + gamecontroller.setup_proto_unix_io( + blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, + simulator_proto_unix_io=simulator_proto_unix_io, + ) + # Inject the proto unix ios into thunderscope and start the test + tscope = Thunderscope( + configure_field_test_view( + simulator_proto_unix_io=simulator_proto_unix_io, + blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, + yellow_is_friendly=args.run_yellow, + ), + layout_path=None, + ) + + # Set control mode for all robots to AI so that packets are sent to the robots + for robot_id in range(MAX_ROBOT_IDS_PER_SIDE): + rc_friendly.toggle_individual_robot_control_mode( + robot_id, + IndividualRobotMode.AI, + ) + + # connect the keyboard estop toggle to the key event if needed + if estop_mode == EstopMode.KEYBOARD_ESTOP: + tscope.keyboard_estop_shortcut.activated.connect( + rc_friendly.toggle_keyboard_estop + ) + # we call this method to enable estop automatically when a field test starts + rc_friendly.toggle_keyboard_estop() + logger.warning( + "\x1b[31;20m" + + "Keyboard Estop Enabled, robots will start moving automatically when test starts!" + + "\x1b[0m" + ) + + time.sleep(LAUNCH_DELAY_S) + + runner = FieldTestRunner( + test_name=test_name, + blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, + gamecontroller=gamecontroller, + thunderscope=tscope, + robot_communication=rc_friendly, + is_yellow_friendly=args.run_yellow, + ) + + friendly_proto_unix_io.register_observer(World, runner.world_buffer) + + yield runner diff --git a/src/software/gameplay_tests/movement_robot_field_test.py b/src/software/gameplay_tests/movement_robot_field_test.py deleted file mode 100644 index f623fd25a1..0000000000 --- a/src/software/gameplay_tests/movement_robot_field_test.py +++ /dev/null @@ -1,192 +0,0 @@ -import math -import threading - -from proto.import_all_protos import * -from software.gameplay_tests.field_test_fixture import * - -from software.gameplay_tests.simulated_test_fixture import * -from software.logger.logger import create_logger - -logger = create_logger(__name__) - - -# this test can only be run on the field -def test_basic_rotation(field_test_runner): - test_angles = [0, 45, 90, 180, 270, 0] - - world = field_test_runner.world_buffer.get(block=True, timeout=WORLD_BUFFER_TIMEOUT) - if len(world.friendly_team.team_robots) == 0: - raise Exception("The first world received had no robots in it!") - - print("Here are the robots:") - print( - [ - robot.current_state.global_position - for robot in world.friendly_team.team_robots - ] - ) - - id = world.friendly_team.team_robots[0].id - print(f"Running test on robot {id}") - - robot = world.friendly_team.team_robots[0] - rob_pos_p = robot.current_state.global_position - - def execute_test(): - # Wait for the user to flip the estop to PLAY - field_test_runner.wait_for_estop_play() - - # Force start the game automatically - field_test_runner.gamecontroller.send_gc_command( - proto.ssl_gc_state_pb2.Command.FORCE_START, - proto.ssl_gc_common_pb2.Team.UNKNOWN, - final_ball_placement_point=None, - ) - - for angle in test_angles: - print(f"Rotating to {angle} degrees") - move_tactic = MoveTactic() - move_tactic.destination.CopyFrom(rob_pos_p) - move_tactic.dribbler_mode = DribblerMode.OFF - move_tactic.final_orientation.CopyFrom( - Angle(radians=angle * math.pi / 180.0) - ) - move_tactic.ball_collision_type = BallCollisionType.AVOID - move_tactic.auto_chip_or_kick.CopyFrom( - AutoChipOrKick(autokick_speed_m_per_s=0.0) - ) - move_tactic.max_allowed_speed_mode = MaxAllowedSpeedMode.PHYSICAL_LIMIT - move_tactic.obstacle_avoidance_mode = ObstacleAvoidanceMode.SAFE - - # Setup Tactic - field_test_runner.set_tactics( - blue_tactics={id: move_tactic}, yellow_tactics=None - ) - field_test_runner.run_test( - always_validation_sequence_set=[[]], - eventually_validation_sequence_set=[[]], - test_timeout_s=5, - ) - - # validate by eye - logger.info(f"robot set to {angle} orientation") - time.sleep(2) - - # Send a halt tactic after the test finishes - field_test_runner.set_tactics( - blue_tactics={id: HaltTactic()}, yellow_tactics=None - ) - - test_thread = threading.Thread(target=execute_test, daemon=True) - test_thread.start() - - # If thunderscope is enabled, show it in the main thread (blocking) - if field_test_runner.thunderscope: - field_test_runner.thunderscope.show() - - test_thread.join() - - -def test_one_robots_square(field_test_runner): - world = field_test_runner.world_buffer.get(block=True, timeout=WORLD_BUFFER_TIMEOUT) - if len(world.friendly_team.team_robots) == 0: - raise Exception("The first world received had no robots in it!") - - print("Here are the robots:") - print( - [ - robot.current_state.global_position - for robot in world.friendly_team.team_robots - ] - ) - - id = world.friendly_team.team_robots[0].id - print(f"Running test on robot {id}") - - point1 = Point(x_meters=0.4, y_meters=0.4) - point2 = Point(x_meters=0.4, y_meters=-0.4) - point3 = Point(x_meters=-0.4, y_meters=-0.4) - point4 = Point(x_meters=-0.4, y_meters=0.4) - - tactic_0 = MoveTactic( - destination=point1, - dribbler_mode=DribblerMode.OFF, - final_orientation=Angle(radians=-math.pi / 2), - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) - tactic_1 = MoveTactic( - destination=point2, - dribbler_mode=DribblerMode.OFF, - final_orientation=Angle(radians=-math.pi / 2), - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) - tactic_2 = MoveTactic( - destination=point3, - dribbler_mode=DribblerMode.OFF, - final_orientation=Angle(radians=-math.pi / 2), - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) - tactic_3 = MoveTactic( - destination=point4, - dribbler_mode=DribblerMode.OFF, - final_orientation=Angle(radians=-math.pi / 2), - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) - - tactics = [tactic_0, tactic_1, tactic_2, tactic_3, tactic_0] - - def execute_test(): - # Wait for the user to flip the estop to PLAY - field_test_runner.wait_for_estop_play() - - # Force start the game automatically - field_test_runner.gamecontroller.send_gc_command( - proto.ssl_gc_state_pb2.Command.FORCE_START, - proto.ssl_gc_common_pb2.Team.UNKNOWN, - final_ball_placement_point=None, - ) - - for tactic in tactics: - print(f"Going to {tactic.destination}") - - field_test_runner.set_tactics( - blue_tactics={ - id: tactic, - }, - yellow_tactics=None, - ) - field_test_runner.run_test( - always_validation_sequence_set=[[]], - eventually_validation_sequence_set=[[]], - test_timeout_s=8, - ) - - # Send a halt tactic after the test finishes - field_test_runner.set_tactics( - blue_tactics={id: HaltTactic()}, yellow_tactics=None - ) - - test_thread = threading.Thread(target=execute_test, daemon=True) - test_thread.start() - - # If thunderscope is enabled, show it in the main thread (blocking) - if field_test_runner.thunderscope: - field_test_runner.thunderscope.show() - - test_thread.join() - - -if __name__ == "__main__": - pytest_main(__file__) diff --git a/src/software/gameplay_tests/passing_field_test.py b/src/software/gameplay_tests/passing_field_test.py index e9308e1e0f..37a5e59938 100644 --- a/src/software/gameplay_tests/passing_field_test.py +++ b/src/software/gameplay_tests/passing_field_test.py @@ -1,78 +1,76 @@ import software.python_bindings as tbots_cpp + from proto.import_all_protos import * -from software.gameplay_tests.field_test_fixture import * +from proto.message_translation.tbots_protobuf import create_world_state +from software.gameplay_tests.util import pytest_main from software.gameplay_tests.validation.friendly_receives_ball_slow import ( FriendlyAlwaysReceivesBallSlow, ) -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) -def test_passing(field_test_runner): - passer_robot_id = 3 - receiver_robot_id = 5 +def test_passing(gameplay_test_runner): + passer_robot_id = 0 + receiver_robot_id = 1 + should_receive_pass = True + passer_point = tbots_cpp.Point(0, 0) + receiver_point = tbots_cpp.Point(2, 0) - world = field_test_runner.world_buffer.get(block=True, timeout=WORLD_BUFFER_TIMEOUT) - passer_point = tbots_cpp.createPoint(world.ball.current_state.global_position) - receiver_point = None - for robot in world.friendly_team.team_robots: - if robot.id == receiver_robot_id: - receiver_point = tbots_cpp.createPoint(robot.current_state.global_position) - - receive_speed_m_per_s = 2.0 - min_pass_speed_m_per_s = 1.0 - max_pass_speed_m_per_s = 4.0 - - pass_to_test = tbots_cpp.Pass.fromDestReceiveSpeed( - passer_point, - receiver_point, - receive_speed_m_per_s, - min_pass_speed_m_per_s, - max_pass_speed_m_per_s, - ) + def setup(): + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(1, 1), receiver_point], + yellow_robot_locations=[], + ball_location=passer_point, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) - kick_vec = tbots_cpp.Vector( - pass_to_test.receiverPoint().x() - pass_to_test.passerPoint().x(), - pass_to_test.receiverPoint().y() - pass_to_test.passerPoint().y(), - ) + receive_speed_m_per_s = 2.0 + min_pass_speed_m_per_s = 1.0 + max_pass_speed_m_per_s = 4.0 - # Setup the passer's tactic - # We use KickTactic since AttackerTactic shoots towards the goal instead if open - # KickTactic just does the kick we want - blue_tactics = {} - blue_tactics[passer_robot_id] = KickTactic( - kick_origin=Point( - x_meters=pass_to_test.passerPoint().x(), - y_meters=pass_to_test.passerPoint().y(), - ), - kick_direction=Angle(radians=kick_vec.orientation().toRadians()), - kick_speed_meters_per_second=pass_to_test.speed(), - ) + pass_to_test = tbots_cpp.Pass.fromDestReceiveSpeed( + passer_point, + receiver_point, + receive_speed_m_per_s, + min_pass_speed_m_per_s, + max_pass_speed_m_per_s, + ) - # if we want a friendly robot to receive the pass - if should_receive_pass: - # arguments for a ReceiverTactic - receiver_args = { - "pass": Pass( - passer_point=Point( + kick_vec = pass_to_test.receiverPoint() - pass_to_test.passerPoint() + + # Setup the passer's tactic + # We use KickTactic since AttackerTactic shoots towards the goal instead if open + # KickTactic just does the kick we want + blue_tactics = { + passer_robot_id: KickTactic( + kick_origin=Point( x_meters=pass_to_test.passerPoint().x(), y_meters=pass_to_test.passerPoint().y(), ), - receiver_point=Point( - x_meters=pass_to_test.receiverPoint().x(), - y_meters=pass_to_test.receiverPoint().y(), - ), - pass_speed_m_per_s=pass_to_test.speed(), - ), - "disable_one_touch_shot": True, + kick_direction=Angle(radians=kick_vec.orientation().toRadians()), + kick_speed_meters_per_second=pass_to_test.speed(), + ) } - blue_tactics[receiver_robot_id] = ReceiverTactic(**receiver_args) + # if we want a friendly robot to receive the pass + if should_receive_pass: + # arguments for a ReceiverTactic + receiver_args = { + "pass": Pass( + passer_point=tbots_cpp.createPointProto(pass_to_test.passerPoint()), + receiver_point=tbots_cpp.createPointProto( + pass_to_test.receiverPoint() + ), + pass_speed_m_per_s=pass_to_test.speed(), + ), + "disable_one_touch_shot": True, + } - field = tbots_cpp.Field.createSSLDivisionBField() - tbots_cpp.EighteenZonePitchDivision(field) + blue_tactics[receiver_robot_id] = ReceiverTactic(**receiver_args) + + gameplay_test_runner.set_tactics(blue_tactics=blue_tactics) # Validate that the ball is always received by the other robot # slower than the max receive speed @@ -85,22 +83,12 @@ def test_passing(field_test_runner): ] ] - field_test_runner.set_tactics(blue_tactics=blue_tactics, yellow_tactics=None) - field_test_runner.run_test( + gameplay_test_runner.run_test( + setup=setup, always_validation_sequence_set=always_validation_sequence_set, - eventually_validation_sequence_set=[[]], test_timeout_s=5, ) - # Send a halt tactic after the test finishes - field_test_runner.set_tactics( - blue_tactics={ - passer_robot_id: HaltTactic(), - receiver_robot_id: HaltTactic(), - }, - yellow_tactics=None, - ) - if __name__ == "__main__": pytest_main(__file__) diff --git a/src/software/gameplay_tests/pivot_kick_field_test.py b/src/software/gameplay_tests/pivot_kick_field_test.py deleted file mode 100644 index 51dbf30d06..0000000000 --- a/src/software/gameplay_tests/pivot_kick_field_test.py +++ /dev/null @@ -1,47 +0,0 @@ -import math -from proto.import_all_protos import * -from software.gameplay_tests.field_test_fixture import * - -from software.gameplay_tests.simulated_test_fixture import * -from software.logger.logger import create_logger -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) - -logger = create_logger(__name__) - - -def test_pivot_kick(field_test_runner): - id = 5 - - world = field_test_runner.world_buffer.get(block=True, timeout=WORLD_BUFFER_TIMEOUT) - print("Here are the robots:") - print( - [ - robot.current_state.global_position - for robot in world.friendly_team.team_robots - ] - ) - - field_test_runner.set_tactics( - blue_tactics={ - id: PivotKickTactic( - kick_origin=Point(x_meters=-1.13, y_meters=0.75), - kick_direction=Angle(radians=-math.pi / 2), - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), - ) - }, - yellow_tactics=None, - ) - field_test_runner.run_test( - always_validation_sequence_set=[[]], - eventually_validation_sequence_set=[[]], - test_timeout_s=15, - ) - - # Send a halt tactic after the test finishes - field_test_runner.set_tactics(blue_tactics={id: HaltTactic()}, yellow_tactics=None) - - -if __name__ == "__main__": - pytest_main(__file__) diff --git a/src/software/gameplay_tests/simulated_test_fixture.py b/src/software/gameplay_tests/simulated_test_fixture.py deleted file mode 100644 index 71c14e1c85..0000000000 --- a/src/software/gameplay_tests/simulated_test_fixture.py +++ /dev/null @@ -1,648 +0,0 @@ -import threading -import queue -import argparse -import time -import sys -import os - -import pytest -from proto.import_all_protos import * - -from software.gameplay_tests.validation import validation -from software.gameplay_tests.tbots_test_runner import TbotsTestRunner -from software.thunderscope.thunderscope import Thunderscope -from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.py_constants import MILLISECONDS_PER_SECOND -from software.thunderscope.binary_context_managers.full_system import FullSystem -from software.thunderscope.binary_context_managers.simulator import Simulator -from software.thunderscope.binary_context_managers.game_controller import Gamecontroller -from software.thunderscope.thunderscope_config import configure_simulated_test_view -from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer - -from software.logger.logger import create_logger -from typing import override - -logger = create_logger(__name__) - -LAUNCH_DELAY_S = 0.1 -WORLD_BUFFER_TIMEOUT = 0.5 -PROCESS_BUFFER_DELAY_S = 0.01 -PAUSE_AFTER_FAIL_DELAY_S = 3 - - -class SimulatedTestRunner(TbotsTestRunner): - """Run a simulated test""" - - def __init__( - self, - test_name, - thunderscope, - simulator_proto_unix_io, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - ci_mode=False, - ): - """Initialize the SimulatorTestRunner - - :param test_name: The name of the test to run - :param thunderscope: The Thunderscope to use, None if not used - :param simulator_proto_unix_io: The simulator proto unix io to use - :param blue_full_system_proto_unix_io: The blue full system proto unix io to use - :param yellow_full_system_proto_unix_io: The yellow full system proto unix io to use - :param gamecontroller: The gamecontroller context managed instance - :param ci_mode: Run test as fast as possible - """ - super(SimulatedTestRunner, self).__init__( - test_name, - thunderscope, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - ) - self.simulator_proto_unix_io = simulator_proto_unix_io - self.ci_mode = ci_mode - - @override - def set_world_state(self, worldstate: WorldState): - """Sets the simulation worldstate - - :param worldstate: proto containing the desired worldstate - """ - self.simulator_proto_unix_io.send_proto(WorldState, worldstate) - - def excepthook(self, args): - """This function is _critical_ for show_thunderscope to work. - If the test Thread will raises an exception we won't be able to close - the window from the main thread. - - :param args: The args passed in from the hook - """ - self.__stopper(delay=PAUSE_AFTER_FAIL_DELAY_S) - self.last_exception = args.exc_value - raise self.last_exception - - def __stopper(self, delay=PROCESS_BUFFER_DELAY_S): - """Stop running the test - - :param delay: How long to wait before closing everything, defaults - to PROCESS_BUFFER_DELAY_S to minimize buffer warnings - """ - time.sleep(delay) - - if self.thunderscope: - self.thunderscope.close() - - def sync_setup(self, setup, param): - """Run setup until simulator has received game state - - :param setup: Function that sets up the world state - :param param: Parameter passed into setup - """ - world_state_received_buffer = ThreadSafeBuffer(1, WorldStateReceivedTrigger) - self.simulator_proto_unix_io.register_observer( - WorldStateReceivedTrigger, world_state_received_buffer - ) - - while True: - setup(param) - - try: - world_state_received_buffer.get( - block=True, timeout=WORLD_BUFFER_TIMEOUT - ) - except queue.Empty: - # Did not receive a response within timeout period - continue - else: - # Received a response from the simulator - break - - def runner( - self, - always_validation_sequence_set, - eventually_validation_sequence_set, - test_timeout_s, - tick_duration_s, - ci_cmd_with_delay, - run_till_end, - ): - """Run a test - - :param always_validation_sequence_set: Validation functions that should - hold on every tick - :param eventually_validation_sequence_set: Validation that should - eventually be true, before the test ends - :param test_timeout_s: The timeout for the test, if any eventually_validations - remain after the timeout, the test fails. - :param tick_duration_s: The simulation step duration - :param ci_cmd_with_delay: A list consisting of tuples with a duration and CI command, e.g. - [ - (time, command, team), - (time, command, team), - ... - ] - :param run_till_end: If true, test runs till the end even if eventually validation passes - If false, test stops once eventually validation passes and fails if time out - """ - time_elapsed_s = 0 - - eventually_validation_failure_msg = "Test Timed Out" - - while time_elapsed_s < test_timeout_s: - # get time before we execute the loop - processing_start_time = time.time() - - # Check for new CI commands at this time step - for delay, cmd, team in ci_cmd_with_delay: - # If delay matches time - if delay <= time_elapsed_s: - # send command - self.gamecontroller.send_gc_command(gc_command=cmd, team=team) - # remove command from the list - ci_cmd_with_delay.remove((delay, cmd, team)) - - tick = SimulatorTick(milliseconds=tick_duration_s * MILLISECONDS_PER_SECOND) - self.simulator_proto_unix_io.send_proto(SimulatorTick, tick) - time_elapsed_s += tick_duration_s - - while True: - try: - world = self.world_buffer.get( - block=True, timeout=WORLD_BUFFER_TIMEOUT, return_cached=False - ) - - # We block until the timeout for the new primitives from AI. if not found still, - # the SSL Wrapper packet is resent in a loop until we actually get a primitive set from AI - # Otherwise, if the AI misses the first SSL Wrapper packet and doesn't start - # the simulated test will continue to tick forward, causes syncing issues with the AI - self.primitive_set_buffer.get( - block=True, timeout=WORLD_BUFFER_TIMEOUT, return_cached=False - ) - - break - except queue.Empty: - # If we timeout, that means full_system missed the last - # wrapper and robot status, lets resend it. - logger.warning("Fullsystem missed last wrapper, resending ...") - - ssl_wrapper = self.ssl_wrapper_buffer.get(block=False) - robot_status = self.robot_status_buffer.get(block=False) - - self.blue_full_system_proto_unix_io.send_proto( - SSL_WrapperPacket, ssl_wrapper - ) - self.blue_full_system_proto_unix_io.send_proto( - RobotStatus, robot_status - ) - - # get the time difference after we get the primitive (after any blocking that happened) - processing_time = time.time() - processing_start_time - - # if the time we have blocked is less than a tick, sleep for the remaining time (for Thunderscope only) - if ( - self.thunderscope - and not self.ci_mode - and tick_duration_s > processing_time - ): - time.sleep(tick_duration_s - processing_time) - - # Validate - ( - eventually_validation_proto_set, - always_validation_proto_set, - ) = validation.run_validation_sequence_sets( - world, - eventually_validation_sequence_set, - always_validation_sequence_set, - ) - - # Set the test name - eventually_validation_proto_set.test_name = self.test_name - always_validation_proto_set.test_name = self.test_name - - # Send out the validation proto to the full system - # for visualization and logging for replays. - if self.is_yellow_friendly: - self.yellow_full_system_proto_unix_io.send_proto( - ValidationProtoSet, eventually_validation_proto_set - ) - self.yellow_full_system_proto_unix_io.send_proto( - ValidationProtoSet, always_validation_proto_set - ) - else: - self.blue_full_system_proto_unix_io.send_proto( - ValidationProtoSet, eventually_validation_proto_set - ) - self.blue_full_system_proto_unix_io.send_proto( - ValidationProtoSet, always_validation_proto_set - ) - - # Check that all always validations are always valid - validation.check_validation(always_validation_proto_set) - - if not run_till_end: - try: - # Check that all eventually validations are eventually valid - validation.check_validation(eventually_validation_proto_set) - self.__stopper() - return - except AssertionError as e: - eventually_validation_failure_msg = str(e) - - if not run_till_end: - raise AssertionError(eventually_validation_failure_msg) - - # Check that all eventually validations are eventually valid - validation.check_validation(eventually_validation_proto_set) - - self.__stopper() - - @override - def run_test( - self, - always_validation_sequence_set, - eventually_validation_sequence_set, - test_timeout_s=3, - tick_duration_s=0.0166, # Default to 60hz - index=0, - ci_cmd_with_delay=[], - run_till_end=True, - **kwargs, - ): - """Helper function to run a test, with thunderscope if enabled - - :param always_validation_sequence_set: validation that should always be true - :param eventually_validation_sequence_set: validation that should eventually be true - :param test_timeout_s: how long the test should run before timing out - :param tick_duration_s: length of a tick - :param index: index of the current test. default is 0 (invariant test) - values can be passed in during aggregate testing for different timeout durations - :param ci_cmd_with_delay: A list consisting of tuples with a duration and CI command, e.g. - [ - (time, command, team), - (time, command, team), - ... - ] - :param run_till_end: If true, test runs till the end even if eventually validation passes - If false, test stops once eventually validation passes and fails if time out - """ - test_timeout_duration = ( - test_timeout_s[index] if type(test_timeout_s) == list else test_timeout_s - ) - - # If thunderscope is enabled, run the test in a thread and show - # thunderscope on this thread. The excepthook is setup to catch - # any test failures and propagate them to the main thread - if self.thunderscope: - run_sim_thread = threading.Thread( - target=self.runner, - daemon=True, - args=[ - always_validation_sequence_set, - eventually_validation_sequence_set, - test_timeout_duration, - tick_duration_s, - ci_cmd_with_delay, - run_till_end, - ], - ) - run_sim_thread.start() - self.thunderscope.show() - run_sim_thread.join() - - if self.last_exception: - pytest.fail(str(self.last_exception)) - - # If thunderscope is disabled, just run the test - else: - self.runner( - always_validation_sequence_set, - eventually_validation_sequence_set, - test_timeout_duration, - tick_duration_s, - ci_cmd_with_delay=ci_cmd_with_delay, - run_till_end=run_till_end, - ) - - -class InvariantTestRunner(SimulatedTestRunner): - """Runs a simulated test only once with a given parameter - - Test passes or fails based on the outcome of this test - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @override - def run_test( - self, - setup=(lambda x: None), - params=[0], - inv_always_validation_sequence_set=[[]], - inv_eventually_validation_sequence_set=[[]], - **kwargs, - ): - """Run an invariant test - - :param setup: Function that sets up the World state and the gamecontroller before running the test - :param params: List of parameters for each iteration of the test - (this method only uses the first element) - :param inv_always_validation_sequence_set: Validation functions for invariant testing - that should hold on every tick - :param inv_eventually_validation_sequence_set: Validation functions for invariant testing - that should eventually be true, before the test ends - """ - threading.excepthook = self.excepthook - - super().sync_setup(setup, params[0]) - - super().run_test( - inv_always_validation_sequence_set, - inv_eventually_validation_sequence_set, - **kwargs, - ) - - -class AggregateTestRunner(SimulatedTestRunner): - """Runs a simulated test multiple times with different given parameters - - Result of the test is determined by comparing the number of - passing iterations to a predetermined acceptable threshold - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @override - def run_test( - self, - setup=(lambda arg: None), - params=[], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], - **kwargs, - ): - """Run an aggregate test - - :param setup: Function that sets up the World state and the gamecontroller before running the test - :param params: List of parameters for each iteration of the test - :param ag_always_validation_sequence_set: Validation functions for aggregate testing - that should hold on every tick - :param ag_eventually_validation_sequence_set: Validation functions for aggregate testing - that should eventually be true, before the test end - """ - threading.excepthook = self.excepthook - - failed_tests = 0 - - # Runs the test once for each given parameter - # Catches Assertion Error thrown by failing test and increments counter - # Calculates overall results and prints them - for x in range(len(params)): - super().sync_setup(setup, params[x]) - - try: - super().run_test( - ag_always_validation_sequence_set, - ag_eventually_validation_sequence_set, - **kwargs, - ) - except AssertionError: - failed_tests += 1 - - # TODO (#2856) Fix validation and results output - - logger.info(f"{failed_tests} test failed") - - assert failed_tests == 0 - - -def get_runtime_dir(): - """Gets the base runtime directory for the test execution. - - TODO: Refactor #3744 - - Creates a new persistent directory for each test so that tests - running in parallel do not interfere with each other. - - :return: The path to the runtime directory. - """ - import uuid - - runtime_dir = os.path.join("/tmp", f"tbots_{uuid.uuid4().hex[:8]}") - os.makedirs(runtime_dir, exist_ok=True) - return runtime_dir - - -RUNTIME_DIR = get_runtime_dir() - - -def load_command_line_arguments(allow_unrecognized: bool = False) -> argparse.Namespace: - """Load command line arguments. - - We aren't using pytest.ini because it does not allow for dynamic defaults, - which we need to use TEST_TMPDIR when it's available. - - We aren't using conftest.py's pytest_addoption because we want to be able to - run the gamecontroller script directly from python without pytest. - Pytest does have a way to access parsed options (request.config.getoption), - but it doesn't seem to play nicely with bazel. We just use argparse instead. - - :param allow_unrecognized: if true, does not raise an error for unrecognized arguments - """ - parser = argparse.ArgumentParser(description="Run simulated pytests") - parser.add_argument( - "--enable_thunderscope", action="store_true", help="enable thunderscope" - ) - parser.add_argument( - "--ci_mode", - action="store_true", - default=False, - help="Run simulator at faster speed", - ) - parser.add_argument( - "--aggregate", action="store_true", default=False, help="Run aggregate test" - ) - parser.add_argument( - "--simulator_runtime_dir", - type=str, - help="simulator runtime directory", - default=RUNTIME_DIR, - ) - parser.add_argument( - "--blue_full_system_runtime_dir", - type=str, - help="blue full_system runtime directory", - default=os.path.join(RUNTIME_DIR, "blue"), - ) - parser.add_argument( - "--yellow_full_system_runtime_dir", - type=str, - help="yellow full_system runtime directory", - default=os.path.join(RUNTIME_DIR, "yellow"), - ) - parser.add_argument( - "--layout", - action="store", - help="Which layout to run, if not specified the last layout will run", - ) - parser.add_argument( - "--debug_blue_full_system", - action="store_true", - default=False, - help="Debug blue full_system", - ) - parser.add_argument( - "--debug_yellow_full_system", - action="store_true", - default=False, - help="Debug yellow full_system", - ) - parser.add_argument( - "--debug_simulator", - action="store_true", - default=False, - help="Debug the simulator", - ) - parser.add_argument( - "--visualization_buffer_size", - action="store", - type=int, - default=5, - help="How many packets to buffer while rendering", - ) - parser.add_argument( - "--show_gamecontroller_logs", - action="store_true", - default=False, - help="Show gamecontroller logs", - ) - parser.add_argument( - "--test_filter", - action="store", - default="", - help="The test filter, if not specified all tests will run. " - + "See https://docs.pytest.org/en/latest/how-to/usage.html#specifying-tests-selecting-tests", - ) - parser.add_argument( - "--enable_realism", - action="store_true", - default=False, - help="Use realism in the simulator", - ) - return parser.parse_known_args()[0] if allow_unrecognized else parser.parse_args() - - -def pytest_main(file): - """Runs the pytest file - - :param file: The test file to run - """ - args = load_command_line_arguments(allow_unrecognized=True) - - # Run the test, -s disables all capturing at -vv increases verbosity - # -W ignore::DeprecationWarning ignores deprecation warnings that spam the output - sys.exit( - pytest.main( - ["-svv", "-W ignore::DeprecationWarning", "-k", args.test_filter, file] - ) - ) - - -@pytest.fixture -def simulated_test_runner(): - args = load_command_line_arguments() - tscope = None - - aggregate = args.aggregate - - simulator_proto_unix_io = ProtoUnixIO() - yellow_full_system_proto_unix_io = ProtoUnixIO() - blue_full_system_proto_unix_io = ProtoUnixIO() - - # Grab the current test name to store the proto log for the test case - current_test = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0] - current_test = current_test.replace("]", "") - current_test = current_test.replace("[", "-") - - # Truncate the test name to 25 characters for UNIX path length limits - test_name = current_test.split("-")[0][:25] - - # Launch all binaries - with Simulator( - f"{args.simulator_runtime_dir}/test/{test_name}", - args.debug_simulator, - args.enable_realism, - ) as simulator, FullSystem( - "software/unix_full_system", - f"{args.blue_full_system_runtime_dir}/test/{test_name}", - args.debug_blue_full_system, - False, - should_restart_on_crash=False, - running_in_realtime=args.enable_thunderscope and not args.ci_mode, - ) as blue_fs, FullSystem( - "software/unix_full_system", - f"{args.yellow_full_system_runtime_dir}/test/{test_name}", - args.debug_yellow_full_system, - True, - should_restart_on_crash=False, - running_in_realtime=args.enable_thunderscope and not args.ci_mode, - ) as yellow_fs: - with Gamecontroller( - suppress_logs=(not args.show_gamecontroller_logs) - ) as gamecontroller: - blue_fs.setup_proto_unix_io(blue_full_system_proto_unix_io) - yellow_fs.setup_proto_unix_io(yellow_full_system_proto_unix_io) - simulator.setup_proto_unix_io( - simulator_proto_unix_io, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - ProtoUnixIO(), - ) - gamecontroller.setup_proto_unix_io( - blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, - simulator_proto_unix_io=simulator_proto_unix_io, - ) - - # If we want to run thunderscope, inject the proto unix ios - # and start the test - if args.enable_thunderscope: - tscope = Thunderscope( - configure_simulated_test_view( - blue_full_system_proto_unix_io=blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io, - simulator_proto_unix_io=simulator_proto_unix_io, - ), - layout_path=args.layout, - ) - - time.sleep(LAUNCH_DELAY_S) - - runner = None - - # Initialise the right runner based on which testing mode is selected - if aggregate: - runner = AggregateTestRunner( - current_test, - tscope, - simulator_proto_unix_io, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - args.ci_mode, - ) - else: - runner = InvariantTestRunner( - current_test, - tscope, - simulator_proto_unix_io, - blue_full_system_proto_unix_io, - yellow_full_system_proto_unix_io, - gamecontroller, - args.ci_mode, - ) - - yield runner diff --git a/src/software/gameplay_tests/simulated_test_runner.py b/src/software/gameplay_tests/simulated_test_runner.py new file mode 100644 index 0000000000..26c851710a --- /dev/null +++ b/src/software/gameplay_tests/simulated_test_runner.py @@ -0,0 +1,203 @@ +import queue +import time +from typing import override + +from software.py_constants import ( + DEFAULT_SIMULATOR_TICK_RATE_SECONDS_PER_TICK, + MILLISECONDS_PER_SECOND, +) + +from proto.import_all_protos import * +from software.gameplay_tests.tbots_test_runner import TbotsTestRunner +from software.gameplay_tests.validation import validation +from software.logger.logger import create_logger +from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer + +logger = create_logger(__name__) + +LAUNCH_DELAY_S = 0.1 +WORLD_BUFFER_TIMEOUT = 0.5 +TICK_DURATION_S = DEFAULT_SIMULATOR_TICK_RATE_SECONDS_PER_TICK + + +class SimulatedTestRunner(TbotsTestRunner): + """Run a simulated test""" + + def __init__( + self, + test_name, + thunderscope, + simulator_proto_unix_io, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + gamecontroller, + ci_mode=False, + ): + """Initialize the SimulatorTestRunner + + :param test_name: The name of the test to run + :param thunderscope: The Thunderscope to use, None if not used + :param simulator_proto_unix_io: The simulator proto unix io to use + :param blue_full_system_proto_unix_io: The blue full system proto unix io to use + :param yellow_full_system_proto_unix_io: The yellow full system proto unix io to use + :param gamecontroller: The gamecontroller context managed instance + :param ci_mode: Run test as fast as possible + """ + super(SimulatedTestRunner, self).__init__( + test_name, + thunderscope, + blue_full_system_proto_unix_io, + yellow_full_system_proto_unix_io, + gamecontroller, + ) + self.simulator_proto_unix_io = simulator_proto_unix_io + self.ci_mode = ci_mode + + @override + def set_world_state(self, worldstate: WorldState): + """Sets the simulation worldstate + + :param worldstate: proto containing the desired worldstate + """ + self.simulator_proto_unix_io.send_proto(WorldState, worldstate) + + @override + def _pre_run_setup(self, setup: (lambda: None)): + """Run setup until simulator has received game state + + :param setup: Function that sets up the world state + """ + world_state_received_buffer = ThreadSafeBuffer(1, WorldStateReceivedTrigger) + self.simulator_proto_unix_io.register_observer( + WorldStateReceivedTrigger, world_state_received_buffer + ) + + while True: + setup() + + try: + world_state_received_buffer.get( + block=True, timeout=WORLD_BUFFER_TIMEOUT + ) + except queue.Empty: + # Did not receive a response within timeout period + continue + else: + # Received a response from the simulator + break + + @override + def _runner( + self, + always_validation_sequence_set, + eventually_validation_sequence_set, + test_timeout_s, + gc_cmd_with_delay, + ): + time.sleep(LAUNCH_DELAY_S) + + time_elapsed_s = 0 + + while time_elapsed_s < test_timeout_s: + processing_start_time = time.time() + + # Check for new GC commands at this time step + for delay, cmd, team in gc_cmd_with_delay: + # If delay matches time + if delay <= time_elapsed_s: + # send command + self.gamecontroller.send_gc_command(gc_command=cmd, team=team) + # remove command from the list + gc_cmd_with_delay.remove((delay, cmd, team)) + + tick = SimulatorTick(milliseconds=TICK_DURATION_S * MILLISECONDS_PER_SECOND) + self.simulator_proto_unix_io.send_proto(SimulatorTick, tick) + time_elapsed_s += TICK_DURATION_S + + while True: + try: + world = self.world_buffer.get( + block=True, timeout=WORLD_BUFFER_TIMEOUT, return_cached=False + ) + + # We block until the timeout for the new primitives from AI. if not found still, + # the SSL Wrapper packet is resent in a loop until we actually get a primitive set from AI + # Otherwise, if the AI misses the first SSL Wrapper packet and doesn't start + # the simulated test will continue to tick forward, causes syncing issues with the AI + self.primitive_set_buffer.get( + block=True, timeout=WORLD_BUFFER_TIMEOUT, return_cached=False + ) + + break + except queue.Empty: + # If we timeout, that means full_system missed the last + # wrapper and robot status, lets resend it. + logger.warning("Fullsystem missed last wrapper, resending ...") + + ssl_wrapper = self.ssl_wrapper_buffer.get(block=False) + robot_status = self.robot_status_buffer.get(block=False) + + self.blue_full_system_proto_unix_io.send_proto( + SSL_WrapperPacket, ssl_wrapper + ) + self.blue_full_system_proto_unix_io.send_proto( + RobotStatus, robot_status + ) + + # get the time difference after we get the primitive (after any blocking that happened) + processing_time = time.time() - processing_start_time + + # if the time we have blocked is less than a tick, sleep for the remaining time (for Thunderscope only) + if ( + self.thunderscope + and not self.ci_mode + and TICK_DURATION_S > processing_time + ): + time.sleep(TICK_DURATION_S - processing_time) + + # Validate + ( + eventually_validation_proto_set, + always_validation_proto_set, + ) = validation.run_validation_sequence_sets( + world, + eventually_validation_sequence_set, + always_validation_sequence_set, + ) + + # Set the test name + eventually_validation_proto_set.test_name = self.test_name + always_validation_proto_set.test_name = self.test_name + + # Send out the validation proto to the full system + # for visualization and logging for replays. + if self.is_yellow_friendly: + self.yellow_full_system_proto_unix_io.send_proto( + ValidationProtoSet, eventually_validation_proto_set + ) + self.yellow_full_system_proto_unix_io.send_proto( + ValidationProtoSet, always_validation_proto_set + ) + else: + self.blue_full_system_proto_unix_io.send_proto( + ValidationProtoSet, eventually_validation_proto_set + ) + self.blue_full_system_proto_unix_io.send_proto( + ValidationProtoSet, always_validation_proto_set + ) + + if len(always_validation_sequence_set) != 0: + # Check that all always validations are always valid + validation.check_validation(always_validation_proto_set) + else: + # If there are no always validations, check eventually validations to end test early + try: + validation.check_validation(eventually_validation_proto_set) + break + except AssertionError: + pass + + # Check that all eventually validations are eventually valid + validation.check_validation(eventually_validation_proto_set) + + self._stopper() diff --git a/src/software/gameplay_tests/tbots_test_runner.py b/src/software/gameplay_tests/tbots_test_runner.py index 03e7d4ae85..33797ee282 100644 --- a/src/software/gameplay_tests/tbots_test_runner.py +++ b/src/software/gameplay_tests/tbots_test_runner.py @@ -1,10 +1,15 @@ +import threading +import time + +import pytest + from proto.import_all_protos import * -from software.logger.logger import create_logger from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from abc import abstractmethod from typing import Any -logger = create_logger(__name__) +PAUSE_AFTER_FAIL_DELAY_S = 3 +PROCESS_BUFFER_DELAY_S = 0.01 class TbotsTestRunner: @@ -120,6 +125,99 @@ def set_plays(self, blue_play: PlayName, yellow_play: PlayName): self.blue_full_system_proto_unix_io.send_proto(Play, Play(name=blue_play)) self.yellow_full_system_proto_unix_io.send_proto(Play, Play(name=yellow_play)) + @abstractmethod + def set_world_state(self, world_state: WorldState): + """Sets the initial world state of the test. + + :param world_state: The WorldState proto to use + """ + raise NotImplementedError("abstract class method called set_world_state") + + def run_test( + self, + setup: (lambda: None), + always_validation_sequence_set=[], + eventually_validation_sequence_set=[], + test_timeout_s=3, + gc_cmd_with_delay=[], + ): + """Begins validating a test based on incoming world protos. + Runs the test in a background thread if thunderscope is enabled. + + :param setup: Function that sets up the world state + :param always_validation_sequence_set: validation set that must always be true + :param eventually_validation_sequence_set: validation set that must eventually be true + :param test_timeout_s: how long the test will run + :param gc_cmd_with_delay: timed GC commands + """ + self._pre_run_setup(setup) + + threading.excepthook = self._excepthook + + args = ( + always_validation_sequence_set, + eventually_validation_sequence_set, + test_timeout_s, + gc_cmd_with_delay, + ) + + if self.thunderscope: + run_test_thread = threading.Thread( + target=self._runner, daemon=True, args=args + ) + run_test_thread.start() + self.thunderscope.show() + run_test_thread.join() + + if self.last_exception: + pytest.fail(str(self.last_exception)) + else: + self._runner(*args) + + @abstractmethod + def _runner( + self, + always_validation_sequence_set, + eventually_validation_sequence_set, + test_timeout_s, + gc_cmd_with_delay, + ): + """Internal test loop; implemented by subclasses. + + See run_test() method for param docs. + """ + raise NotImplementedError("abstract class method called _runner") + + def _pre_run_setup(self, setup: (lambda: None)): + """Hook called before the test loop starts. Override in subclasses that + need to synchronize setup with external systems (e.g. simulator). + + :param setup: Function that sets up the world state + """ + setup() + + def _stopper(self, delay=PROCESS_BUFFER_DELAY_S): + """Stop running the test + + :param delay: How long to wait before closing everything, defaults + to PROCESS_BUFFER_DELAY_S to minimize buffer warnings + """ + time.sleep(delay) + + if self.thunderscope: + self.thunderscope.close() + + def _excepthook(self, args): + """This function is _critical_ for show_thunderscope to work. + If the test Thread will raises an exception we won't be able to close + the window from the main thread. + + :param args: The args passed in from the hook + """ + self._stopper(delay=PAUSE_AFTER_FAIL_DELAY_S) + self.last_exception = args.exc_value + raise self.last_exception + def _create_assigned_tactic_params(self, tactics: dict[int, Any]): """Converts dict of tactics to AssignedTacticPlayControlParams message @@ -136,26 +234,3 @@ def _create_assigned_tactic_params(self, tactics: dict[int, Any]): break return params - - @abstractmethod - def set_world_state(self, worldstate: WorldState): - """Sets the worldstate for the given team - - :param worldstate: the worldstate proto to use - """ - raise NotImplementedError("abstract class method called set_world_state") - - @abstractmethod - def run_test( - self, - always_validation_sequence_set=[[]], - eventually_validation_sequence_set=[[]], - test_timeout_s=3, - ): - """Begins validating a test based on incoming world protos - - :param always_validation_sequence_set: validation set that must always be true - :param eventually_validation_sequence_set: validation set that must eventually be true - :param test_timeout_s: how long the test will run - """ - raise NotImplementedError("abstract method run_test called from base class") diff --git a/src/software/gameplay_tests/util.py b/src/software/gameplay_tests/util.py new file mode 100644 index 0000000000..7603aacf7c --- /dev/null +++ b/src/software/gameplay_tests/util.py @@ -0,0 +1,200 @@ +import argparse +import os +import sys + +import pytest + + +def get_runtime_dir(): + """Gets the base runtime directory for the test execution. + + Creates a new persistent directory for each test so that tests + running in parallel do not interfere with each other. + + :return: The path to the runtime directory. + """ + import uuid + + runtime_dir = os.path.join("/tmp", f"tbots_{uuid.uuid4().hex[:8]}") + os.makedirs(runtime_dir, exist_ok=True) + return runtime_dir + + +def get_pytest_name(): + """Gets the name of the currently running pytest test. + + Sanitizes the test name by replacing square brackets with hyphens. + + :return: The sanitized test name. + """ + current_test = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0] + current_test = current_test.replace("]", "") + current_test = current_test.replace("[", "-") + return current_test + + +def get_pytest_path_name(): + """Gets the base test component name, truncated for filesystem compatibility. + + Extracts just the test name (before any parametrize suffix) and truncates + to 25 characters to adhere to UNIX path length limits. + + :return: The truncated base test name. + """ + # Truncate the test name to 25 characters for UNIX path length limits + return get_pytest_name().split("-")[0][:25] + + +def load_command_line_arguments(allow_unrecognized: bool = False): + """Load in command-line arguments using argparse + + NOTE: Pytest has its own built in argument parser (conftest.py, pytest_addoption) + but it doesn't seem to play nicely with bazel. We just use argparse instead. + + :param allow_unrecognized: if true, does not raise an error for unrecognized arguments + """ + RUNTIME_DIR = get_runtime_dir() + + parser = argparse.ArgumentParser( + description="Run simulated or field gameplay tests" + ) + + general_group = parser.add_argument_group("General test arguments") + general_group.add_argument( + "--run_field_test", + action="store_true", + default=False, + help="Runs test as a field test instead of a simulated test", + ) + general_group.add_argument( + "--test_filter", + action="store", + default="", + help="The test filter, if not specified all tests will run. " + + "See https://docs.pytest.org/en/latest/how-to/usage.html#specifying-tests-selecting-tests", + ) + general_group.add_argument( + "--blue_full_system_runtime_dir", + type=str, + help="Blue full_system runtime directory", + default=os.path.join(RUNTIME_DIR, "blue"), + ) + general_group.add_argument( + "--yellow_full_system_runtime_dir", + type=str, + help="Yellow full_system runtime directory", + default=os.path.join(RUNTIME_DIR, "yellow"), + ) + general_group.add_argument( + "--layout", + action="store", + help="Which layout to run, if not specified the last layout will run", + ) + general_group.add_argument( + "--debug_blue_full_system", + action="store_true", + default=False, + help="Debug blue full_system", + ) + general_group.add_argument( + "--debug_yellow_full_system", + action="store_true", + default=False, + help="Debug yellow full_system", + ) + general_group.add_argument( + "--show_gamecontroller_logs", + action="store_true", + default=False, + help="Show gamecontroller logs", + ) + + simulated_group = parser.add_argument_group("Simulated test arguments") + simulated_group.add_argument( + "--ci_mode", + action="store_true", + default=False, + help="Run simulator at faster speed", + ) + simulated_group.add_argument( + "--simulator_runtime_dir", + type=str, + help="Simulator runtime directory", + default=RUNTIME_DIR, + ) + simulated_group.add_argument( + "--debug_simulator", + action="store_true", + default=False, + help="Debug the simulator", + ) + simulated_group.add_argument( + "--enable_thunderscope", action="store_true", help="Enable thunderscope" + ) + simulated_group.add_argument( + "--enable_realism", + action="store_true", + default=False, + help="Use realism in the simulator", + ) + + field_group = parser.add_argument_group("Field test arguments") + field_group.add_argument( + "--interface", + action="store", + type=str, + default=None, + help="Which interface to communicate over", + ) + field_group.add_argument( + "--channel", + action="store", + type=int, + default=0, + help="Which channel to communicate over", + ) + field_group.add_argument( + "--estop_baudrate", + action="store", + type=int, + default=115200, + help="Estop Baudrate", + ) + field_group.add_argument( + "--run_yellow", + action="store_true", + default=False, + help="Run the test with friendly robots in yellow mode", + ) + + estop_group = field_group.add_mutually_exclusive_group() + estop_group.add_argument( + "--keyboard_estop", + action="store_true", + default=False, + help="Allows the use of the spacebar as an estop instead of a physical one", + ) + estop_group.add_argument( + "--disable_communication", + action="store_true", + default=False, + help="Disables checking for estop plugged in (ONLY USE FOR LOCAL TESTING)", + ) + + return parser.parse_known_args()[0] if allow_unrecognized else parser.parse_args() + + +def pytest_main(file): + """Runs the pytest file + + :param file: The test file to run + """ + args = load_command_line_arguments(allow_unrecognized=True) + + # Run the test, -s disables all capturing at -vv increases verbosity + # -W ignore::DeprecationWarning ignores deprecation warnings that spam the output + sys.exit( + pytest.main( + ["-svv", "-W ignore::DeprecationWarning", "-k", args.test_filter, file] + ) + ) diff --git a/src/software/gameplay_tests/validation/delay_validation.py b/src/software/gameplay_tests/validation/delay_validation.py index 29f455ba6b..4e14989fc9 100644 --- a/src/software/gameplay_tests/validation/delay_validation.py +++ b/src/software/gameplay_tests/validation/delay_validation.py @@ -10,6 +10,7 @@ class DelayValidation(Validation): def __init__(self, delay_s, validation): """A validation wrapper that adds a delay to given validation before being evaluated""" + # TODO (#3786): rewrite without DEFAULT_SIMULATOR_TICK_RATE_SECONDS_PER_TICK for field tests self.delay_s = delay_s self.ticks_so_far = 0 self.delay_ticks = int(delay_s / DEFAULT_SIMULATOR_TICK_RATE_SECONDS_PER_TICK) diff --git a/src/software/gameplay_tests/validation/duration_validation.py b/src/software/gameplay_tests/validation/duration_validation.py index c400493a61..5457967df6 100644 --- a/src/software/gameplay_tests/validation/duration_validation.py +++ b/src/software/gameplay_tests/validation/duration_validation.py @@ -15,6 +15,7 @@ def __init__(self, duration_s, validation): "Type of validation needs to be EVENTUALLY for DurationValidation" ) + # TODO (#3786): rewrite without DEFAULT_SIMULATOR_TICK_RATE_SECONDS_PER_TICK for field tests self.duration_s = duration_s self.passing_ticks = 0 self.duration_ticks = int( diff --git a/src/software/sensor_fusion/filter/ball_occlusion_test.py b/src/software/sensor_fusion/filter/ball_occlusion_test.py index e2cfaac9da..54d15e52d4 100644 --- a/src/software/sensor_fusion/filter/ball_occlusion_test.py +++ b/src/software/sensor_fusion/filter/ball_occlusion_test.py @@ -7,9 +7,7 @@ from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import Command from proto.ssl_gc_common_pb2 import Team -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main @pytest.mark.parametrize( @@ -171,10 +169,10 @@ def test_ball_occlusion( ball_velocity, blue_robot_positions, yellow_robot_positions, - simulated_test_runner, + gameplay_test_runner, ): - def setup(*args): - simulated_test_runner.set_world_state( + def setup(): + gameplay_test_runner.set_world_state( create_world_state( blue_robot_locations=blue_robot_positions, yellow_robot_locations=yellow_robot_positions, @@ -183,18 +181,18 @@ def setup(*args): ), ) - simulated_test_runner.send_gamecontroller_command( + gameplay_test_runner.send_gamecontroller_command( gc_command=Command.Type.HALT, team=Team.UNKNOWN ) - simulated_test_runner.set_plays( + gameplay_test_runner.set_plays( blue_play=PlayName.HaltPlay, yellow_play=PlayName.HaltPlay ) # This test validates that the ball tracking/filter handles occlusion correctly. # The validation simply waits for the simulation to run long enough (10s). # If the system crashes or has tracking issues, the test will fail. - simulated_test_runner.run_test( + gameplay_test_runner.run_test( setup=setup, test_timeout_s=10, ) diff --git a/src/software/simulation/BUILD b/src/software/simulation/BUILD index 8c2b7a4f92..c0f351547d 100644 --- a/src/software/simulation/BUILD +++ b/src/software/simulation/BUILD @@ -1,3 +1,5 @@ +load("@gameplay_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -35,3 +37,15 @@ cc_test( "//software/world", ], ) + +py_test( + name = "simulator_ball_test", + srcs = [ + "simulator_ball_test.py", + ], + deps = [ + "//software:conftest", + "//software/gameplay_tests/validation:validations", + requirement("pytest"), + ], +) diff --git a/src/software/gameplay_tests/simulated_test_ball_model.py b/src/software/simulation/simulator_ball_test.py similarity index 61% rename from src/software/gameplay_tests/simulated_test_ball_model.py rename to src/software/simulation/simulator_ball_test.py index e0f491177d..27344d1e2d 100644 --- a/src/software/gameplay_tests/simulated_test_ball_model.py +++ b/src/software/simulation/simulator_ball_test.py @@ -10,9 +10,7 @@ from software.gameplay_tests.validation.ball_stops_in_region import * from software.gameplay_tests.validation.excessive_dribbling import * from proto.message_translation.tbots_protobuf import create_world_state -from software.gameplay_tests.simulated_test_fixture import ( - pytest_main, -) +from software.gameplay_tests.util import pytest_main # the friction model currently used in the er-force simulator @@ -46,31 +44,19 @@ def test_simulator_move_ball( ball_initial_position, ball_initial_velocity, - simulated_test_runner, + gameplay_test_runner, ): - # Setup Ball - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Tactic - params = AssignedTacticPlayControlParams() - - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + def setup(): + # Setup Ball + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[], + yellow_robot_locations=[], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ), + ) + gameplay_test_runner.set_tactics() # expected ball position initial_v = ball_initial_velocity.length() @@ -104,52 +90,30 @@ def test_simulator_move_ball( ] ] - simulated_test_runner.run_test( + gameplay_test_runner.run_test( + setup=setup, test_timeout_s=8, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + eventually_validation_sequence_set=eventually_validation_sequence_set, + always_validation_sequence_set=always_validation_sequence_set, ) -def test_ball_robot_collision(simulated_test_runner): +def test_ball_robot_collision(gameplay_test_runner): ball_initial_position = tbots_cpp.Field.createSSLDivisionBField().centerPoint() ball_initial_velocity = tbots_cpp.Vector(2.5, 0) robot_position = tbots_cpp.Point(2.5, 0) - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[robot_position], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Ball - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Tactic - params = AssignedTacticPlayControlParams() - - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + def setup(): + # Setup Robot + gameplay_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[robot_position], + yellow_robot_locations=[], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ), + ) + gameplay_test_runner.set_tactics() # expected ball position initial_v = ball_initial_velocity.length() @@ -170,9 +134,6 @@ def test_ball_robot_collision(simulated_test_runner): distance_from_robot = (ball_expected_position - robot_position).length() - # Always Validation - always_validation_sequence_set = [] - # Eventually Validation eventually_validation_sequence_set = [ [ @@ -182,9 +143,9 @@ def test_ball_robot_collision(simulated_test_runner): ] ] - simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + gameplay_test_runner.run_test( + setup=setup, + eventually_validation_sequence_set=eventually_validation_sequence_set, ) diff --git a/src/software/thunderscope/replay/test/replay_corruption_test.py b/src/software/thunderscope/replay/test/replay_corruption_test.py index 8c9bd67dae..7d43c1ca3e 100644 --- a/src/software/thunderscope/replay/test/replay_corruption_test.py +++ b/src/software/thunderscope/replay/test/replay_corruption_test.py @@ -26,7 +26,7 @@ from software.thunderscope.constants import ProtoPlayerFlags from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main random.seed(0) diff --git a/src/software/thunderscope/replay/test/replay_indexing_test.py b/src/software/thunderscope/replay/test/replay_indexing_test.py index 30391729da..4d721e5d26 100644 --- a/src/software/thunderscope/replay/test/replay_indexing_test.py +++ b/src/software/thunderscope/replay/test/replay_indexing_test.py @@ -13,7 +13,7 @@ from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.gameplay_tests.simulated_test_fixture import pytest_main +from software.gameplay_tests.util import pytest_main from software.thunderscope.replay.test.replay_corruption_test import ( create_valid_log_entry, create_random_proto,