diff --git a/server/ladder_service.py b/server/ladder_service.py index ccfb5722f..1cf9ef666 100644 --- a/server/ladder_service.py +++ b/server/ladder_service.py @@ -469,36 +469,24 @@ def game_options(player: Player) -> GameLaunchOptions: map_position=game.get_player_option(player.id, "StartSpot") ) + # Tell the host to launch await host.lobby_connection.launch_game( game, is_host=True, options=game_options(host) ) - try: - await game.wait_hosted(60) - finally: - # TODO: Once the client supports `match_cancelled`, don't - # send `launch_game` to the client if the host timed out. Until - # then, failing to send `launch_game` will cause the client to - # think it is searching for ladder, even though the server has - # already removed it from the queue. - - await asyncio.gather(*[ - guest.lobby_connection.launch_game( - game, is_host=False, options=game_options(guest) - ) - for guest in all_guests - if guest.lobby_connection is not None - ]) - await game.wait_launched(60 + 10 * len(all_guests)) - self._logger.debug("Ladder game launched successfully %s", game) - except Exception as e: - if isinstance(e, asyncio.TimeoutError): - self._logger.info( - "Ladder game failed to start! %s setup timed out", - game + await game.wait_hosted(60) + + # Tell the guests to launch + await asyncio.gather(*[ + guest.lobby_connection.launch_game( + game, is_host=False, options=game_options(guest) ) - else: - self._logger.exception("Ladder game failed to start %s", game) + for guest in all_guests + if guest.lobby_connection is not None + ]) + await game.wait_launched(60 + 10 * len(all_guests)) + self._logger.debug("Ladder game launched successfully %s", game) + except Exception: if game: await game.on_game_end() diff --git a/tests/integration_tests/test_matchmaker.py b/tests/integration_tests/test_matchmaker.py index 2ac079901..2f2d621fe 100644 --- a/tests/integration_tests/test_matchmaker.py +++ b/tests/integration_tests/test_matchmaker.py @@ -167,20 +167,11 @@ async def test_game_matchmaking_start_while_matched(lobby_server): async def test_game_matchmaking_timeout(lobby_server, game_service): proto1, proto2 = await queue_players_for_matchmaking(lobby_server) - msg1, msg2 = await asyncio.gather( - read_until_command(proto1, "game_launch", timeout=120), - read_until_command(proto2, "game_launch", timeout=120) - ) - # LEGACY BEHAVIOUR: The host does not respond with the appropriate GameState - # so the match is cancelled. However, the client does not know how to - # handle `match_cancelled` messages so we still send `game_launch` to - # prevent the client from showing that it is searching when it really isn't. + msg1 = await read_until_command(proto1, "game_launch", timeout=120) await read_until_command(proto2, "match_cancelled", timeout=120) await read_until_command(proto1, "match_cancelled", timeout=120) - assert msg1["uid"] == msg2["uid"] assert msg1["mod"] == "ladder1v1" - assert msg2["mod"] == "ladder1v1" # Ensure that the game is cleaned up await read_until_command( @@ -191,7 +182,15 @@ async def test_game_matchmaking_timeout(lobby_server, game_service): ) assert game_service._games == {} - # Player's state is reset once they leave the game + # Player's state is not reset immediately + await proto1.send_message({ + "command": "game_matchmaking", + "state": "start", + }) + with pytest.raises(asyncio.TimeoutError): + await read_until_command(proto1, "search_info", state="start", timeout=5) + + # Player's state is only reset once they leave the game await proto1.send_message({ "command": "GameState", "target": "game", @@ -200,18 +199,15 @@ async def test_game_matchmaking_timeout(lobby_server, game_service): await proto1.send_message({ "command": "game_matchmaking", "state": "start", - "faction": "uef" }) await read_until_command(proto1, "search_info", state="start", timeout=5) - # And not before they've left the game + # But it is reset for the player who didn't make it into the game await proto2.send_message({ "command": "game_matchmaking", "state": "start", - "faction": "uef" }) - with pytest.raises(asyncio.TimeoutError): - await read_until_command(proto2, "search_info", state="start", timeout=5) + await read_until_command(proto2, "search_info", state="start", timeout=5) @fast_forward(120) diff --git a/tests/integration_tests/test_teammatchmaker.py b/tests/integration_tests/test_teammatchmaker.py index ecbba8ce1..70c4eb350 100644 --- a/tests/integration_tests/test_teammatchmaker.py +++ b/tests/integration_tests/test_teammatchmaker.py @@ -324,7 +324,6 @@ async def test_game_matchmaking_multiqueue_timeout(lobby_server): await protos[0].send_message({ "command": "game_matchmaking", "state": "start", - "faction": "cybran", "queue_name": "ladder1v1" }) await read_until_command(protos[0], "search_info", state="start") @@ -332,7 +331,6 @@ async def test_game_matchmaking_multiqueue_timeout(lobby_server): proto.send_message({ "command": "game_matchmaking", "state": "start", - "faction": "seraphim", "queue_name": "tmm2v2" }) for proto in protos @@ -348,6 +346,14 @@ async def test_game_matchmaking_multiqueue_timeout(lobby_server): # Don't send any GPGNet messages so the match times out await read_until_command(protos[0], "match_cancelled", timeout=120) + # Player's state is not reset immediately + await protos[0].send_message({ + "command": "game_matchmaking", + "state": "start", + }) + with pytest.raises(asyncio.TimeoutError): + await read_until_command(protos[1], "search_info", state="start", timeout=5) + # Player's state is reset once they leave the game await protos[0].send_message({ "command": "GameState", @@ -357,7 +363,6 @@ async def test_game_matchmaking_multiqueue_timeout(lobby_server): await protos[0].send_message({ "command": "game_matchmaking", "state": "start", - "faction": "uef" }) await read_until_command( protos[0], @@ -367,15 +372,6 @@ async def test_game_matchmaking_multiqueue_timeout(lobby_server): timeout=5 ) - # And not before they've left the game - await protos[1].send_message({ - "command": "game_matchmaking", - "state": "start", - "faction": "uef" - }) - with pytest.raises(asyncio.TimeoutError): - await read_until_command(protos[1], "search_info", state="start", timeout=5) - @fast_forward(60) async def test_game_matchmaking_multiqueue_multimatch(lobby_server): @@ -397,7 +393,6 @@ async def test_game_matchmaking_multiqueue_multimatch(lobby_server): proto.send_message({ "command": "game_matchmaking", "state": "start", - "faction": "uef", "queue_name": "ladder1v1" }) for proto in protos[:2] @@ -406,7 +401,6 @@ async def test_game_matchmaking_multiqueue_multimatch(lobby_server): proto.send_message({ "command": "game_matchmaking", "state": "start", - "faction": "aeon", "queue_name": "tmm2v2" }) for proto in protos @@ -460,7 +454,6 @@ async def test_game_matchmaking_timeout(lobby_server): await protos[0].send_message({ "command": "game_matchmaking", "state": "start", - "faction": "uef" }) await read_until_command( protos[0], @@ -470,15 +463,6 @@ async def test_game_matchmaking_timeout(lobby_server): timeout=5 ) - # And not before they've left the game - await protos[1].send_message({ - "command": "game_matchmaking", - "state": "start", - "faction": "uef" - }) - with pytest.raises(asyncio.TimeoutError): - await read_until_command(protos[1], "search_info", state="start", timeout=5) - @fast_forward(120) async def test_game_ratings(lobby_server): diff --git a/tests/unit_tests/test_ladder_service.py b/tests/unit_tests/test_ladder_service.py index 9ed034961..0ea7a027d 100644 --- a/tests/unit_tests/test_ladder_service.py +++ b/tests/unit_tests/test_ladder_service.py @@ -153,7 +153,6 @@ async def test_start_game_1v1( game = game_service[game_service.game_id_counter] assert player1.lobby_connection.launch_game.called - # TODO: Once client supports `match_cancelled` change this to `assert not` assert player2.lobby_connection.launch_game.called assert isinstance(game, LadderGame) assert game.rating_type == queue.rating_type @@ -210,11 +209,9 @@ async def test_start_game_timeout( p1.lobby_connection.write.assert_called_once_with({"command": "match_cancelled"}) p2.lobby_connection.write.assert_called_once_with({"command": "match_cancelled"}) assert p1.lobby_connection.launch_game.called - # TODO: Once client supports `match_cancelled` change this to `assert not` - # and uncomment the following lines. - assert p2.lobby_connection.launch_game.called - # assert p1.state is PlayerState.IDLE - # assert p2.state is PlayerState.IDLE + assert not p2.lobby_connection.launch_game.called + assert p1.state is PlayerState.IDLE + assert p2.state is PlayerState.IDLE @given(