Merge remote-tracking branch 'origin/main' into copilot/fix-azure-functions-worker-crashes

# Conflicts:
#	.github/workflows/python-integration-tests.yml
#	.github/workflows/python-merge-tests.yml
This commit is contained in:
copilot-swe-agent[bot]
2026-06-01 08:51:26 +00:00
committed by GitHub
Unverified
3362 changed files with 336173 additions and 82638 deletions
@@ -1,6 +1,6 @@
# Azure OpenAI Configuration
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=your-deployment-name
AZURE_OPENAI_MODEL=your-deployment-name
FUNCTIONS_WORKER_RUNTIME=python
# Azure Functions Configuration
@@ -14,7 +14,7 @@ cp .env.example .env
Required variables:
- `AZURE_OPENAI_ENDPOINT`
- `AZURE_OPENAI_CHAT_DEPLOYMENT_NAME`
- `AZURE_OPENAI_MODEL`
- `AZURE_OPENAI_API_KEY`
- `AzureWebJobsStorage`
- `DURABLE_TASK_SCHEDULER_CONNECTION_STRING`
@@ -111,13 +111,17 @@ def _should_skip_azure_functions_integration_tests() -> tuple[bool, str]:
f"Durable Task Scheduler emulator not running on port {_DTS_EMULATOR_PORT}. Start with: docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest", # noqa: E501
)
endpoint = os.getenv("AZURE_OPENAI_ENDPOINT", "").strip()
if not endpoint or endpoint == "https://your-resource.openai.azure.com/":
return True, "No real AZURE_OPENAI_ENDPOINT provided; skipping integration tests."
deployment_name = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "").strip()
if not deployment_name or deployment_name == "your-deployment-name":
return True, "No real AZURE_OPENAI_CHAT_DEPLOYMENT_NAME provided; skipping integration tests."
has_foundry_config = bool(os.getenv("FOUNDRY_PROJECT_ENDPOINT", "").strip()) and bool(
os.getenv("FOUNDRY_MODEL", "").strip()
)
has_azure_openai_config = bool(os.getenv("AZURE_OPENAI_ENDPOINT", "").strip()) and bool(
os.getenv("AZURE_OPENAI_MODEL", "").strip()
)
if not has_foundry_config and not has_azure_openai_config:
return (
True,
"No real FOUNDRY_* or AZURE_OPENAI_* configuration provided; skipping integration tests.",
)
return False, "Integration tests enabled."
@@ -322,22 +326,22 @@ def _is_port_in_use(port: int, host: str = _DEFAULT_HOST) -> bool:
return sock.connect_ex((host, port)) == 0
def _load_and_validate_env() -> None:
def _load_and_validate_env(sample_path: Path) -> None:
"""Load .env file from current directory if it exists, then validate required environment variables.
Raises pytest.fail if required environment variables are missing.
"""
_load_env_file_if_present()
# Required environment variables for Azure Functions samples
# These match the variables defined in .env.example
required_env_vars = [
"AZURE_OPENAI_ENDPOINT",
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME",
"AzureWebJobsStorage",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING",
"FUNCTIONS_WORKER_RUNTIME",
]
if sample_path.name == "11_workflow_parallel":
required_env_vars.extend(["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_MODEL"])
else:
required_env_vars.extend(["FOUNDRY_PROJECT_ENDPOINT", "FOUNDRY_MODEL"])
# Check if required env vars are set
missing_vars = [var for var in required_env_vars if not os.environ.get(var)]
@@ -541,7 +545,7 @@ def function_app_for_test(request: pytest.FixtureRequest) -> Iterator[dict[str,
assert sample_path is not None, "Sample path must be resolved before starting the function app"
# Load .env file if it exists and validate required env vars
_load_and_validate_env()
_load_and_validate_env(sample_path)
max_attempts = 3
# The overall budget MUST be shorter than the pytest-timeout value
@@ -26,7 +26,6 @@ pytestmark = [
pytest.mark.integration,
pytest.mark.sample("03_reliable_streaming"),
pytest.mark.usefixtures("function_app_for_test"),
pytest.mark.skip(reason="Temp disabled to fix test instability - needs investigation into root cause"),
]
@@ -56,12 +55,11 @@ class TestSampleReliableStreaming:
# Wait a moment for the agent to start writing to Redis
time.sleep(2)
# Stream response from Redis with shorter timeout
# Note: We use text/plain to avoid SSE parsing complexity
# Stream response from Redis with longer timeout to account for LLM latency
stream_response = requests.get(
f"{self.stream_url}/{thread_id}",
headers={"Accept": "text/plain"},
timeout=30, # Shorter timeout for test
timeout=60,
)
assert stream_response.status_code == 200
@@ -83,7 +81,7 @@ class TestSampleReliableStreaming:
stream_response = requests.get(
f"{self.stream_url}/{thread_id}",
headers={"Accept": "text/event-stream"},
timeout=30, # Shorter timeout
timeout=60,
)
assert stream_response.status_code == 200
content_type = stream_response.headers.get("content-type", "")
@@ -42,6 +42,7 @@ class TestWorkflowParallel:
self.base_url = base_url
self.helper = sample_helper
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
def test_parallel_workflow_document_analysis(self) -> None:
"""Test parallel workflow with a standard document."""
payload = {
@@ -70,6 +71,7 @@ class TestWorkflowParallel:
assert status["runtimeStatus"] == "Completed"
assert "output" in status
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
def test_parallel_workflow_short_document(self) -> None:
"""Test parallel workflow with a short document."""
payload = {
@@ -89,6 +91,7 @@ class TestWorkflowParallel:
assert status["runtimeStatus"] == "Completed"
assert "output" in status
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
def test_parallel_workflow_technical_document(self) -> None:
"""Test parallel workflow with a technical document."""
payload = {
@@ -112,6 +115,7 @@ class TestWorkflowParallel:
status = self.helper.wait_for_orchestration_with_output(data["statusQueryGetUri"], max_wait=300)
assert status["runtimeStatus"] == "Completed"
@pytest.mark.skip(reason="xdist distributes module tests across workers, each spawning a func process")
def test_workflow_status_endpoint(self) -> None:
"""Test that the workflow status endpoint works correctly."""
payload = {