Files
agent-framework/.github/workflows/dotnet-build-and-test.yml
T
Eduard van Valkenburg 7e9c043c4c Python: Improve PR template and breaking-change label automation (#6473)
* Improve PR template and breaking-change label automation

- Add a structured "Related Issue" section using GitHub closing keywords
- Add a Review Guide prompt (major changes, impact, reviewer focus) with a
  note that the focus item is for human reviewers only
- Add checklist items for issue linkage / no duplicate PRs and invert the
  breaking-change item (checked = not breaking)
- Extend label-title-prefix to prepend [BREAKING] when the "breaking change"
  label is added
- Add label-breaking-change workflow to apply the "breaking change" label
  when a PR title contains [BREAKING]

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add pull-requests agent skill with dotnet/python links

- Add root .github/skills/pull-requests/SKILL.md covering PR description
  authoring (following the PR template) and the review-comment workflow
  (review -> plan -> user review -> implement -> reply to all -> resolve)
- Symlink the skill from python/.github/skills and dotnet/.github/skills
- Reference the skill from python/AGENTS.md and dotnet/AGENTS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fold breaking-change labeling into label-pr workflow

Move the title -> 'breaking change' label logic into the existing label-pr
workflow (which already applies the python/.NET labels) and drop the separate
label-breaking-change workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR title prefix review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Pin patched MessagePack for .NET restore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert MessagePack central pin

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move title prefix tests out of tracked GitHub tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Exclude skill docs from CI path filters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Match skill symlinks in CI path exclusions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Exclude AGENTS docs from CI path filters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Scope title-prefix normalization to a real prefix

The normalization branch in addTitlePrefix matched ^Python (no colon), so
titles like "Python samples improvements" or "Pythonic refactor" were treated
as already-prefixed and only re-cased, never receiving the "Python: " prefix.
Scope the match to ^<prefix>:\s* so only an actual existing prefix is
normalized; otherwise the prefix is prepended. Same fix applies to the .NET
prefix (e.g. ".NETStandard bump").

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 10:55:23 +00:00

650 lines
28 KiB
YAML

#
# 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
id-token: "write"
jobs:
paths-filter:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
id: filter
with:
filters: |
dotnet:
- 'dotnet/**'
- '!dotnet/AGENTS.md'
- '!dotnet/**/AGENTS.md'
- '!dotnet/.github/skills/*'
- '!dotnet/.github/skills/**'
cosmosdb:
- 'dotnet/src/Microsoft.Agents.AI.CosmosNoSql/**'
# 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'
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/**'
- '.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.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 build -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} packcheck.csproj
# Clean up
popd
popd
rm -rf "$TEMP_DIR"
# Build src+tests only (no samples) for a single TFM and run tests.
dotnet-test:
needs: paths-filter
if: needs.paths-filter.outputs.dotnetChanges == 'true'
strategy:
fail-fast: false
matrix:
include:
- { targetFramework: "net10.0", os: "ubuntu-latest", configuration: Release, integration-tests: true, environment: "integration" }
- { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: true, environment: "integration" }
runs-on: ${{ matrix.os }}
environment: ${{ matrix.environment }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
sparse-checkout: |
.
.github
dotnet
python
declarative-agents
- name: Free runner disk space
uses: ./.github/actions/free-runner-disk-space
# Start Cosmos DB Emulator for all integration tests and only for unit tests when CosmosDB changes happened)
- name: Start Azure Cosmos DB Emulator
if: ${{ runner.os == 'Windows' && (needs.paths-filter.outputs.cosmosDbChanges == 'true' || (github.event_name != 'pull_request' && matrix.integration-tests)) }}
shell: pwsh
run: |
Write-Host "Launching Azure Cosmos DB Emulator"
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
Start-CosmosDbEmulator -NoUI -Key "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
echo "COSMOSDB_EMULATOR_AVAILABLE=true" >> $env:GITHUB_ENV
- name: Setup dotnet
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.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*" `
-TestProjectNameExcludeFilter "*DurableTask.IntegrationTests*","*AzureFunctions.IntegrationTests*" `
-OutputPath dotnet/filtered-integration.slnx
- name: Run Unit 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: github.event_name != 'pull_request' && matrix.integration-tests
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
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: github.event_name != 'pull_request' && matrix.integration-tests
run: |
dotnet test --solution ./filtered-integration.slnx `
-f ${{ matrix.targetFramework }} `
-c ${{ matrix.configuration }} `
--no-build -v Normal `
--report-xunit-trx `
--report-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 }}
# 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 }}
# Anthropic Models
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.targetFramework == env.COVERAGE_FRAMEWORK
uses: danielpalme/ReportGenerator-GitHub-Action@2a82782178b2816d9d6960a7345fdd164791b323 # 5.5.3
with:
reports: "./TestResults/Coverage/**/*.cobertura.xml"
targetdir: "./TestResults/Reports"
reporttypes: "HtmlInline;JsonSummary"
- name: Upload coverage report artifact
if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: CoverageReport-${{ matrix.os }}-${{ matrix.targetFramework }}-${{ matrix.configuration }} # Artifact name
path: ./TestResults/Reports # Directory containing files to upload
- name: Check coverage
if: 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() && github.event_name != 'pull_request' && matrix.integration-tests
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dotnet-test-results-${{ matrix.targetFramework }}-${{ matrix.os }}
path: IntegrationTestResults/**/*.junit
if-no-files-found: ignore
# 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: github.event_name != 'pull_request' && needs.paths-filter.outputs.foundryHostingChanges == 'true'
runs-on: ubuntu-latest
environment: integration
env:
configuration: Release
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.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@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
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 }}
# Azure AI Search (for the azure-search-rag scenario). Reuses the integration
# environment secrets shared with python-sample-validation.yml. The index is
# provisioned out of band; see dotnet/tests/Foundry.Hosting.IntegrationTests/README.md
# for the required schema and seed content.
AZURE_SEARCH_ENDPOINT: ${{ secrets.AZURE_SEARCH_ENDPOINT }}
AZURE_SEARCH_INDEX_NAME: ${{ secrets.AZURE_SEARCH_INDEX_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: >
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # 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@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 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, dotnet-test-functions]
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
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() &&
github.event_name != 'pull_request' &&
(contains(join(needs.*.result, ','), 'success') ||
contains(join(needs.*.result, ','), 'failure'))
needs: [dotnet-test, dotnet-test-functions]
runs-on: ubuntu-latest
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: dotnet-test-results-*
path: dotnet-test-results/
- name: Restore report history cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dotnet-integration-test-report
path: |
python/dotnet-integration-test-report.md
python/dotnet-integration-report-history.json