diff --git a/dockertests/azure-functions-test-kit/pyproject.toml b/dockertests/azure-functions-test-kit/pyproject.toml index 953a5088..c8bebcdd 100644 --- a/dockertests/azure-functions-test-kit/pyproject.toml +++ b/dockertests/azure-functions-test-kit/pyproject.toml @@ -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", diff --git a/dockertests/azure-functions-test-kit/src/azure_functions_test_kit/plugin.py b/dockertests/azure-functions-test-kit/src/azure_functions_test_kit/plugin.py index 32610f26..fe51b77c 100644 --- a/dockertests/azure-functions-test-kit/src/azure_functions_test_kit/plugin.py +++ b/dockertests/azure-functions-test-kit/src/azure_functions_test_kit/plugin.py @@ -9,6 +9,7 @@ import os from pathlib import Path from dotenv import load_dotenv +import pytest def pytest_configure(config): @@ -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") diff --git a/eng/ci/templates/jobs/run-docker-tests-linux.yml b/eng/ci/templates/jobs/run-docker-tests-linux.yml index c8e32a87..70c4564c 100644 --- a/eng/ci/templates/jobs/run-docker-tests-linux.yml +++ b/eng/ci/templates/jobs/run-docker-tests-linux.yml @@ -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' diff --git a/src/main/java/com/microsoft/azure/functions/worker/handler/FunctionEnvironmentReloadRequestHandler.java b/src/main/java/com/microsoft/azure/functions/worker/handler/FunctionEnvironmentReloadRequestHandler.java index 5fbd1f67..141e09b1 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/handler/FunctionEnvironmentReloadRequestHandler.java +++ b/src/main/java/com/microsoft/azure/functions/worker/handler/FunctionEnvironmentReloadRequestHandler.java @@ -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 modifiedEnvVars = setTimeZone(environmentVariables); + setEnv(modifiedEnvVars); setCapabilities(response, environmentVariables); return "FunctionEnvironmentReloadRequest completed"; @@ -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 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 setTimeZone(Map 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 modifiedVars = new HashMap<>(environmentVariables); + modifiedVars.put("WEBSITE_TIME_ZONE", tzValue); + modifiedVars.put("TZ", tzValue); + + return modifiedVars; } /*