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)