# # 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 }} steps: - uses: actions/checkout@v6 - uses: dorny/paths-filter@v3 id: filter with: filters: | dotnet: - 'dotnet/**' cosmosdb: - 'dotnet/src/Microsoft.Agents.AI.CosmosNoSql/**' # 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" dotnet-build-and-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: "net9.0", os: "windows-latest", configuration: Debug } - { targetFramework: "net8.0", os: "ubuntu-latest", configuration: Release } - { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: true, environment: "integration" } runs-on: ${{ matrix.os }} environment: ${{ matrix.environment }} steps: - uses: actions/checkout@v6 with: persist-credentials: false sparse-checkout: | . .github dotnet python workflow-samples # 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 "COSMOS_EMULATOR_AVAILABLE=true" >> $env:GITHUB_ENV - name: Setup dotnet uses: actions/setup-dotnet@v5.1.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 '' > 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" - name: Run Unit Tests shell: bash run: | export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | tr '\n' ' ') for project in $UT_PROJECTS; do # Query the project's target frameworks using MSBuild with the current configuration target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r') # Check if the project supports the target framework if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then if [[ "${{ matrix.targetFramework }}" == "${{ env.COVERAGE_FRAMEWORK }}" ]]; then dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute=GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute else dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx fi else echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)" fi done 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@v2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} 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: bash if: github.event_name != 'pull_request' && matrix.integration-tests run: | export INTEGRATION_TEST_PROJECTS=$(find ./dotnet -type f -name "*IntegrationTests.csproj" | tr '\n' ' ') for project in $INTEGRATION_TEST_PROJECTS; do # Query the project's target frameworks using MSBuild with the current configuration target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r') # Check if the project supports the target framework if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx else echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)" fi done env: # Cosmos DB Emulator connection settings COSMOSDB_ENDPOINT: https://localhost:8081 COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== # OpenAI Models OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }} OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }} OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }} # Azure OpenAI Models AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }} AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }} # Azure AI Foundry AzureAI__Endpoint: ${{ secrets.AZUREAI__ENDPOINT }} AzureAI__DeploymentName: ${{ vars.AZUREAI__DEPLOYMENTNAME }} AzureAI__BingConnectionId: ${{ vars.AZUREAI__BINGCONECTIONID }} FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }} FOUNDRY_MEDIA_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MEDIA_DEPLOYMENT_NAME }} FOUNDRY_MODEL_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MODEL_DEPLOYMENT_NAME }} FOUNDRY_CONNECTION_GROUNDING_TOOL: ${{ vars.FOUNDRY_CONNECTION_GROUNDING_TOOL }} # Generate test reports and check coverage - name: Generate test reports if: matrix.targetFramework == env.COVERAGE_FRAMEWORK uses: danielpalme/ReportGenerator-GitHub-Action@5.5.1 with: reports: "./TestResults/Coverage/**/coverage.cobertura.xml" targetdir: "./TestResults/Reports" reporttypes: "HtmlInline;JsonSummary" - name: Upload coverage report artifact if: matrix.targetFramework == env.COVERAGE_FRAMEWORK uses: actions/upload-artifact@v6 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: .github/workflows/dotnet-check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD # 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-and-test] 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@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@v8 with: script: core.setFailed('Integration Tests Cancelled!')