Repository navigation
Python: Separate message texts with a newline in AgentResponse.text #32204
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # This workflow will build all .slnx files in the dotnet folder, and run all unit tests and integration tests using dotnet docker containers, | |
| # each targeting a single version of the dotnet SDK. | |
| # | |
| name: dotnet-build-and-test | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| branches: ["main", "feature*"] | |
| merge_group: | |
| branches: ["main", "feature*"] | |
| push: | |
| branches: ["main", "feature*"] | |
| schedule: | |
| - cron: "0 0 * * *" # Run at midnight UTC daily | |
| env: | |
| COVERAGE_THRESHOLD: 80 | |
| COVERAGE_FRAMEWORK: net10.0 # framework target for which we run/report code coverage | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| paths-filter: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| dotnetChanges: ${{ steps.filter.outputs.dotnet }} | |
| cosmosDbChanges: ${{ steps.filter.outputs.cosmosdb }} | |
| azureStorageChanges: ${{ steps.filter.outputs.azurestorage }} | |
| foundryHostingChanges: ${{ steps.filter.outputs.foundryHosting }} | |
| coreChanged: ${{ steps.filter.outputs.core }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: filter | |
| with: | |
| filters: | | |
| dotnet: | |
| - 'dotnet/**' | |
| - '.github/workflows/dotnet-build-and-test.yml' | |
| cosmosdb: | |
| - 'dotnet/src/Microsoft.Agents.AI.CosmosNoSql/**' | |
| azurestorage: | |
| - 'dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/**' | |
| - 'dotnet/tests/Microsoft.Agents.AI.Hosting.AzureStorage.IntegrationTests/**' | |
| - 'dotnet/tests/Microsoft.Agents.AI.Hosting.AzureStorage.UnitTests/**' | |
| - 'dotnet/Directory.Packages.props' | |
| - '.github/workflows/dotnet-build-and-test.yml' | |
| # The Foundry hosted-agent IT is costly (builds a container, pushes to ACR, | |
| # provisions live agents). Only run it when the project under test, its | |
| # dependency chain, the test container, the test fixture, or their tooling | |
| # changed. Keep this list in sync with $hashedDirs in scripts/it-build-image.ps1. | |
| foundryHosting: | |
| - 'dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/**' | |
| - 'dotnet/src/Microsoft.Agents.AI.Foundry/**' | |
| - 'dotnet/src/Microsoft.Agents.AI/**' | |
| - 'dotnet/src/Microsoft.Agents.AI.Abstractions/**' | |
| - 'dotnet/src/Microsoft.Agents.AI.Workflows/**' | |
| - 'dotnet/tests/Foundry.Hosting.IntegrationTests/**' | |
| - 'dotnet/tests/Foundry.Hosting.IntegrationTests.TestContainer/**' | |
| - 'dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/**' | |
| - 'dotnet/Directory.Packages.props' | |
| - 'dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-build-image.ps1' | |
| - '.github/workflows/dotnet-build-and-test.yml' | |
| 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/src/Microsoft.Agents.AI.Workflows.Generators/**' | |
| - 'dotnet/eng/scripts/New-FilteredSolution.ps1' | |
| - 'dotnet/tests/Directory.Build.props' | |
| - 'dotnet/Directory.Packages.props' | |
| - 'dotnet/global.json' | |
| - '.github/workflows/dotnet-build-and-test.yml' | |
| # run only if 'dotnet' files were changed | |
| - name: dotnet tests | |
| if: steps.filter.outputs.dotnet == 'true' | |
| run: echo "Dotnet file" | |
| - name: dotnet CosmosDB tests | |
| if: steps.filter.outputs.cosmosdb == 'true' | |
| run: echo "Dotnet CosmosDB changes" | |
| # run only if not 'dotnet' files were changed | |
| - name: not dotnet tests | |
| if: steps.filter.outputs.dotnet != 'true' | |
| run: echo "NOT dotnet file" | |
| # Build the full solution (including samples) on all TFMs. No tests. | |
| dotnet-build: | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.dotnetChanges == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { targetFramework: "net10.0", os: "ubuntu-latest", configuration: Release } | |
| - { targetFramework: "net9.0", os: "windows-latest", configuration: Debug } | |
| - { targetFramework: "net8.0", os: "ubuntu-latest", configuration: Release } | |
| - { targetFramework: "net472", os: "windows-latest", configuration: Release } | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| . | |
| .github | |
| dotnet | |
| python | |
| declarative-agents | |
| - name: Free runner disk space | |
| uses: ./.github/actions/free-runner-disk-space | |
| - name: Setup dotnet | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| global-json-file: ${{ github.workspace }}/dotnet/global.json | |
| - name: Build dotnet solutions | |
| shell: bash | |
| run: | | |
| export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | tr '\n' ' ') | |
| for solution in $SOLUTIONS; do | |
| dotnet build $solution -c ${{ matrix.configuration }} --warnaserror | |
| done | |
| - name: Package install check | |
| shell: bash | |
| # All frameworks are only built for the release configuration, so we only run this step for the release configuration | |
| # and dotnet new doesn't support net472 | |
| if: matrix.configuration == 'Release' && matrix.targetFramework != 'net472' | |
| run: | | |
| TEMP_DIR=$(mktemp -d) | |
| export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | tr '\n' ' ') | |
| for solution in $SOLUTIONS; do | |
| dotnet pack $solution /property:TargetFrameworks=${{ matrix.targetFramework }} -c ${{ matrix.configuration }} --no-build --no-restore --output "$TEMP_DIR/artifacts" | |
| done | |
| pushd "$TEMP_DIR" | |
| # Create a new console app to test the package installation | |
| dotnet new console -f ${{ matrix.targetFramework }} --name packcheck --output consoleapp | |
| # Create minimal nuget.config and use only dotnet nuget commands | |
| echo '<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear /></packageSources></configuration>' > consoleapp/nuget.config | |
| # Add sources with local first using dotnet nuget commands | |
| dotnet nuget add source ../artifacts --name local --configfile consoleapp/nuget.config | |
| dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org --configfile consoleapp/nuget.config | |
| # Change to project directory to ensure local nuget.config is used | |
| pushd consoleapp | |
| dotnet add packcheck.csproj package Microsoft.Agents.AI --prerelease | |
| dotnet add packcheck.csproj package Microsoft.Agents.AI.LocalCodeAct --prerelease | |
| dotnet build -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} packcheck.csproj | |
| # Clean up | |
| popd | |
| popd | |
| rm -rf "$TEMP_DIR" | |
| # Unit and integration jobs share steps but run with separate permissions and environments. | |
| dotnet-test: | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.dotnetChanges == 'true' | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { targetFramework: "net10.0", os: "ubuntu-latest", configuration: Release, integration-tests: false } | |
| - { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: false } | |
| runs-on: ${{ matrix.os }} | |
| steps: &dotnet-test-steps | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| . | |
| .github | |
| dotnet | |
| docs/specs | |
| python | |
| declarative-agents | |
| - name: Free runner disk space | |
| uses: ./.github/actions/free-runner-disk-space | |
| # Branch and merge-queue unit checks always use the emulator; direct PR checks are path-selected. | |
| - name: Start Azure Cosmos DB Emulator | |
| if: ${{ runner.os == 'Windows' && (needs.paths-filter.outputs.cosmosDbChanges == 'true' || matrix.integration-tests || github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group') }} | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $emulatorPath = "$env:ProgramFiles\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe" | |
| $emulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" | |
| $maxRetries = 3 | |
| $timeout = 600 # 10 minutes instead of default 4 minutes | |
| $retryCount = 0 | |
| $started = $false | |
| Write-Host "======================================" -ForegroundColor Cyan | |
| Write-Host "Starting Azure Cosmos DB Emulator" -ForegroundColor Cyan | |
| Write-Host "Timeout: $timeout seconds, Max retries: $maxRetries" -ForegroundColor Cyan | |
| Write-Host "======================================" -ForegroundColor Cyan | |
| Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" | |
| while (-not $started -and $retryCount -lt $maxRetries) { | |
| $retryCount++ | |
| if ($retryCount -gt 1) { | |
| Write-Host "`n--- Retry attempt $retryCount of $maxRetries ---" -ForegroundColor Yellow | |
| # Stop and cleanup before retry | |
| Write-Host "Stopping existing emulator instances..." -ForegroundColor Yellow | |
| Stop-CosmosDbEmulator -ErrorAction SilentlyContinue | |
| Start-Sleep -Seconds 5 | |
| # Force kill if still running | |
| Get-Process -Name "Microsoft.Azure.Cosmos.Emulator" -ErrorAction SilentlyContinue | Stop-Process -Force | |
| Start-Sleep -Seconds 3 | |
| # Cleanup old data | |
| $dataPath = Join-Path $env:LOCALAPPDATA "CosmosDbEmulator" | |
| if (Test-Path $dataPath) { | |
| Write-Host "Cleaning up emulator data..." -ForegroundColor Yellow | |
| Remove-Item -Recurse -Force $dataPath -ErrorAction SilentlyContinue | |
| Start-Sleep -Seconds 2 | |
| } | |
| } | |
| try { | |
| Write-Host "`nStarting emulator (attempt $retryCount)..." -ForegroundColor Cyan | |
| Start-CosmosDbEmulator -NoUI -Key $emulatorKey -AllowNetworkAccess -NoFirewall | |
| # Wait for emulator to be ready with extended timeout | |
| $startTime = [DateTimeOffset]::Now | |
| $stopTime = $startTime.AddSeconds($timeout) | |
| $lastStatus = -1 | |
| Write-Host "Waiting for emulator to be ready..." -ForegroundColor Yellow | |
| do { | |
| Start-Sleep -Seconds 5 | |
| $elapsed = [int](([DateTimeOffset]::Now - $startTime).TotalSeconds) | |
| # Check status | |
| $statusProcess = Start-Process $emulatorPath -ArgumentList "/getstatus" -PassThru -Wait -NoNewWindow -ErrorAction SilentlyContinue | |
| $statusCode = if ($statusProcess) { $statusProcess.ExitCode } else { -1 } | |
| # Log status changes | |
| if ($statusCode -ne $lastStatus) { | |
| $statusMsg = switch ($statusCode) { | |
| 1 { "Emulator is starting..." } | |
| 2 { "Emulator is running!"; $started = $true } | |
| 3 { "Emulator is stopped" } | |
| default { "Unknown status (code: $statusCode)" } | |
| } | |
| Write-Host "[${elapsed}s] $statusMsg" -ForegroundColor $(if ($statusCode -eq 2) { "Green" } else { "Yellow" }) | |
| $lastStatus = $statusCode | |
| } | |
| if ($started) { break } | |
| # Check if process crashed | |
| $processRunning = Get-Process -Name "Microsoft.Azure.Cosmos.Emulator" -ErrorAction SilentlyContinue | |
| if (-not $processRunning -and $statusCode -ne 2) { | |
| Write-Warning "Emulator process is not running" | |
| break | |
| } | |
| } while ([DateTimeOffset]::Now -lt $stopTime) | |
| if (-not $started) { | |
| Write-Error "Emulator failed to start within $timeout seconds" | |
| } | |
| } | |
| catch { | |
| Write-Warning "Error on attempt ${retryCount}: $_" | |
| if ($retryCount -ge $maxRetries) { throw } | |
| } | |
| } | |
| if ($started) { | |
| Write-Host "`n======================================" -ForegroundColor Green | |
| Write-Host "Cosmos DB Emulator started successfully!" -ForegroundColor Green | |
| Write-Host "======================================`n" -ForegroundColor Green | |
| # Verify connectivity | |
| try { | |
| $response = Invoke-WebRequest -Uri "https://localhost:8081/" -UseBasicParsing -SkipCertificateCheck -TimeoutSec 10 -ErrorAction Stop | |
| Write-Host "Emulator is responding (Status: $($response.StatusCode))" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Emulator may not be fully ready: $_" | |
| } | |
| echo "COSMOSDB_EMULATOR_AVAILABLE=true" >> $env:GITHUB_ENV | |
| } | |
| else { | |
| Write-Host "`n======================================" -ForegroundColor Red | |
| Write-Host "Failed to start emulator after $maxRetries attempts" -ForegroundColor Red | |
| Write-Host "======================================" -ForegroundColor Red | |
| # Show diagnostic info | |
| $logPath = Join-Path $env:LOCALAPPDATA "CosmosDbEmulator\cosmosdb.log" | |
| if (Test-Path $logPath) { | |
| Write-Host "`nLast 30 lines of emulator log:" -ForegroundColor Yellow | |
| Get-Content $logPath -Tail 30 | |
| } | |
| throw "Failed to start Cosmos DB Emulator" | |
| } | |
| - name: Start Azurite Blob service | |
| if: ${{ runner.os == 'Linux' && (needs.paths-filter.outputs.azureStorageChanges == 'true' || matrix.integration-tests || github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group') }} | |
| shell: bash | |
| run: | | |
| docker run --detach --rm \ | |
| --name azurite \ | |
| --publish 10000:10000 \ | |
| mcr.microsoft.com/azure-storage/azurite:3.35.0@sha256:647c63a91102a9d8e8000aab803436e1fc85fbb285e7ce830a82ee5d6661cf37 \ | |
| azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --skipApiVersionCheck | |
| for attempt in {1..30}; do | |
| if (echo > /dev/tcp/127.0.0.1/10000) > /dev/null 2>&1; then | |
| echo "AZURITE_AVAILABLE=true" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| docker logs azurite | |
| exit 1 | |
| - name: Setup dotnet | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| global-json-file: ${{ github.workspace }}/dotnet/global.json | |
| - name: Generate test solution (no samples) | |
| shell: pwsh | |
| run: | | |
| ./dotnet/eng/scripts/New-FilteredSolution.ps1 ` | |
| -Solution dotnet/agent-framework-dotnet.slnx ` | |
| -TargetFramework ${{ matrix.targetFramework }} ` | |
| -Configuration ${{ matrix.configuration }} ` | |
| -ExcludeSamples ` | |
| -OutputPath dotnet/filtered.slnx ` | |
| -Verbose | |
| - name: Build src and tests | |
| shell: bash | |
| run: dotnet build dotnet/filtered.slnx -c ${{ matrix.configuration }} -f ${{ matrix.targetFramework }} --warnaserror | |
| - name: Generate test-type filtered solutions | |
| shell: pwsh | |
| run: | | |
| $commonArgs = @{ | |
| Solution = "dotnet/filtered.slnx" | |
| TargetFramework = "${{ matrix.targetFramework }}" | |
| Configuration = "${{ matrix.configuration }}" | |
| Verbose = $true | |
| } | |
| ./dotnet/eng/scripts/New-FilteredSolution.ps1 @commonArgs ` | |
| -TestProjectNameIncludeFilter "*UnitTests*" ` | |
| -OutputPath dotnet/filtered-unit.slnx | |
| ./dotnet/eng/scripts/New-FilteredSolution.ps1 @commonArgs ` | |
| -TestProjectNameIncludeFilter "*IntegrationTests*" ` | |
| -OutputPath dotnet/filtered-integration.slnx | |
| - name: Run Unit Tests | |
| if: ${{ !matrix.integration-tests }} | |
| shell: pwsh | |
| working-directory: dotnet | |
| run: | | |
| $coverageSettings = Join-Path $PWD "tests/coverage.runsettings" | |
| $coverageArgs = @() | |
| if ("${{ matrix.targetFramework }}" -eq "${{ env.COVERAGE_FRAMEWORK }}") { | |
| $coverageArgs = @( | |
| "--coverage", | |
| "--coverage-output-format", "cobertura", | |
| "--coverage-settings", $coverageSettings, | |
| "--results-directory", "../TestResults/Coverage/" | |
| ) | |
| } | |
| dotnet test --solution ./filtered-unit.slnx ` | |
| -f ${{ matrix.targetFramework }} ` | |
| -c ${{ matrix.configuration }} ` | |
| --no-build -v Normal ` | |
| --report-xunit-trx ` | |
| --ignore-exit-code 8 ` | |
| @coverageArgs | |
| env: | |
| # Cosmos DB Emulator connection settings | |
| COSMOSDB_ENDPOINT: https://localhost:8081 | |
| COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== | |
| - name: Log event name and matrix integration-tests | |
| shell: bash | |
| run: echo "github.event_name:${{ github.event_name }} matrix.integration-tests:${{ matrix.integration-tests }} github.event.action:${{ github.event.action }} github.event.pull_request.merged:${{ github.event.pull_request.merged }}" | |
| - name: Azure CLI Login | |
| if: matrix.integration-tests | |
| uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Run Integration Tests | |
| shell: pwsh | |
| working-directory: dotnet | |
| if: matrix.integration-tests | |
| run: | | |
| dotnet test --solution ./filtered-integration.slnx ` | |
| -f ${{ matrix.targetFramework }} ` | |
| -c ${{ matrix.configuration }} ` | |
| --no-build -v Normal ` | |
| --report-xunit-trx ` | |
| --report-xunit-junit ` | |
| --results-directory ../IntegrationTestResults/ ` | |
| --ignore-exit-code 8 ` | |
| --filter-not-trait "Category=IntegrationDisabled" ` | |
| --filter-not-trait "Category=FoundryHostedAgents" ` | |
| --parallel-algorithm aggressive ` | |
| --max-threads 2.0x | |
| env: | |
| # Cosmos DB Emulator connection settings | |
| COSMOSDB_ENDPOINT: https://localhost:8081 | |
| COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== | |
| # 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 }} | |
| # Microsoft 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 }} | |
| # Anthropic Models | |
| # Disable Anthropic tests by not providing environment vars until 404 failure is resolved | |
| # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| # ANTHROPIC_CHAT_MODEL_NAME: ${{ vars.ANTHROPIC_CHAT_MODEL_NAME }} | |
| # ANTHROPIC_REASONING_MODEL_NAME: ${{ vars.ANTHROPIC_REASONING_MODEL_NAME }} | |
| # Generate test reports and check coverage | |
| - name: Generate test reports | |
| if: ${{ !matrix.integration-tests && matrix.targetFramework == env.COVERAGE_FRAMEWORK }} | |
| uses: danielpalme/ReportGenerator-GitHub-Action@861674232075b2c6bd1405a04c4bb9b150653e81 # 5.5.12 | |
| with: | |
| reports: "./TestResults/Coverage/**/*.cobertura.xml" | |
| targetdir: "./TestResults/Reports" | |
| reporttypes: "HtmlInline;JsonSummary;Cobertura" | |
| - name: Upload coverage report artifact | |
| if: ${{ !matrix.integration-tests && matrix.targetFramework == env.COVERAGE_FRAMEWORK }} | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: CoverageReport-${{ matrix.os }}-${{ matrix.targetFramework }}-${{ matrix.configuration }} # Artifact name | |
| path: ./TestResults/Reports # Directory containing files to upload | |
| - name: Check coverage | |
| if: ${{ !matrix.integration-tests && matrix.targetFramework == env.COVERAGE_FRAMEWORK }} | |
| shell: pwsh | |
| run: ./dotnet/eng/scripts/dotnet-check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD | |
| - name: Upload integration test results | |
| if: always() && matrix.integration-tests | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: dotnet-test-results-${{ matrix.targetFramework }}-${{ matrix.os }} | |
| path: | | |
| IntegrationTestResults/**/*.junit | |
| IntegrationTestResults/**/*.junit.xml | |
| if-no-files-found: ignore | |
| # Repository PRs and reviewer-approved merge groups can run integration checks before merging. | |
| dotnet-integration-test: | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.dotnetChanges == 'true' && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')) | |
| permissions: | |
| contents: read | |
| id-token: "write" | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { targetFramework: "net10.0", os: "ubuntu-latest", configuration: Release, integration-tests: true } | |
| - { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: true } | |
| runs-on: ${{ matrix.os }} | |
| environment: integration | |
| steps: *dotnet-test-steps | |
| dotnet-upload-coverage: | |
| needs: [paths-filter, dotnet-test] | |
| if: needs.paths-filter.outputs.dotnetChanges == 'true' && github.event_name != 'merge_group' && ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') || (github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch)) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: read | |
| code-quality: write | |
| pull-requests: read | |
| steps: | |
| - name: Download coverage report artifact | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| name: CoverageReport-ubuntu-latest-${{ env.COVERAGE_FRAMEWORK }}-Release | |
| path: ./TestResults/Reports | |
| - name: Upload coverage report to GitHub | |
| uses: actions/upload-code-coverage@2b21a77928be8d5168c2b9581a67f2adbebacc52 # v1.4.4 | |
| with: | |
| file: ./TestResults/Reports/Cobertura.xml | |
| language: CSharp | |
| label: code-coverage/dotnet | |
| fail-on-error: false | |
| - name: Summarize GitHub coverage report | |
| run: echo ".NET coverage is uploaded to GitHub's integrated coverage report with label \`code-coverage/dotnet\`." >> "$GITHUB_STEP_SUMMARY" | |
| # The Foundry hosted-agent IT is costly (it builds a container, pushes to ACR, and provisions | |
| # live agents on a separate Foundry project). Running it in its own job keeps the overall | |
| # workflow time roughly flat: it executes in parallel to dotnet-build and dotnet-test and is | |
| # gated on paths-filter.outputs.foundryHostingChanges so unrelated edits skip the work. | |
| dotnet-foundry-hosted-it: | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.foundryHostingChanges == 'true' && (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')) | |
| permissions: | |
| contents: read | |
| id-token: "write" | |
| runs-on: ubuntu-latest | |
| environment: integration | |
| # Scenario agents share @latest endpoints across PRs and branch runs. | |
| concurrency: | |
| group: dotnet-foundry-hosted-integration | |
| cancel-in-progress: false | |
| queue: max | |
| env: | |
| configuration: Release | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| . | |
| .github | |
| dotnet | |
| python | |
| - name: Free runner disk space | |
| uses: ./.github/actions/free-runner-disk-space | |
| - name: Setup dotnet | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| global-json-file: ${{ github.workspace }}/dotnet/global.json | |
| # Build the test csproj directly instead of a filtered slnx + -f override. | |
| # The test project pins TargetFrameworks=net10.0 and its ProjectReference closure | |
| # gives MSBuild a single-rooted graph, so each multi-targeted dependency is invoked | |
| # exactly once for net10.0. This avoids the MSB3026/MSB3491/MSB4018/MSB3883 file-lock | |
| # collisions caused by parallel inner-builds racing on shared bin/obj output paths | |
| # under the previous slnx + global TFM override approach. | |
| - name: Build Foundry hosted IT (and its deps) | |
| shell: bash | |
| run: dotnet build dotnet/tests/Foundry.Hosting.IntegrationTests/Foundry.Hosting.IntegrationTests.csproj -c "$configuration" --warnaserror | |
| - name: Azure CLI Login | |
| uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| # We rebuild and push the test container image on every IT run so framework code changes | |
| # are picked up; the image tag is content-hashed across the test container source AND its | |
| # framework project references, so identical content is a no-op push. | |
| # | |
| # The script always passes --no-dependencies to dotnet publish so publish never re-touches | |
| # the framework lib DLLs the prior "Build Foundry hosted IT (and its deps)" step produced. | |
| # This structurally eliminates the MSB3026 collision that VBCSCompiler from the prebuild | |
| # would otherwise cause by holding file handles to those DLLs. Do not remove the prebuild | |
| # step: the subsequent `dotnet test --no-build` step and the publish's ProjectReference | |
| # resolution both depend on the prebuilt outputs being present. | |
| - name: Build and push Foundry Hosted Agents test container | |
| id: build-foundry-hosted-image | |
| shell: pwsh | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| $registry = "${{ vars.IT_HOSTED_AGENT_REGISTRY }}" | |
| if ([string]::IsNullOrWhiteSpace($registry)) { | |
| throw "IT_HOSTED_AGENT_REGISTRY not set in the integration environment." | |
| } | |
| & "${{ github.workspace }}/dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-build-image.ps1" -Registry $registry | Tee-Object -FilePath $env:GITHUB_ENV -Append | |
| - name: Run Foundry Hosted Agents Integration Tests | |
| shell: pwsh | |
| working-directory: dotnet | |
| run: | | |
| dotnet test --project tests/Foundry.Hosting.IntegrationTests/Foundry.Hosting.IntegrationTests.csproj ` | |
| -c $env:configuration ` | |
| --no-build -v Normal ` | |
| --report-xunit-trx ` | |
| --ignore-exit-code 8 ` | |
| --filter-trait "Category=FoundryHostedAgents" | |
| env: | |
| AZURE_AI_PROJECT_ENDPOINT: ${{ vars.IT_HOSTED_AGENT_PROJECT_ENDPOINT }} | |
| AZURE_AI_MODEL_DEPLOYMENT_NAME: ${{ vars.IT_HOSTED_AGENT_MODEL_DEPLOYMENT_NAME }} | |
| # Use the dedicated hosted-agent Search index for both Search scenarios. | |
| AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }} | |
| AZURE_SEARCH_INDEX_NAME: ${{ vars.FOUNDRY_HOSTED_AGENT_SEARCH_INDEX_NAME }} | |
| # IT_HOSTED_AGENT_IMAGE was exported into $GITHUB_ENV by the previous step. | |
| # 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-integration-test, dotnet-foundry-hosted-it] | |
| steps: | |
| - name: Get Date | |
| shell: bash | |
| run: | | |
| echo "date=$(date +'%m/%d/%Y %H:%M:%S')" >> "$GITHUB_ENV" | |
| - name: Run Type is Daily | |
| if: ${{ github.event_name == 'schedule' }} | |
| shell: bash | |
| run: | | |
| echo "run_type=Daily" >> "$GITHUB_ENV" | |
| - name: Run Type is Manual | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| shell: bash | |
| run: | | |
| echo "run_type=Manual" >> "$GITHUB_ENV" | |
| - name: Run Type is ${{ github.event_name }} | |
| if: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'}} | |
| shell: bash | |
| run: | | |
| echo "run_type=${{ github.event_name }}" >> "$GITHUB_ENV" | |
| - name: Fail workflow if tests failed | |
| id: check_tests_failed | |
| if: contains(join(needs.*.result, ','), 'failure') | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: core.setFailed('Integration Tests Failed!') | |
| - name: Fail workflow if tests cancelled | |
| id: check_tests_cancelled | |
| if: contains(join(needs.*.result, ','), 'cancelled') | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: core.setFailed('Integration Tests Cancelled!') | |
| # Integration test trend report (aggregates JUnit XML results from dotnet test jobs) | |
| dotnet-integration-test-report: | |
| name: Integration Test Report | |
| if: > | |
| always() && | |
| (needs.dotnet-integration-test.result == 'success' || | |
| needs.dotnet-integration-test.result == 'failure') | |
| needs: [dotnet-integration-test] | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: python | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/python-setup | |
| python | |
| - name: Set up python and install the project | |
| uses: ./.github/actions/python-setup | |
| with: | |
| python-version: "3.13" | |
| os: ${{ runner.os }} | |
| - name: Download all test results from current run | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| pattern: dotnet-test-results-* | |
| path: dotnet-test-results/ | |
| - name: Restore report history cache | |
| uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: python/dotnet-integration-report-history.json | |
| key: dotnet-integration-report-history-${{ github.run_id }} | |
| restore-keys: | | |
| dotnet-integration-report-history- | |
| - name: Generate trend report | |
| run: > | |
| uv run python scripts/integration_test_report/aggregate.py | |
| ../dotnet-test-results/ | |
| dotnet-integration-report-history.json | |
| dotnet-integration-test-report.md | |
| - name: Post to Job Summary | |
| if: always() | |
| run: cat dotnet-integration-test-report.md >> $GITHUB_STEP_SUMMARY | |
| - name: Save report history cache | |
| if: always() | |
| uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: python/dotnet-integration-report-history.json | |
| key: dotnet-integration-report-history-${{ github.run_id }} | |
| - name: Upload trend report | |
| if: always() | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: dotnet-integration-test-report | |
| path: | | |
| python/dotnet-integration-test-report.md | |
| python/dotnet-integration-report-history.json |