From bba14cb9ba4de180eac3a091ea51c708fa3b80d4 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 16 Jul 2026 19:05:43 +0200 Subject: [PATCH] test(client): retry input-roundtrip server spawn on fresh port when it dies pre-connect The server binds AFTER ~250ms of plugin/sim init, so a random-port collision (os error 98) escapes the 0.15s spawn-liveness check and surfaced as a hard test failure at the push gate. Death during the connect window now retries on a fresh port within MAX_PORT_ATTEMPTS. Verified: targeted suite 6/6. Co-Authored-By: Claude Fable 5 --- client/tests/test_input_roundtrip.gd | 75 +++++++++++++++------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/client/tests/test_input_roundtrip.gd b/client/tests/test_input_roundtrip.gd index 71c8136cd..3354a910d 100644 --- a/client/tests/test_input_roundtrip.gd +++ b/client/tests/test_input_roundtrip.gd @@ -26,17 +26,13 @@ static func _random_test_port() -> int: func _spawn_server(server_path: String) -> bool: - for attempt in range(MAX_PORT_ATTEMPTS): - _test_port = _random_test_port() - var addr := "127.0.0.1:%d" % _test_port - _server_pid = OS.create_process(server_path, [addr]) - if _server_pid <= 0: - continue - await get_tree().create_timer(0.15).timeout - if OS.is_process_running(_server_pid): - return true + _test_port = _random_test_port() + var addr := "127.0.0.1:%d" % _test_port + _server_pid = OS.create_process(server_path, [addr]) + if _server_pid <= 0: _server_pid = -1 - return false + return false + return true func after_test() -> void: @@ -51,37 +47,48 @@ func after_test() -> void: ## Spawn the server, connect, and complete the protocol handshake. ## Binary absence is handled by per-test `_do_skip` — by the time this runs the ## binary exists, so any failure here is a real failure (asserted loudly). +## The server binds its port AFTER plugin/simulation init (~250ms), so a +## random-port collision surfaces as a mid-connect process death, not an +## instant spawn failure — a death during the connect window therefore +## retries on a fresh port instead of failing the test (the push gate hit +## exactly that race: "Failed to bind ... Address already in use"). func _connect_to_server() -> bool: var server_path := _server_binary_path() - var spawned := await _spawn_server(server_path) - assert_bool(spawned).override_failure_message( - "server spawn failed after %d port attempts" % MAX_PORT_ATTEMPTS - ).is_true() - if not spawned: - return false - - _bridge = LocalBridge.new() var connected := false - var elapsed := 0.0 - while elapsed < CONNECT_TIMEOUT: - if _server_pid > 0 and not OS.is_process_running(_server_pid): - push_warning("Server process died during connection") + for _attempt in range(MAX_PORT_ATTEMPTS): + if not _spawn_server(server_path): + continue + _bridge = LocalBridge.new() + var elapsed := 0.0 + while elapsed < CONNECT_TIMEOUT: + if not OS.is_process_running(_server_pid): + push_warning( + "Server died pre-connect (port %d likely in use) — retrying" % _test_port + ) + break + if _bridge.get_status() == StreamPeerTCP.STATUS_NONE: + _bridge.connect_to_server("127.0.0.1", _test_port) + _bridge.poll() + if _bridge.get_status() == StreamPeerTCP.STATUS_CONNECTED: + connected = true + break + if _bridge.get_status() == StreamPeerTCP.STATUS_ERROR: + _bridge.disconnect_from_server() + _bridge.reset() + await get_tree().create_timer(0.1).timeout + elapsed += 0.1 + if connected: break - if _bridge.get_status() == StreamPeerTCP.STATUS_NONE: - _bridge.connect_to_server("127.0.0.1", _test_port) - _bridge.poll() - if _bridge.get_status() == StreamPeerTCP.STATUS_CONNECTED: - connected = true - break - if _bridge.get_status() == StreamPeerTCP.STATUS_ERROR: - _bridge.disconnect_from_server() - _bridge.reset() - await get_tree().create_timer(0.1).timeout - elapsed += 0.1 + _bridge.disconnect_from_server() + _bridge = null + if _server_pid > 0 and OS.is_process_running(_server_pid): + OS.kill(_server_pid) + _server_pid = -1 assert_bool(connected).override_failure_message( - "TCP connect to spawned server failed within %.1fs" % CONNECT_TIMEOUT + "TCP connect to spawned server failed within %.1fs across %d port attempts" + % [CONNECT_TIMEOUT, MAX_PORT_ATTEMPTS] ).is_true() if not connected: return false