Skip to content

Commit b5299d6

Browse files
committed
Add gRPC server startup timeout and improved error reporting
1 parent 968177c commit b5299d6

1 file changed

Lines changed: 46 additions & 9 deletions

File tree

src/shared/system_test_utilities.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,45 @@
88

99

1010
class GrpcServerProcess:
11+
_SERVER_STARTUP_TIMEOUT = 60
12+
1113
def __init__(self, config_file_path):
1214
server_exe = self._get_grpc_server_exe()
13-
self._proc = subprocess.Popen([str(server_exe), config_file_path], stdout=subprocess.PIPE)
14-
15-
# Read/parse output until we find the port number or the process exits; discard the rest.
15+
self._proc = subprocess.Popen(
16+
[str(server_exe), config_file_path],
17+
stdout=subprocess.PIPE,
18+
stderr=subprocess.PIPE,
19+
)
20+
self._stderr_lines = []
21+
self._stderr_thread = threading.Thread(target=self._capture_stderr, daemon=True)
22+
self._stderr_thread.start()
23+
24+
# Read/parse output until we find the port number, the process exits, or we time out.
1625
try:
1726
self.server_port = None
18-
while self.server_port is None and self._proc.poll() is None:
19-
line = self._proc.stdout.readline()
20-
match = re.search(rb"Server listening on port (\d+)", line)
21-
if match:
22-
self.server_port = int(match.group(1))
27+
reader_thread = threading.Thread(target=self._read_port, daemon=True)
28+
reader_thread.start()
29+
reader_thread.join(timeout=self._SERVER_STARTUP_TIMEOUT)
30+
31+
if reader_thread.is_alive():
32+
self._proc.kill()
33+
self._stderr_thread.join(timeout=5)
34+
stderr_output = b''.join(self._stderr_lines).decode(errors='replace').strip()
35+
msg = f"Server did not start within {self._SERVER_STARTUP_TIMEOUT} seconds"
36+
if stderr_output:
37+
msg += f"\nServer stderr:\n{stderr_output}"
38+
raise RuntimeError(msg)
2339

2440
if self._proc.poll() is not None:
25-
raise RuntimeError(f"Server exited with return code {self._proc.returncode}")
41+
self._stderr_thread.join(timeout=5)
42+
stderr_output = b''.join(self._stderr_lines).decode(errors='replace').strip()
43+
msg = f"Server exited with return code {self._proc.returncode}"
44+
if stderr_output:
45+
msg += f"\nServer stderr:\n{stderr_output}"
46+
raise RuntimeError(msg)
47+
48+
if self.server_port is None:
49+
raise RuntimeError("Server process ended without reporting a port")
2650

2751
self._stdout_thread = threading.Thread(target=self._discard_output, args=(self._proc.stdout,), daemon=True)
2852
self._stdout_thread.start()
@@ -52,6 +76,19 @@ def _get_grpc_server_exe(self):
5276
pytest.skip("NI gRPC Device Server not installed")
5377
return server_exe
5478

79+
def _read_port(self):
80+
while self.server_port is None and self._proc.poll() is None:
81+
line = self._proc.stdout.readline()
82+
if not line:
83+
break
84+
match = re.search(rb"Server listening on port (\d+)", line)
85+
if match:
86+
self.server_port = int(match.group(1))
87+
88+
def _capture_stderr(self):
89+
for line in self._proc.stderr:
90+
self._stderr_lines.append(line)
91+
5592
def _discard_output(self, stdout):
5693
while True:
5794
data = stdout.read(8196)

0 commit comments

Comments
 (0)