From 84e786e5319bf289f9492bf1cdf2e51109c87f3e Mon Sep 17 00:00:00 2001 From: Steven Wroblewski Date: Thu, 1 Oct 2026 23:24:10 +0200 Subject: [PATCH] =?UTF-8?q?Zwei-Instanzen-Test=20unabh=C3=A4ngig=20von=20d?= =?UTF-8?q?er=20Startzeit,=20Bot=20endet=20immer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Uhr des Hosts wartet jetzt auf den Gast. So kann ein langsamer Start des zweiten Prozesses die Takte bis zum Vergleich nicht mehr verbrauchen. Der Bot endet auch dann, wenn er seinen Bericht nicht schreiben kann, und überlebt den Test nie. Ein leeres --storage= führt ihn nicht mehr in den Ordner des Spielers. Ein neuer Test prüft, dass der Bot ohne schreibbaren Bericht mit Exit-Code 1 endet. Co-Authored-By: Claude Haiku 4.5 --- game/tests/test_two_instances.gd | 30 ++++++++++++++++++++++++++++++ game/tools/guest_bot.gd | 18 +++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/game/tests/test_two_instances.gd b/game/tests/test_two_instances.gd index 965dd2d..2d9b897 100644 --- a/game/tests/test_two_instances.gd +++ b/game/tests/test_two_instances.gd @@ -18,6 +18,9 @@ func test_a_guest_in_another_process_stays_in_lockstep() -> void: host.options.port = 0 host.listen_address = "127.0.0.1" check(host.play(true), "the world opens") + # The host's clock waits for the guest, so that a slow start of the second + # process cannot use up the ticks before UNTIL. + host.clock.paused = true var report_path := directory.path_join("guest.json") var arguments := PackedStringArray([ @@ -63,3 +66,30 @@ func test_a_guest_in_another_process_stays_in_lockstep() -> void: host.link.link.close() host.free() TestFiles.remove_dir(directory) + + +func test_a_guest_bot_that_cannot_write_its_report_still_ends() -> void: + var directory := TestFiles.temporary_dir("bot-report") + var arguments := PackedStringArray([ + "--headless", + "--path", ProjectSettings.globalize_path("res://"), + "--log-file", ProjectSettings.globalize_path(directory.path_join("bot.log")), + "-s", "res://tools/guest_bot.gd", + "--", + # An address that cannot be read ends the session at once. + "--join=host:port", + "--storage=" + directory.path_join("bot"), + # A folder cannot be opened as the report file. + "--out=" + ProjectSettings.globalize_path(directory), + ]) + var process := OS.create_process(OS.get_executable_path(), arguments) + check(process > 0, "the bot starts") + var started := Time.get_ticks_msec() + while OS.is_process_running(process) and Time.get_ticks_msec() - started < PATIENCE_MSEC: + await tree.process_frame + var ended := not OS.is_process_running(process) + if not ended: + OS.kill(process) + check(ended, "the bot ends") + check_eq(OS.get_process_exit_code(process), 1, "and reports a failure") + TestFiles.remove_dir(directory) diff --git a/game/tools/guest_bot.gd b/game/tools/guest_bot.gd index 55df730..786459d 100644 --- a/game/tools/guest_bot.gd +++ b/game/tools/guest_bot.gd @@ -24,7 +24,9 @@ func _run() -> void: var report := {"error": "the host did not answer"} var main: Node = load("res://main.tscn").instantiate() - main.storage_dir = settings.get("--storage", "user://bot") + # Never the player's own folder, even for an empty --storage=. + var storage: String = settings.get("--storage", "") + main.storage_dir = storage if not storage.is_empty() else "user://bot" main.command_line = PackedStringArray(["--join=" + settings.get("--join", ""), "--name=Bot"]) root.add_child(main) @@ -50,10 +52,16 @@ func _run() -> void: } break - var file := FileAccess.open(settings.get("--out", "user://bot.json"), FileAccess.WRITE) - file.store_string(JSON.stringify(report)) - file.close() + # The bot ends in every case, even if the report cannot be written, so that + # it never outlives the test that started it. + var path: String = settings.get("--out", "user://bot.json") + var file := FileAccess.open(path, FileAccess.WRITE) + if file == null: + push_error("the report cannot be written to %s: %s" % [path, error_string(FileAccess.get_open_error())]) + else: + file.store_string(JSON.stringify(report)) + file.close() main.notification(Node.NOTIFICATION_WM_CLOSE_REQUEST) root.remove_child(main) main.free() - quit(0 if not report.has("error") else 1) + quit(0 if file != null and not report.has("error") else 1)