84e786e531
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 <noreply@anthropic.com>
96 lines
4.1 KiB
GDScript
96 lines
4.1 KiB
GDScript
extends TestCase
|
|
## What milestone 1 promises: two instances of the game, each in a process of
|
|
## its own, stay in lockstep. This process is the host; a second Godot
|
|
## process plays a guest with tools/guest_bot.gd.
|
|
|
|
## The tick at which the host stops and both compare their worlds.
|
|
const UNTIL := 150
|
|
## Milliseconds the test waits for the second instance.
|
|
const PATIENCE_MSEC := 30000
|
|
|
|
|
|
func test_a_guest_in_another_process_stays_in_lockstep() -> void:
|
|
var directory := TestFiles.temporary_dir("two-instances")
|
|
var host: Node = load("res://main.tscn").instantiate()
|
|
host.storage_dir = directory.path_join("host")
|
|
host.command_line = PackedStringArray()
|
|
tree.root.add_child(host)
|
|
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([
|
|
"--headless",
|
|
"--path", ProjectSettings.globalize_path("res://"),
|
|
"--log-file", ProjectSettings.globalize_path(directory.path_join("guest.log")),
|
|
"-s", "res://tools/guest_bot.gd",
|
|
"--",
|
|
"--join=127.0.0.1:%d" % host.link.link.local_port(),
|
|
"--storage=" + directory.path_join("guest"),
|
|
"--until=%d" % UNTIL,
|
|
"--out=" + report_path,
|
|
])
|
|
var process := OS.create_process(OS.get_executable_path(), arguments)
|
|
check(process > 0, "the second instance starts")
|
|
var started := Time.get_ticks_msec()
|
|
while OS.is_process_running(process) and Time.get_ticks_msec() - started < PATIENCE_MSEC:
|
|
if host.session.guest_count() > 0:
|
|
# Once the guest is there, the test runs the host's ticks itself,
|
|
# one per frame, instead of twenty a second, and stops at UNTIL.
|
|
# A guest's arrival ends a pause, so the clock is stopped here.
|
|
host.clock.paused = true
|
|
if host.session.tick() < UNTIL:
|
|
host._run_tick()
|
|
await tree.process_frame
|
|
if OS.is_process_running(process):
|
|
OS.kill(process)
|
|
check(false, "the second instance did not finish in time")
|
|
|
|
var report: Variant = JSON.parse_string(FileAccess.get_file_as_string(report_path))
|
|
check(report is Dictionary and not report.has("error"), "the guest reports: %s" % [report])
|
|
if report is Dictionary and not report.has("error"):
|
|
check_eq(int(report["tick"]), UNTIL, "the guest stopped where the host stopped")
|
|
check_eq(report["hash"], host.session.state_hash_hex(), "both worlds are the same")
|
|
check_eq(int(report["player"]), 1, "the guest is player 1")
|
|
check_eq(int(report["stones"]), 1, "the guest broke the rock")
|
|
check_eq(int(report["resyncs"]), 0, "without a divergence on the way")
|
|
var rock: Vector2i = host.session.spawn_point() + Vector2i(7, -2)
|
|
check_eq(host.session.terrain_at(rock.x, rock.y), GwSession.TERRAIN_GROUND, "the rock is gone in the host's world too")
|
|
check_eq(host.session.player_name(1), "Bot", "the host knows the guest")
|
|
|
|
tree.root.remove_child(host)
|
|
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)
|