diff --git a/.github/workflows/azure-functions-smoke-tests.yml b/.github/workflows/azure-functions-smoke-tests.yml
new file mode 100644
index 000000000..9ee868fda
--- /dev/null
+++ b/.github/workflows/azure-functions-smoke-tests.yml
@@ -0,0 +1,45 @@
+name: Azure Functions Smoke Tests
+
+on:
+ push:
+ branches:
+ - main
+ - 'feature/**'
+ paths-ignore: [ '**.md' ]
+ pull_request:
+ paths-ignore: [ '**.md' ]
+ workflow_dispatch:
+
+jobs:
+ smoke-tests:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Setup .NET from global.json
+ uses: actions/setup-dotnet@v3
+ with:
+ global-json-file: global.json
+
+ - name: Restore dependencies
+ run: dotnet restore test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
+
+ - name: Run smoke tests
+ run: |
+ cd test/AzureFunctionsSmokeTests
+ pwsh -File run-smoketests.ps1
+
+ - name: Upload smoke test logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: smoke-test-logs
+ path: test/AzureFunctionsSmokeTests/logs/
+ if-no-files-found: ignore
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b36897e3f..d4a8354ce 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -25,6 +25,8 @@
+
+
diff --git a/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj b/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
new file mode 100644
index 000000000..219d5bc98
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
@@ -0,0 +1,46 @@
+
+
+
+ net8.0
+ v4
+ Exe
+ enable
+
+ false
+ false
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
diff --git a/test/AzureFunctionsSmokeTests/Dockerfile b/test/AzureFunctionsSmokeTests/Dockerfile
new file mode 100644
index 000000000..4df636799
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/Dockerfile
@@ -0,0 +1,10 @@
+# Use the Azure Functions base image for .NET 8.0 isolated
+FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
+
+# Set environment variables
+ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
+ AzureFunctionsJobHost__Logging__Console__IsEnabled=true \
+ FUNCTIONS_WORKER_RUNTIME=dotnet-isolated
+
+# Copy the published app
+COPY ./publish /home/site/wwwroot
diff --git a/test/AzureFunctionsSmokeTests/HelloCitiesOrchestration.cs b/test/AzureFunctionsSmokeTests/HelloCitiesOrchestration.cs
new file mode 100644
index 000000000..14a5b5f53
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/HelloCitiesOrchestration.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+using Microsoft.Extensions.Logging;
+
+namespace AzureFunctionsSmokeTests;
+
+///
+/// Smoke test orchestration functions for Azure Functions with Durable Task.
+///
+public static class HelloCitiesOrchestration
+{
+ [Function(nameof(HelloCitiesOrchestration))]
+ public static async Task> RunOrchestrator(
+ [OrchestrationTrigger] TaskOrchestrationContext context)
+ {
+ ILogger logger = context.CreateReplaySafeLogger(nameof(HelloCitiesOrchestration));
+ logger.LogInformation("Starting HelloCities orchestration.");
+
+ List outputs = new List();
+
+ // Call activities in sequence
+ outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Tokyo"));
+ outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Seattle"));
+ outputs.Add(await context.CallActivityAsync(nameof(SayHello), "London"));
+
+ logger.LogInformation("HelloCities orchestration completed.");
+
+ // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
+ return outputs;
+ }
+
+ [Function(nameof(SayHello))]
+ public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
+ {
+ ILogger logger = executionContext.GetLogger(nameof(SayHello));
+ logger.LogInformation($"Saying hello to {name}.");
+ return $"Hello {name}!";
+ }
+
+ [Function("HelloCitiesOrchestration_HttpStart")]
+ public static async Task HttpStart(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ FunctionContext executionContext)
+ {
+ ILogger logger = executionContext.GetLogger("HelloCitiesOrchestration_HttpStart");
+
+ // Function input comes from the request content.
+ string instanceId = await client
+ .ScheduleNewOrchestrationInstanceAsync(nameof(HelloCitiesOrchestration));
+
+ logger.LogInformation($"Started orchestration with ID = '{instanceId}'.");
+
+ // Returns an HTTP 202 response with an instance management payload.
+ return client.CreateCheckStatusResponse(req, instanceId);
+ }
+}
diff --git a/test/AzureFunctionsSmokeTests/Program.cs b/test/AzureFunctionsSmokeTests/Program.cs
new file mode 100644
index 000000000..eddb3547e
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/Program.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.Hosting;
+
+namespace AzureFunctionsSmokeTests;
+
+public class Program
+{
+ public static void Main()
+ {
+ IHost host = new HostBuilder()
+ .ConfigureFunctionsWorkerDefaults()
+ .Build();
+
+ host.Run();
+ }
+}
diff --git a/test/AzureFunctionsSmokeTests/README.md b/test/AzureFunctionsSmokeTests/README.md
new file mode 100644
index 000000000..bfdf4241c
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/README.md
@@ -0,0 +1,83 @@
+# Azure Functions Smoke Tests
+
+This directory contains smoke tests for Azure Functions with Durable Task, designed to validate the SDK and Source Generator functionality in a real Azure Functions isolated .NET environment.
+
+## Overview
+
+The smoke tests ensure that:
+- The Durable Task SDK works correctly with Azure Functions isolated worker
+- Source generators produce valid code
+- Orchestrations can be triggered and completed successfully
+- The complete end-to-end workflow functions as expected
+
+## Structure
+
+- **HelloCitiesOrchestration.cs** - Simple orchestration that calls multiple activities
+- **Program.cs** - Azure Functions host entry point
+- **host.json** - Azure Functions host configuration
+- **local.settings.json** - Local development settings
+- **Dockerfile** - Docker image configuration for the Functions app
+- **run-smoketests.ps1** - PowerShell script to run smoke tests locally or in CI
+
+## Running Smoke Tests Locally
+
+### Prerequisites
+
+- Docker installed and running
+- PowerShell Core (pwsh) installed
+- .NET 8.0 SDK or later
+
+### Run the Tests
+
+From the `test/AzureFunctionsSmokeTests` directory:
+
+```bash
+pwsh -File run-smoketests.ps1
+```
+
+The script will:
+1. Build and publish the Azure Functions project
+2. Create a Docker image
+3. Start Azurite (Azure Storage emulator) in a Docker container
+4. Start the Azure Functions app in a Docker container
+5. Trigger the HelloCities orchestration via HTTP
+6. Poll for orchestration completion
+7. Validate the result
+8. Clean up all containers
+
+### Parameters
+
+The script accepts the following optional parameters:
+
+```powershell
+pwsh -File run-smoketests.ps1 `
+ -ImageName "custom-image-name" `
+ -ContainerName "custom-container-name" `
+ -Port 8080 `
+ -Timeout 120
+```
+
+## CI Integration
+
+The smoke tests are automatically run in GitHub Actions via the `.github/workflows/azure-functions-smoke-tests.yml` workflow on:
+- Push to `main` or `feature/**` branches
+- Pull requests targeting `main` or `feature/**` branches
+- Manual workflow dispatch
+
+## Troubleshooting
+
+If the smoke tests fail:
+
+1. **Check container logs**: The script will display logs automatically on failure
+2. **Verify Azurite is running**: Ensure port 10000-10002 are available
+3. **Check Functions app port**: Ensure the configured port (default 8080) is available
+4. **Build errors**: Ensure all dependencies are restored with `dotnet restore`
+
+## Adding New Smoke Tests
+
+To add new orchestration scenarios:
+
+1. Create new function classes following the pattern in `HelloCitiesOrchestration.cs`
+2. Ensure proper XML documentation comments
+3. Add test logic to validate the new scenario
+4. Update this README with the new test case
diff --git a/test/AzureFunctionsSmokeTests/host.json b/test/AzureFunctionsSmokeTests/host.json
new file mode 100644
index 000000000..305e9bf49
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/host.json
@@ -0,0 +1,21 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Default": "Information",
+ "DurableTask.AzureStorage": "Warning",
+ "DurableTask.Core": "Warning"
+ },
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ }
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "DotNetIsolatedSmokeTests"
+ }
+ }
+}
diff --git a/test/AzureFunctionsSmokeTests/local.settings.json b/test/AzureFunctionsSmokeTests/local.settings.json
new file mode 100644
index 000000000..8eea88f48
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/local.settings.json
@@ -0,0 +1,7 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
+ }
+}
diff --git a/test/AzureFunctionsSmokeTests/run-smoketests.ps1 b/test/AzureFunctionsSmokeTests/run-smoketests.ps1
new file mode 100644
index 000000000..185b9e3df
--- /dev/null
+++ b/test/AzureFunctionsSmokeTests/run-smoketests.ps1
@@ -0,0 +1,271 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Runs smoke tests for the Azure Functions app using Docker containers.
+.DESCRIPTION
+ This script builds and publishes the Azure Functions smoke test app,
+ starts required containers (Azurite for storage emulation and the Functions app),
+ triggers the orchestration, and validates successful completion.
+.PARAMETER ImageName
+ Docker image name for the Functions app (default: "azurefunctions-smoketests")
+.PARAMETER ContainerName
+ Docker container name for the Functions app (default: "azurefunctions-smoketests-container")
+.PARAMETER Port
+ Port to expose the Functions app on (default: 8080)
+.PARAMETER Timeout
+ Timeout in seconds to wait for orchestration completion (default: 120)
+#>
+
+param(
+ [string]$ImageName = "azurefunctions-smoketests",
+ [string]$ContainerName = "azurefunctions-smoketests-container",
+ [int]$Port = 8080,
+ [int]$Timeout = 120
+)
+
+$ErrorActionPreference = "Stop"
+
+# Get the directory where the script is located
+$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$projectDir = $scriptDir
+$publishDir = Join-Path $projectDir "publish"
+
+Write-Host "=== Azure Functions Smoke Test Runner ===" -ForegroundColor Cyan
+Write-Host ""
+
+# Function to clean up containers
+function Cleanup {
+ Write-Host "Cleaning up containers..." -ForegroundColor Yellow
+
+ # Stop and remove the Functions app container
+ docker stop $ContainerName 2>$null | Out-Null
+ docker rm $ContainerName 2>$null | Out-Null
+
+ # Stop and remove Azurite container
+ docker stop azurite-smoketest 2>$null | Out-Null
+ docker rm azurite-smoketest 2>$null | Out-Null
+
+ # Remove the Docker network
+ docker network rm smoketest-network 2>$null | Out-Null
+}
+
+# Cleanup on script exit
+trap {
+ Write-Host "Error occurred. Cleaning up..." -ForegroundColor Red
+ Cleanup
+ exit 1
+}
+
+try {
+ # Cleanup any existing containers first
+ Write-Host "Cleaning up any existing containers..." -ForegroundColor Yellow
+ Cleanup
+ Write-Host ""
+
+ # Step 1: Build the project
+ Write-Host "Step 1: Building the Azure Functions project..." -ForegroundColor Green
+ dotnet build $projectDir -c Release
+ if ($LASTEXITCODE -ne 0) {
+ throw "Build failed with exit code $LASTEXITCODE"
+ }
+ Write-Host "Build completed successfully." -ForegroundColor Green
+ Write-Host ""
+
+ # Step 2: Publish the project
+ Write-Host "Step 2: Publishing the Azure Functions project..." -ForegroundColor Green
+ if (Test-Path $publishDir) {
+ Remove-Item $publishDir -Recurse -Force
+ }
+ dotnet publish $projectDir -c Release -o $publishDir
+ if ($LASTEXITCODE -ne 0) {
+ throw "Publish failed with exit code $LASTEXITCODE"
+ }
+ Write-Host "Publish completed successfully." -ForegroundColor Green
+ Write-Host ""
+
+ # Step 3: Build Docker image
+ Write-Host "Step 3: Building Docker image '$ImageName'..." -ForegroundColor Green
+ docker build -t $ImageName $projectDir
+ if ($LASTEXITCODE -ne 0) {
+ throw "Docker build failed with exit code $LASTEXITCODE"
+ }
+ Write-Host "Docker image built successfully." -ForegroundColor Green
+ Write-Host ""
+
+ # Step 4: Create Docker network
+ Write-Host "Step 4: Creating Docker network..." -ForegroundColor Green
+ docker network create smoketest-network 2>$null
+ Write-Host "Docker network created or already exists." -ForegroundColor Green
+ Write-Host ""
+
+ # Step 5: Start Azurite container
+ Write-Host "Step 5: Starting Azurite storage emulator..." -ForegroundColor Green
+ docker run -d `
+ --name azurite-smoketest `
+ --network smoketest-network `
+ -p 10000:10000 `
+ -p 10001:10001 `
+ -p 10002:10002 `
+ mcr.microsoft.com/azure-storage/azurite:latest
+
+ if ($LASTEXITCODE -ne 0) {
+ throw "Failed to start Azurite container"
+ }
+
+ # Wait for Azurite to be ready
+ Write-Host "Waiting for Azurite to be ready..." -ForegroundColor Yellow
+ Start-Sleep -Seconds 5
+ Write-Host "Azurite is ready." -ForegroundColor Green
+ Write-Host ""
+
+ # Step 6: Start Azure Functions container
+ Write-Host "Step 6: Starting Azure Functions container..." -ForegroundColor Green
+
+ # Azurite connection string for Docker network
+ # Using the default Azurite development account credentials
+ $accountName = "devstoreaccount1"
+ $accountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
+ $blobEndpoint = "http://azurite-smoketest:10000/$accountName"
+ $queueEndpoint = "http://azurite-smoketest:10001/$accountName"
+ $tableEndpoint = "http://azurite-smoketest:10002/$accountName"
+
+ $storageConnectionString = @(
+ "DefaultEndpointsProtocol=http"
+ "AccountName=$accountName"
+ "AccountKey=$accountKey"
+ "BlobEndpoint=$blobEndpoint"
+ "QueueEndpoint=$queueEndpoint"
+ "TableEndpoint=$tableEndpoint"
+ ) -join ";"
+
+ docker run -d `
+ --name $ContainerName `
+ --network smoketest-network `
+ -p "${Port}:80" `
+ -e AzureWebJobsStorage="$storageConnectionString" `
+ -e FUNCTIONS_WORKER_RUNTIME=dotnet-isolated `
+ -e WEBSITE_HOSTNAME="localhost:$Port" `
+ $ImageName
+
+ if ($LASTEXITCODE -ne 0) {
+ throw "Failed to start Functions container"
+ }
+
+ # Wait for Functions host to start
+ Write-Host "Waiting for Azure Functions host to start..." -ForegroundColor Yellow
+
+ # Give the host time to fully initialize
+ # The admin/host/status endpoint is not available in all configurations,
+ # so we'll wait a reasonable amount of time and check logs
+ Start-Sleep -Seconds 15
+
+ # Check if the container is still running
+ $containerStatus = docker inspect --format='{{.State.Status}}' $ContainerName
+ if ($containerStatus -ne "running") {
+ Write-Host "Functions container is not running. Checking logs..." -ForegroundColor Red
+ docker logs $ContainerName
+ throw "Functions container failed to start"
+ }
+
+ # Check logs for successful startup
+ $logs = docker logs $ContainerName 2>&1 | Out-String
+ if ($logs -match "Job host started" -or $logs -match "Host started") {
+ Write-Host "Azure Functions host is ready." -ForegroundColor Green
+ }
+ else {
+ Write-Host "Warning: Could not confirm host startup from logs." -ForegroundColor Yellow
+ Write-Host "Attempting to continue with orchestration trigger..." -ForegroundColor Yellow
+ }
+ Write-Host ""
+
+ # Step 7: Trigger orchestration
+ Write-Host "Step 7: Triggering orchestration..." -ForegroundColor Green
+ $startUrl = "http://localhost:$Port/api/HelloCitiesOrchestration_HttpStart"
+
+ try {
+ $startResponse = Invoke-WebRequest -Uri $startUrl -Method Post -UseBasicParsing
+ if ($startResponse.StatusCode -ne 202) {
+ throw "Unexpected status code: $($startResponse.StatusCode)"
+ }
+ }
+ catch {
+ Write-Host "Failed to trigger orchestration. Error: $_" -ForegroundColor Red
+ Write-Host "Container logs:" -ForegroundColor Yellow
+ docker logs $ContainerName
+ throw
+ }
+
+ $responseContent = $startResponse.Content | ConvertFrom-Json
+ $statusQueryGetUri = $responseContent.statusQueryGetUri
+ $instanceId = $responseContent.id
+
+ Write-Host "Orchestration started with instance ID: $instanceId" -ForegroundColor Green
+ Write-Host "Status query URI: $statusQueryGetUri" -ForegroundColor Cyan
+ Write-Host ""
+
+ # Step 8: Poll for completion
+ Write-Host "Step 8: Polling for orchestration completion..." -ForegroundColor Green
+ $startTime = Get-Date
+ $completed = $false
+ $consecutiveErrors = 0
+ $maxConsecutiveErrors = 3
+
+ while (((Get-Date) - $startTime).TotalSeconds -lt $Timeout) {
+ Start-Sleep -Seconds 2
+
+ try {
+ $statusResponse = Invoke-WebRequest -Uri $statusQueryGetUri -UseBasicParsing
+ $status = $statusResponse.Content | ConvertFrom-Json
+
+ # Reset error counter on successful poll
+ $consecutiveErrors = 0
+
+ Write-Host "Current status: $($status.runtimeStatus)" -ForegroundColor Yellow
+
+ if ($status.runtimeStatus -eq "Completed") {
+ $completed = $true
+ Write-Host ""
+ Write-Host "Orchestration completed successfully!" -ForegroundColor Green
+ Write-Host "Output: $($status.output)" -ForegroundColor Cyan
+ break
+ }
+ elseif ($status.runtimeStatus -eq "Failed" -or $status.runtimeStatus -eq "Terminated") {
+ throw "Orchestration ended with status: $($status.runtimeStatus)"
+ }
+ }
+ catch {
+ $consecutiveErrors++
+ Write-Host "Error polling status (attempt $consecutiveErrors/$maxConsecutiveErrors): $_" -ForegroundColor Red
+
+ if ($consecutiveErrors -ge $maxConsecutiveErrors) {
+ Write-Host "Container logs:" -ForegroundColor Yellow
+ docker logs $ContainerName
+ throw "Too many consecutive errors polling orchestration status"
+ }
+ }
+ }
+
+ if (-not $completed) {
+ Write-Host "Container logs:" -ForegroundColor Yellow
+ docker logs $ContainerName
+ throw "Orchestration did not complete within timeout period"
+ }
+
+ Write-Host ""
+ Write-Host "=== Smoke test completed successfully! ===" -ForegroundColor Green
+}
+finally {
+ # Cleanup
+ Cleanup
+
+ # Cleanup publish directory
+ if (Test-Path $publishDir) {
+ Write-Host "Cleaning up publish directory..." -ForegroundColor Yellow
+ Remove-Item $publishDir -Recurse -Force
+ }
+}
+
+Write-Host ""
+Write-Host "All smoke tests passed!" -ForegroundColor Green
+exit 0