From c22b7e184eb447512fbe9edcded80f795cd0fcd3 Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Fri, 21 Nov 2025 00:22:42 +0100 Subject: [PATCH 1/2] TST: Use a pytest fixture when the layout is not important --- test/test_remote_game.py | 101 +++++++++------------------ test/test_team.py | 147 ++++++++++----------------------------- 2 files changed, 68 insertions(+), 180 deletions(-) diff --git a/test/test_remote_game.py b/test/test_remote_game.py index be4659e4c..ab169e179 100644 --- a/test/test_remote_game.py +++ b/test/test_remote_game.py @@ -37,6 +37,20 @@ def remote_teams(): with run_and_terminate_process(remote_food_eater): yield teams +@pytest.fixture +def dummy_layout(): + return """ + ########## + # b y # + #a .. x# + ########## + """ + +@pytest.fixture +def dummy_layout_dict(dummy_layout): + # Use this when a simple layout is needed but the details don’t matter + return pelita.layout.parse_layout(dummy_layout) + def test_remote_call_pelita(remote_teams): res, stdout, stderr = call_pelita(remote_teams, rounds=10, size='small', viewer='null', seed='2') @@ -47,39 +61,27 @@ def test_remote_call_pelita(remote_teams): assert res['timeouts'] == [None, None] -def test_remote_run_game(remote_teams): +def test_remote_run_game(remote_teams, dummy_layout_dict): # TODO: A failure here freezes pytest - layout = """ - ########## - # b y # - #a .. x# - ########## - """ - state = pelita.game.run_game(remote_teams, max_rounds=30, layout_dict=pelita.layout.parse_layout(layout)) + + state = pelita.game.run_game(remote_teams, max_rounds=30, layout_dict=dummy_layout_dict) assert state['whowins'] == 1 assert state['fatal_errors'] == [[], []] assert state['timeouts'] == [{}, {}] -def test_remote_timeout(): +def test_remote_timeout(dummy_layout_dict): # We have a slow player that also generates a bad move # in its second turn. # We need to detect both. # To avoid timing issues, the blue player will also need to be a bit slower - layout = """ - ########## - # b y # - #a .. x# - ########## - """ - blue = FIXTURE_DIR / 'remote_timeout_blue.py' red = FIXTURE_DIR / 'remote_timeout_red.py' state = pelita.game.run_game([str(blue), str(red)], max_rounds=8, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, timeout_length=0.4) assert state['whowins'] == 0 @@ -95,13 +97,7 @@ def test_remote_timeout(): @pytest.mark.parametrize("failing_team", [0, 1]) -def test_bad_team_name(failing_team): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ +def test_bad_team_name(failing_team, dummy_layout_dict): failing_player = FIXTURE_DIR / 'player_bad_team_name.py' good_player = "0" @@ -113,7 +109,7 @@ def test_bad_team_name(failing_team): state = pelita.game.run_game(teams, max_rounds=8, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, timeout_length=0.4) assert state['whowins'] == -1 @@ -121,14 +117,7 @@ def test_bad_team_name(failing_team): assert "longer than 25" in state['fatal_errors'][failing_team][0]['description'] -def test_remote_dumps_are_written(): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ - +def test_remote_dumps_are_written(dummy_layout_dict): blue = FIXTURE_DIR / 'remote_dumps_are_written_blue.py' red = FIXTURE_DIR / 'remote_dumps_are_written_red.py' @@ -138,7 +127,7 @@ def test_remote_dumps_are_written(): state = pelita.game.run_game([str(blue), str(red)], max_rounds=2, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, store_output=out_folder.name) assert state['whowins'] == 2 @@ -157,13 +146,7 @@ def test_remote_dumps_are_written(): @pytest.mark.parametrize("failing_team", [0, 1]) -def test_remote_dumps_with_failure(failing_team): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ +def test_remote_dumps_with_failure(failing_team, dummy_layout_dict): failing_player = FIXTURE_DIR / 'remote_dumps_with_failure_bad.py' good_player = FIXTURE_DIR / 'remote_dumps_with_failure_good.py' @@ -178,7 +161,7 @@ def test_remote_dumps_with_failure(failing_team): state = pelita.game.run_game(teams, max_rounds=2, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, store_output=out_folder.name) assert state['whowins'] == 1 - failing_team @@ -244,13 +227,7 @@ def test_remote_dumps_with_failure(failing_team): ['player_no_name', True, 'AttributeError'], ['player_syntax_error', True, 'SyntaxError'], ]) -def test_remote_move_failures(player_name, is_setup_error, error_type): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ +def test_remote_move_failures(player_name, is_setup_error, error_type, dummy_layout_dict): failing_player = FIXTURE_DIR / player_name good_player = FIXTURE_DIR / 'remote_dumps_with_failure_good.py' @@ -258,7 +235,7 @@ def test_remote_move_failures(player_name, is_setup_error, error_type): if is_setup_error: state = pelita.game.run_game([str(failing_player), str(good_player)], max_rounds=2, - layout_dict=pelita.layout.parse_layout(layout)) + layout_dict=dummy_layout_dict) assert state['whowins'] == -1 assert state['game_phase'] == 'FAILURE' @@ -274,7 +251,7 @@ def test_remote_move_failures(player_name, is_setup_error, error_type): else: state = pelita.game.run_game([str(failing_player), str(good_player)], max_rounds=2, - layout_dict=pelita.layout.parse_layout(layout)) + layout_dict=dummy_layout_dict) assert state['whowins'] == 1 assert state['game_phase'] == 'FINISHED' @@ -286,13 +263,7 @@ def test_remote_move_failures(player_name, is_setup_error, error_type): assert state['timeouts'] == [{}, {}] -def test_cleanup_timeout(): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ +def test_cleanup_timeout(dummy_layout_dict): failing_player = FIXTURE_DIR / 'player_long_cleanup.py' good_player = FIXTURE_DIR / 'remote_dumps_with_failure_good.py' @@ -301,7 +272,7 @@ def test_cleanup_timeout(): state = pelita.game.run_game([str(failing_player), str(good_player)], max_rounds=2, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, raise_bot_exceptions=True) duration = time.monotonic() - start @@ -315,20 +286,14 @@ def test_cleanup_timeout(): assert 1 < duration < 10 -def test_remote_initial_timeout_zero(): - layout = """ - ########## - # b y # - #a .. x# - ########## - """ +def test_remote_initial_timeout_zero(dummy_layout_dict): teams = ["0", "0"] state = pelita.game.run_game(teams, max_rounds=2, initial_timeout_length=6, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, raise_bot_exceptions=True) assert state['game_phase'] == 'FINISHED' @@ -338,7 +303,7 @@ def test_remote_initial_timeout_zero(): state = pelita.game.run_game(teams, max_rounds=2, initial_timeout_length=0, - layout_dict=pelita.layout.parse_layout(layout), + layout_dict=dummy_layout_dict, raise_bot_exceptions=True) assert state['game_phase'] == 'FAILURE' diff --git a/test/test_team.py b/test/test_team.py index fef53e2d9..71c9ee11b 100644 --- a/test/test_team.py +++ b/test/test_team.py @@ -8,6 +8,25 @@ from pelita.maze_generator import generate_maze from pelita.exceptions import PelitaBotError +@pytest.fixture +def dummy_layout(): + return """ + ################## + #.#... .##. y# + # # # . .### #x# + # ####. . # + # . .#### # + #a# ###. . # # # + #b .##. ...#.# + ################## + """ + +@pytest.fixture +def dummy_layout_dict(dummy_layout): + # Use this when a simple layout is needed but the details don’t matter + return parse_layout(dummy_layout) + + def stopping(bot, state): return bot.position @@ -633,19 +652,7 @@ def team_2(bot, state): assert state['team_time'][1] >= 2.0 -def test_bot_str_repr(): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - - parsed = parse_layout(test_layout) +def test_bot_str_repr(dummy_layout_dict): def asserting_team(bot, state): bot_str = str(bot).split('\n') @@ -660,27 +667,14 @@ def asserting_team(bot, state): return bot.position - state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=parsed, + state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) # assertions might have been caught in run_game # check that all is good assert state['fatal_errors'] == [[], []] -def test_bot_html_repr(): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - - parsed = parse_layout(test_layout) - +def test_bot_html_repr(dummy_layout_dict): def asserting_team(bot, state): # Not a full-fledged test at this time. We mainly want to catch API changes for now. @@ -689,26 +683,14 @@ def asserting_team(bot, state): return bot.position - state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=parsed, + state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) # assertions might have been caught in run_game # check that all is good assert state['fatal_errors'] == [[], []] -def test_bot_repr(): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - - parsed = parse_layout(test_layout) +def test_bot_repr(dummy_layout_dict): def asserting_team(bot, state): bot_repr = repr(bot) @@ -721,7 +703,7 @@ def asserting_team(bot, state): return bot.position - state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=parsed, + state = run_game([asserting_team, asserting_team], max_rounds=1, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) # assertions might have been caught in run_game # check that all is good @@ -739,25 +721,12 @@ def asserting_team(bot, state): ("123456789 123456789 123456789 1", "123456789 123456789 123456789 "), # too long ((0,0), "(0, 0)"), # not a string ]) -def test_bot_say(say, expected): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - - parsed = parse_layout(test_layout) - +def test_bot_say(say, expected, dummy_layout_dict): def speaking_team(bot, state): bot.say(say) return bot.position - state = setup_game([speaking_team, speaking_team], max_rounds=1, layout_dict=parsed, raise_bot_exceptions=True) + state = setup_game([speaking_team, speaking_team], max_rounds=1, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) idx = 0 while not state["gameover"]: state = play_turn(state) @@ -769,31 +738,19 @@ def speaking_team(bot, state): assert state['turn'] == 3 -def test_valid_paint_background(): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ +def test_valid_paint_background(dummy_layout_dict): overlay = [ ((0,0) , "#AABBCC"), ((1,1) , "#BBCCDD"), ((100, 200) , "#DDCCAA"), # silently ignore coords out of the maze ] - parsed = parse_layout(test_layout) - def overlay_team(bot, state): for pos, color in overlay: bot.paint_background(pos, color=color) return bot.position - state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=parsed, raise_bot_exceptions=True) + state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) overlay_in_maze = [{'pos':(0,0), 'color':"#AABBCC"}, {'pos':(1,1), 'color':"#BBCCDD"}] for idx in range(4): @@ -809,42 +766,19 @@ def overlay_team(bot, state): ((1, 2),'#AABBCCDDEE', 'Background color.+AABBCCDDEE.+'), # color uses alpha channel ((1, 2),'#GGGGGG', 'Background color.+GGGGGG.+'), # color out of range ]) -def test_paint_background_exceptions(pos, color, match): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - parsed = parse_layout(test_layout) - +def test_paint_background_exceptions(pos, color, match, dummy_layout_dict): def overlay_team(bot, state): bot.paint_background(pos, color) return bot.position - state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=parsed, raise_bot_exceptions=True) + state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) with pytest.raises(PelitaBotError, match=match): state = play_turn(state, raise_bot_exceptions=True) -def test_paint_background_doesnt_overwrite(): +def test_paint_background_doesnt_overwrite(dummy_layout_dict): # check that we do override color for a coordinate if already set and # that we don't touch other properties that may be defined on that coordinate - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - parsed = parse_layout(test_layout) pos = (0, 1) color = '#FFFFFF' @@ -854,7 +788,7 @@ def overlay_team(bot, state): bot.paint_background(pos, color) return bot.position - state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=parsed, raise_bot_exceptions=True) + state = setup_game([overlay_team, overlay_team], max_rounds=2, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) for idx in range(4): state = play_turn(state) @@ -862,18 +796,7 @@ def overlay_team(bot, state): assert gs_overlay == [{'pos':(0,1), 'color':'#FFFFFF', 'text':'something'}] -def test_paint_background_different_bots(): - test_layout = """ - ################## - #.#... .##. y# - # # # . .### #x# - # ####. . # - # . .#### # - #a# ###. . # # # - #b .##. ...#.# - ################## - """ - parsed = parse_layout(test_layout) +def test_paint_background_different_bots(dummy_layout_dict): # blue team pos0 = (0, 0) @@ -899,7 +822,7 @@ def overlay_team_red(bot, state): bot.paint_background(pos3, color3) return bot.position - state = setup_game([overlay_team_blue, overlay_team_red], max_rounds=2, layout_dict=parsed, raise_bot_exceptions=True) + state = setup_game([overlay_team_blue, overlay_team_red], max_rounds=2, layout_dict=dummy_layout_dict, raise_bot_exceptions=True) pos_color = [(pos0, color0), (pos1, color1), (pos2, color2), (pos3, color3)] for idx in range(4): From beb83cc3872f01503f731016c5a438dbe066feba Mon Sep 17 00:00:00 2001 From: Rike-Benjamin Schuppner Date: Fri, 21 Nov 2025 00:25:00 +0100 Subject: [PATCH 2/2] TST: Rewrite old class-based test --- test/test_team.py | 74 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/test/test_team.py b/test/test_team.py index 71c9ee11b..f6a5edfa3 100644 --- a/test/test_team.py +++ b/test/test_team.py @@ -34,44 +34,42 @@ def randomBot(bot, state): legal = bot.legal_positions[:] return bot.random.choice(legal) -class TestStoppingTeam: - @staticmethod - def round_counting(): - storage_copy = {} - def inner(bot, state): - print(state) - state[bot.turn] = state.get(bot.turn, 0) + 1 - storage_copy['rounds'] = state[bot.turn] - print(state) - return bot.position - inner._storage = storage_copy - return inner - - def test_stopping(self): - test_layout = ( - """ ############ - #a#.by .# x# - ############ """) - - round_counting = self.round_counting() - team = [ - stopping, - round_counting - ] - state = run_game(team, max_rounds=1, layout_dict=parse_layout(test_layout), raise_bot_exceptions=True) - assert state['bots'][0] == (1, 1) - assert state['bots'][1] == (10, 1) - assert round_counting._storage['rounds'] == 1 - - round_counting = self.round_counting() - team = [ - stopping, - round_counting - ] - state = run_game(team, max_rounds=3, layout_dict=parse_layout(test_layout), raise_bot_exceptions=True) - assert state['bots'][0] == (1, 1) - assert state['bots'][1] == (10, 1) - assert round_counting._storage['rounds'] == 3 +def mk_round_counting_team(): + storage_copy = {} + def team(bot, state): + print(state) + state[bot.turn] = state.get(bot.turn, 0) + 1 + storage_copy['rounds'] = state[bot.turn] + print(state) + return bot.position + team._storage = storage_copy + return team + +def test_round_counting(): + test_layout = ( + """ ############ + #a#.by .# x# + ############ """) + + round_counting_team = mk_round_counting_team() + team = [ + stopping, + round_counting_team + ] + state = run_game(team, max_rounds=1, layout_dict=parse_layout(test_layout), raise_bot_exceptions=True) + assert state['bots'][0] == (1, 1) + assert state['bots'][1] == (10, 1) + assert round_counting_team._storage['rounds'] == 1 + + round_counting_team = mk_round_counting_team() + team = [ + stopping, + round_counting_team + ] + state = run_game(team, max_rounds=3, layout_dict=parse_layout(test_layout), raise_bot_exceptions=True) + assert state['bots'][0] == (1, 1) + assert state['bots'][1] == (10, 1) + assert round_counting_team._storage['rounds'] == 3 def test_track_and_kill_count(): # for each team, we track whether they have been eaten at least once