1+ # ###################################################################################
2+ # To execute
3+ # 1. In powershell, set security policy for this script:
4+ # Set-ExecutionPolicy Unrestricted -Scope Process -Force
5+ # 2. Change directory to the script folder:
6+ # CD src (wherever your script is)
7+ # 3. In powershell, run script:
8+ # .\Get-CodeCoverage.ps1 -TestProjectFilter '*Tests*.csproj'
9+ # This script uses native .NET 10 code coverage (Microsoft.Testing.Platform)
10+ # Note: Due to MSTest 4.1.0 incompatibility with 'dotnet test' on .NET 10, this runs tests as executables
11+ # ###################################################################################
12+
13+ Param (
14+ [string ]$TestProjectFilter = ' *Tests*.csproj' ,
15+ [string ]$Configuration = ' Release' ,
16+ [string ]$TestRootPath = ' '
17+ )
18+ # ###################################################################################
19+ if ($IsWindows ) {Set-ExecutionPolicy Unrestricted - Scope Process - Force}
20+ $VerbosePreference = ' SilentlyContinue' # 'Continue'
21+ # ###################################################################################
22+
23+ function Resolve-TestRootPath {
24+ param (
25+ [Parameter (Mandatory = $true )]
26+ [System.IO.DirectoryInfo ]$ScriptDir ,
27+ [Parameter (Mandatory = $false )]
28+ [string ]$OverridePath
29+ )
30+
31+ if (-not [string ]::IsNullOrWhiteSpace($OverridePath )) {
32+ return Get-Item - Path (Resolve-Path - Path $OverridePath )
33+ }
34+
35+ $current = $ScriptDir
36+ while ($null -ne $current ) {
37+ $srcCandidate = Join-Path $current.FullName ' src'
38+ if (Test-Path - Path $srcCandidate ) {
39+ return Get-Item - Path $srcCandidate
40+ }
41+
42+ $current = $current.Parent
43+ }
44+
45+ return $ScriptDir
46+ }
47+
48+ # Install required tools
49+ & dotnet tool install - g dotnet- reportgenerator- globaltool
50+ & dotnet tool install - g dotnet- coverage
51+
52+ $timestamp = Get-Date - Format " yyyyMMdd-HHmmss"
53+ $scriptPath = Get-Item - Path $PSScriptRoot
54+ $testRootPath = Resolve-TestRootPath - ScriptDir $scriptPath - OverridePath $TestRootPath
55+ $reportOutputPath = Join-Path $testRootPath " TestResults\Reports\$timestamp "
56+
57+ New-Item - ItemType Directory - Force - Path $reportOutputPath
58+
59+ # Find test projects
60+ $testProjects = Get-ChildItem $testRootPath - Filter $TestProjectFilter - Recurse
61+ Write-Host " Found $ ( $testProjects.Count ) test projects."
62+
63+ foreach ($project in $testProjects ) {
64+ $testProjectPath = $project.FullName
65+ Write-Host " Running tests with coverage for project: $ ( $project.BaseName ) "
66+
67+ Write-Host " Building test project: $ ( $project.BaseName ) "
68+ & dotnet build $testProjectPath -- configuration $Configuration
69+
70+ # Use 'dotnet run' instead of 'dotnet test' for MSTest runner projects
71+ # This bypasses the VSTest target that's incompatible with .NET 10 SDK
72+ Push-Location $project.DirectoryName
73+ & dotnet run -- configuration $Configuration -- no- build -- -- coverage
74+ Pop-Location
75+ }
76+
77+ # Collect all coverage files (Microsoft.Testing.Platform outputs .coverage files)
78+ $coverageFiles = Get-ChildItem - Path $testRootPath - Filter " *.coverage" - Recurse | Select-Object - ExpandProperty FullName
79+
80+ if ($coverageFiles.Count -eq 0 ) {
81+ Write-Warning " No coverage files found. Make sure your test projects have code coverage enabled."
82+ exit 0
83+ }
84+
85+ Write-Host " Found $ ( $coverageFiles.Count ) coverage file(s)"
86+
87+ # Convert binary .coverage files to XML format
88+ $coverageXmlFiles = @ ()
89+ foreach ($coverageFile in $coverageFiles ) {
90+ $xmlFile = $coverageFile -replace ' \.coverage$' , ' .cobertura.xml'
91+ Write-Host " Converting $coverageFile to XML format..."
92+ & dotnet- coverage merge $coverageFile -- output $xmlFile -- output- format cobertura
93+ if (Test-Path $xmlFile ) {
94+ $coverageXmlFiles += $xmlFile
95+ }
96+ }
97+
98+ if ($coverageXmlFiles.Count -eq 0 ) {
99+ Write-Warning " No XML coverage files were generated."
100+ exit 0
101+ }
102+
103+ Write-Host " Generated $ ( $coverageXmlFiles.Count ) XML coverage file(s)"
104+
105+ # Generate HTML report
106+ $coverageFilesArg = ($coverageXmlFiles -join " ;" )
107+ & reportgenerator - reports:$coverageFilesArg - targetdir:$reportOutputPath - reporttypes:Html
108+
109+ Write-Host " Code coverage report generated at: $reportOutputPath "
110+
111+ $reportIndexHtml = Join-Path $reportOutputPath " index.html"
112+ if (Test-Path $reportIndexHtml ) {
113+ Invoke-Item - Path $reportIndexHtml
114+ }
115+ else {
116+ Write-Warning " Report index.html not found at: $reportIndexHtml "
117+ }
0 commit comments