Skip to content

Commit d32dfbb

Browse files
authored
Fix Docker Test Container Connection Failure (#852)
* add retries * use windows * run sequentially * use windows * output complete exception * skip api version check * pin azure-storage-blob * revert other changes back * set timezone before setting env variables * output logs for failing tests * remove timezone implementation changes * configure automatic retries * simplify retry implementation
1 parent a5d9a75 commit d32dfbb

5 files changed

Lines changed: 165 additions & 4 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Azure Functions Test Kit
2+
3+
Reusable test kit for testing Azure Functions workers in Docker containers.
4+
5+
## Features
6+
7+
### 🔄 Automatic Test Retries
8+
9+
Flaky tests are automatically retried using [`pytest-rerunfailures`](https://github.com/pytest-dev/pytest-rerunfailures). This is especially useful for integration tests that may fail due to timing issues, network hiccups, or container startup delays.
10+
11+
**Default behavior** (configured in `pyproject.toml` via `addopts`):
12+
- Tests are automatically retried up to **2 times** on failure
13+
- **5 seconds delay** between retry attempts
14+
15+
**Override via command line:**
16+
17+
```bash
18+
# Disable retries completely
19+
pytest --reruns 0
20+
21+
# Set custom retry count
22+
pytest --reruns 5 --reruns-delay 3
23+
```
24+
25+
**Override via environment variable (CI pipelines):**
26+
27+
```bash
28+
# pytest's built-in env var for injecting CLI options
29+
export PYTEST_ADDOPTS="--reruns 3 --reruns-delay 10"
30+
```
31+
32+
### 🐳 Automatic Container Logs on Failure
33+
34+
When a test fails, the test kit automatically captures and displays the container logs. This helps with debugging by showing exactly what happened inside the container.
35+
36+
**Example output:**
37+
```
38+
================================================================================
39+
🐳 CONTAINER LOGS FOR FAILED TEST
40+
================================================================================
41+
[2026-01-13 10:30:15] Starting Azure Functions host...
42+
[2026-01-13 10:30:16] Worker initialization started...
43+
[2026-01-13 10:30:17] Error: Failed to load function app
44+
================================================================================
45+
```
46+
47+
### 📋 Environment Configuration
48+
49+
The plugin automatically loads configuration from `.env` files in your test directory or parent directories (up to 3 levels).
50+
51+
**Supported environment variables:**
52+
53+
```env
54+
# Storage Configuration
55+
FUNCTIONS_TEST_USE_AZURITE=true # Use Azurite emulator (default: true)
56+
FUNCTIONS_TEST_STORAGE_CONNECTION_STRING=... # Azure Storage connection string (for real storage)
57+
58+
# Runtime Configuration
59+
FUNCTIONS_TEST_RUNTIME=java # Runtime: java, python, etc. (default: java)
60+
FUNCTIONS_TEST_RUNTIME_VERSION=21 # Runtime version (default: 21)
61+
FUNCTIONS_TEST_HOST_VERSION=4 # Functions host version (default: 4)
62+
63+
# Test Configuration
64+
FUNCTIONS_TEST_APPS_DIR=./app-packages # Directory with app packages (default: ./app-packages)
65+
FUNCTIONS_TEST_WORKER_DIR=/path/to/worker # Custom worker directory to mount
66+
67+
# Retry Configuration: use PYTEST_ADDOPTS or pyproject.toml addopts
68+
# See "Automatic Test Retries" section above
69+
```
70+
71+
## Installation
72+
73+
```bash
74+
pip install -e ./azure-functions-test-kit
75+
```
76+
77+
The plugin is automatically activated via pytest's entry point mechanism.
78+
79+
## Usage
80+
81+
### Basic Test Example
82+
83+
```python
84+
import pytest
85+
from azure_functions_test_kit import LinuxConsumptionTestEnvironment
86+
87+
@pytest.fixture
88+
def test_env():
89+
"""Create a fresh test environment for each test"""
90+
with LinuxConsumptionTestEnvironment(apps_to_upload=['MyApp']) as env:
91+
yield env
92+
93+
def test_my_function(test_env):
94+
# Test automatically gets retries and log capture
95+
response = requests.get(f"{test_env.url}/api/MyFunction")
96+
assert response.status_code == 200
97+
```
98+
99+
### Disabling Retries for Specific Tests
100+
101+
```python
102+
@pytest.mark.no_rerun # This test will not be retried
103+
def test_critical_function(test_env):
104+
# This test will fail immediately without retries
105+
pass
106+
```
107+
108+
## How It Works
109+
110+
1. **Plugin Discovery**: pytest automatically discovers the plugin via the `pytest11` entry point
111+
2. **Environment Loading**: `.env` files are loaded during `pytest_configure`
112+
3. **Retry Configuration**: `pytest-rerunfailures` handles retries via `addopts` in `pyproject.toml`
113+
4. **Test Execution**: Tests run with automatic retry on failure
114+
5. **Log Capture**: On failure, container logs are captured and displayed
115+
116+
## Benefits
117+
118+
- **Reduced flakiness**: Automatic retries handle transient failures
119+
- **Better debugging**: Container logs are immediately available on failure
120+
- **Flexible configuration**: Override defaults via environment variables or command line
121+
- **Zero code changes**: Works automatically with existing tests

dockertests/azure-functions-test-kit/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ requires-python = ">=3.8"
1010
dependencies = [
1111
"pytest>=7.0",
1212
"pytest-xdist>=3.0",
13-
"azure-storage-blob>=12.19.0",
13+
"pytest-rerunfailures>=12.0",
14+
"azure-storage-blob==12.27.1",
1415
"requests>=2.31.0",
1516
"cryptography>=41.0.0",
1617
"python-dotenv>=1.0.0",

dockertests/azure-functions-test-kit/src/azure_functions_test_kit/plugin.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
Provides automatic .env file loading and optional helper fixtures.
77
Tests are free to create their own fixtures using the exported classes.
88
"""
9-
import os
109
from pathlib import Path
1110
from dotenv import load_dotenv
11+
import pytest
1212

1313

1414
def pytest_configure(config):
@@ -30,3 +30,42 @@ def pytest_configure(config):
3030
break
3131
else:
3232
print("ℹ️ [azure-functions-test-kit] No .env file found, using environment variables")
33+
34+
35+
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
36+
def pytest_runtest_makereport(item, call):
37+
"""Hook to capture test failures and display container logs."""
38+
# Execute all other hooks to obtain the report object
39+
outcome = yield
40+
rep = outcome.get_result()
41+
42+
# Only process test failures in the call phase (actual test execution)
43+
if rep.when == "call" and rep.failed:
44+
# Try to get the test_env fixture from the test
45+
test_env = None
46+
if hasattr(item, 'funcargs') and 'test_env' in item.funcargs:
47+
test_env = item.funcargs['test_env']
48+
49+
if test_env and hasattr(test_env, 'functions_controller'):
50+
functions_controller = test_env.functions_controller
51+
if functions_controller:
52+
try:
53+
logs = functions_controller.get_container_logs()
54+
55+
# Add container logs to the test report
56+
print("\n" + "="*80)
57+
print("🐳 CONTAINER LOGS FOR FAILED TEST")
58+
print("="*80)
59+
print(logs)
60+
print("="*80 + "\n")
61+
62+
# Also append to the test's longrepr for visibility in reports
63+
if hasattr(rep, 'longrepr'):
64+
log_section = f"\n\n{'='*80}\n🐳 CONTAINER LOGS\n{'='*80}\n{logs}\n{'='*80}\n"
65+
if rep.longrepr:
66+
rep.longrepr = str(rep.longrepr) + log_section
67+
else:
68+
rep.longrepr = log_section
69+
70+
except Exception as e:
71+
print(f"\n⚠️ Failed to retrieve container logs: {e}\n")

dockertests/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ testpaths = ["."]
2525
pythonpath = ["."]
2626
python_files = ["test_*.py"]
2727
python_classes = ["Test*", "!TestEnvironment"]
28-
addopts = ["-v", "--tb=short"]
28+
addopts = ["-v", "--tb=short", "--reruns=2", "--reruns-delay=5"]

eng/ci/templates/jobs/run-docker-tests-linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
6363
- bash: |
6464
cd dockertests
65-
pytest . -v -n auto --tb=short
65+
pytest . -v -n auto --tb=long
6666
displayName: 'Run Docker integration tests'
6767
env:
6868
FUNCTIONS_TEST_WORKER_DIR: '$(Build.SourcesDirectory)/java-worker'

0 commit comments

Comments
 (0)