From 08dcf74cf47d0e7320ed58252d6fd465d4706a4f Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Fri, 8 May 2026 07:20:39 -0700 Subject: [PATCH] Split DurableTask/AzureFunctions integration tests into dedicated CI job - Add -TestProjectNameExclude parameter to New-FilteredSolution.ps1 - Add 'functions' and 'core' path filters to paths-filter job - Exclude DurableTask/AzureFunctions from main dotnet-test job - Remove emulator setup from dotnet-test (no longer needed) - Add new dotnet-test-functions job (ubuntu/net10.0 only, path-conditional) - Update merge gate and report job to include dotnet-test-functions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/dotnet-build-and-test.yml | 130 ++++++++++++++++++-- dotnet/eng/scripts/New-FilteredSolution.ps1 | 28 +++++ 2 files changed, 148 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dotnet-build-and-test.yml b/.github/workflows/dotnet-build-and-test.yml index 0b589e2c55..e3fc869088 100644 --- a/.github/workflows/dotnet-build-and-test.yml +++ b/.github/workflows/dotnet-build-and-test.yml @@ -38,6 +38,8 @@ jobs: dotnetChanges: ${{ steps.filter.outputs.dotnet }} cosmosDbChanges: ${{ steps.filter.outputs.cosmosdb }} foundryHostingChanges: ${{ steps.filter.outputs.foundryHosting }} + functionsChanged: ${{ steps.filter.outputs.functions }} + coreChanged: ${{ steps.filter.outputs.core }} steps: - uses: actions/checkout@v6 - uses: dorny/paths-filter@v3 @@ -63,6 +65,21 @@ jobs: - 'dotnet/Directory.Packages.props' - 'dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-build-image.ps1' - '.github/workflows/dotnet-build-and-test.yml' + functions: + - 'dotnet/src/Microsoft.Agents.AI.DurableTask/**' + - 'dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**' + - 'dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/**' + - 'dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/**' + - '.github/actions/azure-functions-integration-setup/**' + core: + - 'dotnet/src/Microsoft.Agents.AI/**' + - 'dotnet/src/Microsoft.Agents.AI.Abstractions/**' + - 'dotnet/src/Microsoft.Agents.AI.OpenAI/**' + - 'dotnet/src/Microsoft.Agents.AI.Workflows/**' + - 'dotnet/eng/scripts/New-FilteredSolution.ps1' + - 'dotnet/tests/Directory.Build.props' + - 'dotnet/Directory.Packages.props' + - 'dotnet/global.json' # run only if 'dotnet' files were changed - name: dotnet tests if: steps.filter.outputs.dotnet == 'true' @@ -214,6 +231,7 @@ jobs: -OutputPath dotnet/filtered-unit.slnx ./dotnet/eng/scripts/New-FilteredSolution.ps1 @commonArgs ` -TestProjectNameFilter "*IntegrationTests*" ` + -TestProjectNameExclude "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" ` -OutputPath dotnet/filtered-integration.slnx - name: Run Unit Tests @@ -255,14 +273,6 @@ jobs: tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - # This setup action is required for both Durable Task and Azure Functions integration tests. - # We only run it on Ubuntu since the Durable Task and Azure Functions features are not available - # on .NET Framework (net472) which is what we use the Windows runner for. - - name: Set up Durable Task and Azure Functions Integration Test Emulators - if: github.event_name != 'pull_request' && matrix.integration-tests && matrix.os == 'ubuntu-latest' - uses: ./.github/actions/azure-functions-integration-setup - id: azure-functions-setup - - name: Run Integration Tests shell: pwsh working-directory: dotnet @@ -428,11 +438,111 @@ jobs: AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.IT_HOSTED_AGENT_MODEL_DEPLOYMENT_NAME }} # IT_HOSTED_AGENT_IMAGE was exported into $GITHUB_ENV by the previous step. + # DurableTask and AzureFunctions integration tests (ubuntu/net10.0 only). + # Split from main dotnet-test job for path-based filtering and parallelism. + dotnet-test-functions: + needs: [paths-filter] + if: > + needs.paths-filter.outputs.dotnetChanges == 'true' && + github.event_name != 'pull_request' && + (needs.paths-filter.outputs.functionsChanged == 'true' || + needs.paths-filter.outputs.coreChanged == 'true' || + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + environment: integration + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + sparse-checkout: | + . + .github + dotnet + python + declarative-agents + + - name: Setup dotnet + uses: actions/setup-dotnet@v5.2.0 + with: + global-json-file: ${{ github.workspace }}/dotnet/global.json + + - name: Build functions integration test projects + shell: bash + working-directory: dotnet + run: | + dotnet build ./tests/Microsoft.Agents.AI.DurableTask.IntegrationTests -c Release -f net10.0 --warnaserror + dotnet build ./tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests -c Release -f net10.0 --warnaserror + + - name: Azure CLI Login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Set up Durable Task and Azure Functions Integration Test Emulators + uses: ./.github/actions/azure-functions-integration-setup + id: azure-functions-setup + + - name: Run Functions Integration Tests + shell: pwsh + working-directory: dotnet + run: | + # Run DurableTask integration tests + dotnet test ` + --project ./tests/Microsoft.Agents.AI.DurableTask.IntegrationTests ` + -f net10.0 ` + -c Release ` + --no-build -v Normal ` + --report-xunit-trx ` + --report-junit ` + --results-directory ../IntegrationTestResults/ ` + --ignore-exit-code 8 ` + --filter-not-trait "Category=IntegrationDisabled" ` + --parallel-algorithm aggressive ` + --max-threads 2.0x + + # Run AzureFunctions integration tests + dotnet test ` + --project ./tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests ` + -f net10.0 ` + -c Release ` + --no-build -v Normal ` + --report-xunit-trx ` + --report-junit ` + --results-directory ../IntegrationTestResults/ ` + --ignore-exit-code 8 ` + --filter-not-trait "Category=IntegrationDisabled" ` + --parallel-algorithm aggressive ` + --max-threads 2.0x + env: + # OpenAI Models + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_CHAT_MODEL_NAME: ${{ vars.OPENAI_CHAT_MODEL_NAME }} + OPENAI_REASONING_MODEL_NAME: ${{ vars.OPENAI_REASONING_MODEL_NAME }} + # Azure OpenAI Models + AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }} + AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }} + AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }} + # Azure AI Foundry + AZURE_AI_PROJECT_ENDPOINT: ${{ vars.AZURE_AI_PROJECT_ENDPOINT }} + AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.AZURE_AI_MODEL_DEPLOYMENT_NAME }} + AZURE_AI_BING_CONNECTION_ID: ${{ vars.AZURE_AI_BING_CONNECTION_ID }} + + - name: Upload functions test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: dotnet-test-results-functions-net10.0-ubuntu-latest + path: IntegrationTestResults/**/*.junit + if-no-files-found: ignore + # This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed dotnet-build-and-test-check: if: always() runs-on: ubuntu-latest - needs: [dotnet-build, dotnet-test, dotnet-foundry-hosted-it] + needs: [dotnet-build, dotnet-test, dotnet-foundry-hosted-it, dotnet-test-functions] steps: - name: Get Date shell: bash @@ -479,7 +589,7 @@ jobs: github.event_name != 'pull_request' && (contains(join(needs.*.result, ','), 'success') || contains(join(needs.*.result, ','), 'failure')) - needs: [dotnet-test] + needs: [dotnet-test, dotnet-test-functions] runs-on: ubuntu-latest defaults: run: diff --git a/dotnet/eng/scripts/New-FilteredSolution.ps1 b/dotnet/eng/scripts/New-FilteredSolution.ps1 index de6a8f9d1d..84b4c86aa2 100644 --- a/dotnet/eng/scripts/New-FilteredSolution.ps1 +++ b/dotnet/eng/scripts/New-FilteredSolution.ps1 @@ -25,6 +25,11 @@ Optional wildcard pattern to filter test project names (e.g., *UnitTests*, *IntegrationTests*). When specified, only test projects whose filename matches this pattern are kept. +.PARAMETER TestProjectNameExclude + Optional wildcard pattern(s) to exclude test projects by name (e.g., *DurableTask.IntegrationTests*). + When specified, test projects whose filename matches any of these patterns are removed. + Applied after TestProjectNameFilter. Can be a single string or an array of strings. + .PARAMETER ExcludeSamples When specified, removes all projects under the samples/ directory from the solution. @@ -43,6 +48,10 @@ .EXAMPLE # Inline usage with dotnet test (PowerShell) dotnet test --solution (./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472) --no-build -f net472 + +.EXAMPLE + # Generate integration tests excluding DurableTask and AzureFunctions + ./dotnet/eng/scripts/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameFilter "*IntegrationTests*" -TestProjectNameExclude "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" -OutputPath filtered-other-integration.slnx #> [CmdletBinding()] @@ -57,6 +66,8 @@ param( [string]$TestProjectNameFilter, + [string[]]$TestProjectNameExclude, + [switch]$ExcludeSamples, [string]$OutputPath @@ -107,6 +118,23 @@ foreach ($proj in $allProjects) { continue } + # Exclude test projects matching any exclusion pattern + if ($isTestProject -and $TestProjectNameExclude) { + $excluded = $false + foreach ($pattern in $TestProjectNameExclude) { + if ($projFileName -like $pattern) { + $excluded = $true + break + } + } + if ($excluded) { + Write-Verbose "Removing (exclude filter): $projRelPath" + $removed += $projRelPath + $proj.ParentNode.RemoveChild($proj) | Out-Null + continue + } + } + if (-not (Test-Path $projFullPath)) { Write-Verbose "Project not found, keeping in solution: $projRelPath" $kept += $projRelPath