Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dockertests/azure-functions-test-kit/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ requires-python = ">=3.8"
dependencies = [
"pytest>=7.0",
"pytest-xdist>=3.0",
"azure-storage-blob>=12.19.0",
"azure-storage-blob==12.27.1",
"requests>=2.31.0",
"cryptography>=41.0.0",
"python-dotenv>=1.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
from pathlib import Path
from dotenv import load_dotenv
import pytest


def pytest_configure(config):
Expand All @@ -30,3 +31,42 @@ def pytest_configure(config):
break
else:
print("ℹ️ [azure-functions-test-kit] No .env file found, using environment variables")


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Hook to capture test failures and display container logs."""
# Execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result()

# Only process test failures in the call phase (actual test execution)
if rep.when == "call" and rep.failed:
# Try to get the test_env fixture from the test
test_env = None
if hasattr(item, 'funcargs') and 'test_env' in item.funcargs:
test_env = item.funcargs['test_env']

if test_env and hasattr(test_env, 'functions_controller'):
functions_controller = test_env.functions_controller
if functions_controller:
try:
logs = functions_controller.get_container_logs()

# Add container logs to the test report
print("\n" + "="*80)
print("🐳 CONTAINER LOGS FOR FAILED TEST")
print("="*80)
print(logs)
print("="*80 + "\n")

# Also append to the test's longrepr for visibility in reports
if hasattr(rep, 'longrepr'):
log_section = f"\n\n{'='*80}\n🐳 CONTAINER LOGS\n{'='*80}\n{logs}\n{'='*80}\n"
if rep.longrepr:
rep.longrepr = str(rep.longrepr) + log_section
else:
rep.longrepr = log_section

except Exception as e:
print(f"\n⚠️ Failed to retrieve container logs: {e}\n")
2 changes: 1 addition & 1 deletion eng/ci/templates/jobs/run-docker-tests-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:

- bash: |
cd dockertests
pytest . -v -n auto --tb=short
pytest . -v -n auto --tb=long
displayName: 'Run Docker integration tests'
env:
FUNCTIONS_TEST_WORKER_DIR: '$(Build.SourcesDirectory)/java-worker'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ String execute(FunctionEnvironmentReloadRequest request, Builder response) throw
if (environmentVariables.isEmpty()) {
return "Ignoring FunctionEnvironmentReloadRequest as newSettings map is empty.";
}
setEnv(environmentVariables);
setTimeZone(environmentVariables);

// Set timezone and get modified environment variables with TZ adjusted if needed
Map<String, String> modifiedEnvVars = setTimeZone(environmentVariables);
setEnv(modifiedEnvVars);
setCapabilities(response, environmentVariables);

return "FunctionEnvironmentReloadRequest completed";
Expand All @@ -56,32 +58,38 @@ private void setCapabilities(FunctionEnvironmentReloadResponse.Builder response,
}

/*
* Sets the default timezone based on the TZ environment variable
* Sets the default timezone based on the TZ environment variable.
* Returns a modified map where both WEBSITE_TIME_ZONE and TZ are synchronized.
*/
private void setTimeZone(Map<String, String> environmentVariables) {
// Check WEBSITE_TIME_ZONE first, fall back to TZ if not set
String tzValue = environmentVariables.get("WEBSITE_TIME_ZONE");
String tzSource = "WEBSITE_TIME_ZONE";
private Map<String, String> setTimeZone(Map<String, String> environmentVariables) {
String websiteTimeZone = environmentVariables.get("WEBSITE_TIME_ZONE");
String tz = environmentVariables.get("TZ");

// Determine which timezone to use (WEBSITE_TIME_ZONE takes precedence)
String tzValue = (websiteTimeZone != null && !websiteTimeZone.isEmpty()) ? websiteTimeZone : tz;

if (tzValue == null || tzValue.isEmpty()) {
tzValue = environmentVariables.get("TZ");
tzSource = "TZ";
return environmentVariables;
}

if (tzValue != null && !tzValue.isEmpty()) {
try {
TimeZone timeZone = TimeZone.getTimeZone(tzValue);
TimeZone.setDefault(timeZone);
System.setProperty("user.timezone", timeZone.getID());
WorkerLogManager.getSystemLogger().log(Level.INFO,
String.format("Set default timezone to: %s (from %s environment variable: %s)",
timeZone.getID(), tzSource, tzValue));
} catch (Exception e) {
WorkerLogManager.getSystemLogger().log(Level.WARNING,
String.format("Failed to set timezone from %s environment variable '%s': %s",
tzSource, tzValue, e.getMessage()));
}
// Set the JVM timezone
try {
TimeZone timeZone = TimeZone.getTimeZone(tzValue);
TimeZone.setDefault(timeZone);
System.setProperty("user.timezone", timeZone.getID());
WorkerLogManager.getSystemLogger().log(Level.INFO,
String.format("Set default timezone to: %s", timeZone.getID()));
} catch (Exception e) {
WorkerLogManager.getSystemLogger().log(Level.WARNING,
String.format("Failed to set timezone '%s': %s", tzValue, e.getMessage()));
}

// Synchronize both environment variables to the same value to prevent race conditions
Map<String, String> modifiedVars = new HashMap<>(environmentVariables);
modifiedVars.put("WEBSITE_TIME_ZONE", tzValue);
modifiedVars.put("TZ", tzValue);

return modifiedVars;
}

/*
Expand Down