Add solution filtered parallel test run (#4226)

This commit is contained in:
westey
2026-02-24 21:21:33 +00:00
committed by GitHub
Unverified
parent be5ed93b99
commit a39e324a43
2 changed files with 147 additions and 33 deletions
@@ -0,0 +1,109 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft. All rights reserved.
<#
.SYNOPSIS
Generates a filtered .slnx solution file that only includes test projects supporting a given target framework.
.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.
.PARAMETER Solution
Path to the source .slnx solution file.
.PARAMETER TargetFramework
The target framework to filter by (e.g., net10.0, net472).
.PARAMETER Configuration
Optional MSBuild configuration used when querying TargetFrameworks. Defaults to Debug.
.PARAMETER OutputPath
Optional output path for the filtered .slnx file. If not specified, a temp file is created.
.EXAMPLE
# Generate a filtered solution and run tests
$filtered = ./eng/New-FilteredSolution.ps1 -Solution ./agent-framework-dotnet.slnx -TargetFramework net472
dotnet test --solution $filtered --no-build -f net472
.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
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Solution,
[Parameter(Mandatory)]
[string]$TargetFramework,
[string]$Configuration = "Debug",
[string]$OutputPath
)
$ErrorActionPreference = "Stop"
# Resolve the solution path
$solutionPath = Resolve-Path $Solution
$solutionDir = Split-Path $solutionPath -Parent
if (-not $OutputPath) {
$OutputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "filtered-$(Split-Path $solutionPath -Leaf)")
}
# 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 = @()
foreach ($proj in $testProjects) {
$projRelPath = $proj.GetAttribute("Path")
$projFullPath = Join-Path $solutionDir $projRelPath
if (-not (Test-Path $projFullPath)) {
Write-Verbose "Project not found, keeping in solution: $projRelPath"
$kept += $projRelPath
continue
}
# Query the project's target frameworks using MSBuild
$targetFrameworks = & dotnet msbuild $projFullPath -getProperty:TargetFrameworks -p:Configuration=$Configuration -nologo 2>$null
$targetFrameworks = $targetFrameworks.Trim()
if ($targetFrameworks -like "*$TargetFramework*") {
Write-Verbose "Keeping: $projRelPath (targets: $targetFrameworks)"
$kept += $projRelPath
}
else {
Write-Verbose "Removing: $projRelPath (targets: $targetFrameworks, missing: $TargetFramework)"
$removed += $projRelPath
$proj.ParentNode.RemoveChild($proj) | Out-Null
}
}
# Write the filtered solution
$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
foreach ($r in $removed) {
Write-Host " - $r" -ForegroundColor Yellow
}
}
Write-Host "Kept $($kept.Count) test project(s)." -ForegroundColor Green
# Output the path for piping
Write-Output $OutputPath
+38 -33
View File
@@ -140,27 +140,37 @@ jobs:
popd
rm -rf "$TEMP_DIR"
- name: Run Unit Tests
shell: bash
- name: Generate filtered solution
shell: pwsh
run: |
cd dotnet
export UT_PROJECTS=$(find ./ -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')
.github/workflows/New-FrameworkFilteredSolution.ps1 `
-Solution dotnet/agent-framework-dotnet.slnx `
-TargetFramework ${{ matrix.targetFramework }} `
-Configuration ${{ matrix.configuration }} `
-OutputPath dotnet/filtered.slnx `
-Verbose
# Check if the project supports the target framework
if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
if [[ "${{ matrix.targetFramework }}" == "${{ env.COVERAGE_FRAMEWORK }}" ]]; then
# --ignore-exit-code 8: ignore failures due to finding no matching tests to run in a single project.
dotnet test --project $project -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} --no-build -v Normal --report-xunit-trx --ignore-exit-code 8 --coverage --coverage-output-format cobertura --coverage-settings "$(pwd)/tests/coverage.runsettings" --results-directory "$(pwd)/../TestResults/Coverage/"
else
dotnet test --project $project -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} --no-build -v Normal --report-xunit-trx --ignore-exit-code 8
fi
else
echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
fi
done
- name: Run Unit Tests
shell: pwsh
run: |
$coverageArgs = @()
if ("${{ matrix.targetFramework }}" -eq "${{ env.COVERAGE_FRAMEWORK }}") {
$coverageArgs = @(
"--coverage",
"--coverage-output-format", "cobertura",
"--coverage-settings", "dotnet/tests/coverage.runsettings",
"--results-directory", "TestResults/Coverage/"
)
}
dotnet test --solution dotnet/filtered.slnx `
-f ${{ matrix.targetFramework }} `
-c ${{ matrix.configuration }} `
--no-build -v Normal `
--report-xunit-trx `
--ignore-exit-code 8 `
--filter-query "/*UnitTests*/*/*/*" `
@coverageArgs
env:
# Cosmos DB Emulator connection settings
COSMOSDB_ENDPOINT: https://localhost:8081
@@ -187,22 +197,17 @@ jobs:
id: azure-functions-setup
- name: Run Integration Tests
shell: bash
shell: pwsh
if: github.event_name != 'pull_request' && matrix.integration-tests
run: |
cd dotnet
export INTEGRATION_TEST_PROJECTS=$(find ./ -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 --project $project -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} --no-build -v Normal --report-xunit-trx --ignore-exit-code 8 --filter-not-trait "Category=IntegrationDisabled"
else
echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
fi
done
dotnet test --solution dotnet/filtered.slnx `
-f ${{ matrix.targetFramework }} `
-c ${{ matrix.configuration }} `
--no-build -v Normal `
--report-xunit-trx `
--ignore-exit-code 8 `
--filter-query "/*IntegrationTests*/*/*/*" `
--filter-not-trait "Category=IntegrationDisabled"
env:
# Cosmos DB Emulator connection settings
COSMOSDB_ENDPOINT: https://localhost:8081