88import json
99import logging
1010import os
11+ import socket
1112import subprocess
1213import sys
1314import time
@@ -50,21 +51,32 @@ def __init__(
5051 sample_name : str ,
5152 script_name : str ,
5253 endpoint : str = "/responses" , # Default endpoint
53- base_url : str = "http://localhost:8088" ,
54+ base_url : Optional [ str ] = None ,
5455 env_vars : Optional [Dict [str , str ]] = None ,
5556 timeout : int = 120 ,
57+ port : Optional [int ] = None ,
5658 ):
5759 self .sample_name = sample_name
5860 self .script_name = script_name
59- self .base_url = base_url
6061 self .endpoint = endpoint
6162 self .timeout = timeout
6263
6364 # Setup paths
6465 self .project_root = project_root # Use already defined project_root
65- self .sample_dir = self .project_root / "samples" / "python" / sample_name
66+ self .sample_dir = self .project_root / "samples" / sample_name
6667 self .original_dir = os .getcwd ()
6768
69+ # Determine port assignment priority: explicit param > env override > random
70+ if env_vars and env_vars .get ("DEFAULT_AD_PORT" ):
71+ self .port = int (env_vars ["DEFAULT_AD_PORT" ])
72+ elif port is not None :
73+ self .port = port
74+ else :
75+ self .port = self ._find_free_port ()
76+
77+ # Configure base URL for client requests
78+ self .base_url = (base_url or f"http://127.0.0.1:{ self .port } " ).rstrip ("/" )
79+
6880 # Setup environment
6981 # Get Agent Framework configuration (new format)
7082 azure_ai_project_endpoint = os .getenv ("AZURE_AI_PROJECT_ENDPOINT" , "" )
@@ -118,13 +130,29 @@ def __init__(
118130 if env_vars :
119131 self .env_vars .update (env_vars )
120132
133+ # Ensure server picks the dynamically assigned port and clients know how to reach it
134+ self .env_vars .setdefault ("DEFAULT_AD_PORT" , str (self .port ))
135+ self .env_vars .setdefault ("AGENT_BASE_URL" , self .base_url )
136+
121137 self .process = None
122138 self .session = requests .Session ()
123139
140+ @staticmethod
141+ def _find_free_port () -> int :
142+ with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as sock :
143+ sock .bind (("127.0.0.1" , 0 ))
144+ return sock .getsockname ()[1 ]
145+
124146 def setup (self ):
125147 """Setup test environment."""
126148 os .chdir (self .sample_dir )
127149
150+ logger .info (
151+ "Configured %s to listen on %s" ,
152+ self .sample_name ,
153+ f"{ self .base_url } { self .endpoint } " ,
154+ )
155+
128156 # Validate critical environment variables
129157 # For Agent Framework samples, check new env vars first
130158 required_vars = []
@@ -175,10 +203,17 @@ def setup(self):
175203
176204 def start_server (self ):
177205 """Start the agent server."""
178- logger .info (f"Starting { self .sample_name } server in { self .sample_dir } " )
206+ logger .info (
207+ "Starting %s server in %s on port %s" ,
208+ self .sample_name ,
209+ self .sample_dir ,
210+ self .port ,
211+ )
179212
180213 env = os .environ .copy ()
181214 env .update (self .env_vars )
215+ env ["DEFAULT_AD_PORT" ] = str (self .port )
216+ env .setdefault ("AGENT_BASE_URL" , self .base_url )
182217
183218 # Use unbuffered output to capture logs in real-time
184219 self .process = subprocess .Popen (
@@ -195,7 +230,11 @@ def start_server(self):
195230
196231 def wait_for_ready (self , max_attempts : int = 30 , delay : float = 1.0 ) -> bool :
197232 """Wait for server to be ready."""
198- logger .info (f"Waiting for server to be ready (max { max_attempts } attempts)" )
233+ logger .info (
234+ "Waiting for server to be ready at %s (max %s attempts)" ,
235+ f"{ self .base_url } { self .endpoint } " ,
236+ max_attempts ,
237+ )
199238
200239 for i in range (max_attempts ):
201240 # Check process status first
0 commit comments