From 017dc053c7da1ddd4f47a8b2d2f3af897819eb30 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:31:34 +0000 Subject: [PATCH 1/2] Separate build and test into parallel jobs --- .../New-FrameworkFilteredSolution.ps1 | 33 ++++++-- .github/workflows/dotnet-build-and-test.yml | 82 ++++++++++++++----- 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/.github/workflows/New-FrameworkFilteredSolution.ps1 b/.github/workflows/New-FrameworkFilteredSolution.ps1 index aa05099612..3302a0391d 100644 --- a/.github/workflows/New-FrameworkFilteredSolution.ps1 +++ b/.github/workflows/New-FrameworkFilteredSolution.ps1 @@ -22,10 +22,13 @@ .PARAMETER Configuration Optional MSBuild configuration used when querying TargetFrameworks. Defaults to Debug. -.PARAMETER ProjectNameFilter +.PARAMETER TestProjectNameFilter 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 ExcludeSamples + When specified, removes all projects under the samples/ directory from the solution. + .PARAMETER OutputPath Optional output path for the filtered .slnx file. If not specified, a temp file is created. @@ -36,7 +39,7 @@ .EXAMPLE # Generate a solution with only unit test projects - ./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net10.0 -ProjectNameFilter "*UnitTests*" -OutputPath filtered-unit.slnx + ./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameFilter "*UnitTests*" -OutputPath filtered-unit.slnx .EXAMPLE # Inline usage with dotnet test (PowerShell) @@ -53,7 +56,9 @@ param( [string]$Configuration = "Debug", - [string]$ProjectNameFilter, + [string]$TestProjectNameFilter, + + [switch]$ExcludeSamples, [string]$OutputPath ) @@ -71,19 +76,31 @@ if (-not $OutputPath) { # Parse the .slnx XML [xml]$slnx = Get-Content $solutionPath -Raw -# Find all Project elements with paths containing "tests/" -$testProjects = $slnx.SelectNodes("//Project[contains(@Path, 'tests/')]") - $removed = @() $kept = @() +# Remove sample projects if requested +if ($ExcludeSamples) { + $sampleProjects = $slnx.SelectNodes("//Project[contains(@Path, 'samples/')]") + foreach ($proj in $sampleProjects) { + $projRelPath = $proj.GetAttribute("Path") + Write-Verbose "Removing (sample): $projRelPath" + $removed += $projRelPath + $proj.ParentNode.RemoveChild($proj) | Out-Null + } + Write-Host "Removed $($sampleProjects.Count) sample project(s)." -ForegroundColor Yellow +} + +# Find all Project elements with paths containing "tests/" +$testProjects = $slnx.SelectNodes("//Project[contains(@Path, 'tests/')]") + foreach ($proj in $testProjects) { $projRelPath = $proj.GetAttribute("Path") $projFullPath = Join-Path $solutionDir $projRelPath $projFileName = Split-Path $projRelPath -Leaf # Filter by project name pattern if specified - if ($ProjectNameFilter -and ($projFileName -notlike $ProjectNameFilter)) { + if ($TestProjectNameFilter -and ($projFileName -notlike $TestProjectNameFilter)) { Write-Verbose "Removing (name filter): $projRelPath" $removed += $projRelPath $proj.ParentNode.RemoveChild($proj) | Out-Null @@ -117,7 +134,7 @@ $slnx.Save($OutputPath) # Report results to stderr so stdout is clean for piping Write-Host "Filtered solution written to: $OutputPath" -ForegroundColor Green if ($removed.Count -gt 0) { - Write-Host "Removed $($removed.Count) test project(s) not targeting ${TargetFramework}:" -ForegroundColor Yellow + Write-Host "Removed $($removed.Count) project(s):" -ForegroundColor Yellow foreach ($r in $removed) { Write-Host " - $r" -ForegroundColor Yellow } diff --git a/.github/workflows/dotnet-build-and-test.yml b/.github/workflows/dotnet-build-and-test.yml index f62055f7ff..9899b6ee2e 100644 --- a/.github/workflows/dotnet-build-and-test.yml +++ b/.github/workflows/dotnet-build-and-test.yml @@ -59,20 +59,20 @@ jobs: if: steps.filter.outputs.dotnet != 'true' run: echo "NOT dotnet file" - dotnet-build-and-test: + # 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, integration-tests: true, environment: "integration" } + - { 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, integration-tests: true, environment: "integration" } + - { targetFramework: "net472", os: "windows-latest", configuration: Release } runs-on: ${{ matrix.os }} - environment: ${{ matrix.environment }} steps: - uses: actions/checkout@v6 with: @@ -84,16 +84,6 @@ jobs: 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: @@ -140,20 +130,74 @@ jobs: popd rm -rf "$TEMP_DIR" - - name: Generate filtered solutions + # 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@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: Generate test solution (no samples) + shell: pwsh + run: | + .github/workflows/New-FrameworkFilteredSolution.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/agent-framework-dotnet.slnx" + Solution = "dotnet/filtered.slnx" TargetFramework = "${{ matrix.targetFramework }}" Configuration = "${{ matrix.configuration }}" Verbose = $true } .github/workflows/New-FrameworkFilteredSolution.ps1 @commonArgs ` - -ProjectNameFilter "*UnitTests*" ` + -TestProjectNameFilter "*UnitTests*" ` -OutputPath dotnet/filtered-unit.slnx .github/workflows/New-FrameworkFilteredSolution.ps1 @commonArgs ` - -ProjectNameFilter "*IntegrationTests*" ` + -TestProjectNameFilter "*IntegrationTests*" ` -OutputPath dotnet/filtered-integration.slnx - name: Run Unit Tests @@ -262,7 +306,7 @@ jobs: dotnet-build-and-test-check: if: always() runs-on: ubuntu-latest - needs: [dotnet-build-and-test] + needs: [dotnet-build, dotnet-test] steps: - name: Get Date shell: bash From 5ea03b913efa78b2d05f49f9a54c53ce9460623a Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:48:42 +0000 Subject: [PATCH 2/2] Filter source projects by framework for tests build --- ...dSolution.ps1 => New-FilteredSolution.ps1} | 32 +++++++++---------- .github/workflows/dotnet-build-and-test.yml | 6 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) rename .github/workflows/{New-FrameworkFilteredSolution.ps1 => New-FilteredSolution.ps1} (73%) diff --git a/.github/workflows/New-FrameworkFilteredSolution.ps1 b/.github/workflows/New-FilteredSolution.ps1 similarity index 73% rename from .github/workflows/New-FrameworkFilteredSolution.ps1 rename to .github/workflows/New-FilteredSolution.ps1 index 3302a0391d..2871f5f36b 100644 --- a/.github/workflows/New-FrameworkFilteredSolution.ps1 +++ b/.github/workflows/New-FilteredSolution.ps1 @@ -3,15 +3,14 @@ <# .SYNOPSIS - Generates a filtered .slnx solution file that only includes test projects supporting a given target framework. + Generates a filtered .slnx solution file by removing projects that don't match the specified criteria. .DESCRIPTION - Parses a .slnx solution file and queries each test project's TargetFrameworks using MSBuild. - Removes test projects that don't support the specified target framework, writes the result - to a temporary or specified output path, and prints the output path. - - This is useful for running `dotnet test --solution` with MTP (Microsoft Testing Platform), - which requires all test projects in the solution to support the requested target framework. + Parses a .slnx solution file and applies one or more filters: + - Removes projects that don't support the specified target framework (via MSBuild query). + - Optionally removes all sample projects (under samples/). + - Optionally filters test projects by name pattern (e.g., only *UnitTests*). + Writes the filtered solution to the specified output path and prints the path. .PARAMETER Solution Path to the source .slnx solution file. @@ -34,16 +33,16 @@ .EXAMPLE # Generate a filtered solution and run tests - $filtered = ./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net472 + $filtered = .github/workflows/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472 dotnet test --solution $filtered --no-build -f net472 .EXAMPLE # Generate a solution with only unit test projects - ./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameFilter "*UnitTests*" -OutputPath filtered-unit.slnx + .github/workflows/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net10.0 -TestProjectNameFilter "*UnitTests*" -OutputPath filtered-unit.slnx .EXAMPLE # Inline usage with dotnet test (PowerShell) - dotnet test --solution (./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net472) --no-build -f net472 + dotnet test --solution (.github/workflows/New-FilteredSolution.ps1 -Solution dotnet/agent-framework-dotnet.slnx -TargetFramework net472) --no-build -f net472 #> [CmdletBinding()] @@ -91,16 +90,17 @@ if ($ExcludeSamples) { Write-Host "Removed $($sampleProjects.Count) sample project(s)." -ForegroundColor Yellow } -# Find all Project elements with paths containing "tests/" -$testProjects = $slnx.SelectNodes("//Project[contains(@Path, 'tests/')]") +# Filter all remaining projects by target framework +$allProjects = $slnx.SelectNodes("//Project") -foreach ($proj in $testProjects) { +foreach ($proj in $allProjects) { $projRelPath = $proj.GetAttribute("Path") $projFullPath = Join-Path $solutionDir $projRelPath $projFileName = Split-Path $projRelPath -Leaf + $isTestProject = $projRelPath -like "*tests/*" - # Filter by project name pattern if specified - if ($TestProjectNameFilter -and ($projFileName -notlike $TestProjectNameFilter)) { + # Filter test projects by name pattern if specified + if ($isTestProject -and $TestProjectNameFilter -and ($projFileName -notlike $TestProjectNameFilter)) { Write-Verbose "Removing (name filter): $projRelPath" $removed += $projRelPath $proj.ParentNode.RemoveChild($proj) | Out-Null @@ -139,7 +139,7 @@ if ($removed.Count -gt 0) { Write-Host " - $r" -ForegroundColor Yellow } } -Write-Host "Kept $($kept.Count) test project(s)." -ForegroundColor Green +Write-Host "Kept $($kept.Count) project(s)." -ForegroundColor Green # Output the path for piping Write-Output $OutputPath diff --git a/.github/workflows/dotnet-build-and-test.yml b/.github/workflows/dotnet-build-and-test.yml index 9899b6ee2e..e79ff15f6d 100644 --- a/.github/workflows/dotnet-build-and-test.yml +++ b/.github/workflows/dotnet-build-and-test.yml @@ -172,7 +172,7 @@ jobs: - name: Generate test solution (no samples) shell: pwsh run: | - .github/workflows/New-FrameworkFilteredSolution.ps1 ` + .github/workflows/New-FilteredSolution.ps1 ` -Solution dotnet/agent-framework-dotnet.slnx ` -TargetFramework ${{ matrix.targetFramework }} ` -Configuration ${{ matrix.configuration }} ` @@ -193,10 +193,10 @@ jobs: Configuration = "${{ matrix.configuration }}" Verbose = $true } - .github/workflows/New-FrameworkFilteredSolution.ps1 @commonArgs ` + .github/workflows/New-FilteredSolution.ps1 @commonArgs ` -TestProjectNameFilter "*UnitTests*" ` -OutputPath dotnet/filtered-unit.slnx - .github/workflows/New-FrameworkFilteredSolution.ps1 @commonArgs ` + .github/workflows/New-FilteredSolution.ps1 @commonArgs ` -TestProjectNameFilter "*IntegrationTests*" ` -OutputPath dotnet/filtered-integration.slnx