I am posting this after 2 days of trying various solutions that didn't work for me. This is mostly for my future reference, but I hope others can benefit as well. I used this source as the basis for my work, however this post was able to get Coverlet.MsBuild to work (which would have made things much easier) but for me, Coverlet.MSBuild would not generate anything no matter what I tried to do. So I had to go with Coverlet.Collect which made things a lot more complicated.
Here is how I set up my powershell to generate reports for TeamCity:
# This file is used by TeamCity build steps
$checkoutDir = $args[0]
Write-Output "`$checkoutDir : $checkoutDir"
$donetCliExePath = $args[1]
Write-Output "`$donetCliExePath : $donetCliExePath"
$coverageOutputPath = "$checkoutDir\coverage"
Write-Output "`coverageOutputPath : $coverageOutputPath"
# The TeamCity Build Agent runs using the LocalSystem account
# and all user profile paths are defined in an Environment
# variable $env:USERPROFILE. All global dotnet tools are installed
# in the running users (LocalSystem) profile in the Path below:
$dotNetToolPath = "$env:USERPROFILE\.dotnet\tools"
Write-Output "`$dotNetToolPath : $dotNetToolPath"
Set-Alias -Name DotNet -Value "$donetCliExePath"
try
{
Write-output "Ensuring reportgenerator is available."
& DotNet tool update --global dotnet-reportgenerator-globaltool
# Get a list of all unit testing projects
$testCsProjList = Get-Childitem -Recurse $checkoutDir -Filter *.Tests.Unit.csproj
# Set up a job where each unit test dll is run in parallel
# up to 10 test fixtures at a time.
# --collect:"XPlat Code Coverage" 2>&1 runs coverlet code coverage
$job = $testCsProjList | foreach-Object -parallel {
DotNet test `
"$($_.FullName)" `
--no-build `
-c Release `
-l "console;verbosity=normal" `
--test-adapter-path "$env:USERPROFILE\.nuget\packages\xunit.runner.visualstudio\2.4.5\build\netcoreapp3.1" `
--collect:"XPlat Code Coverage" 2>&1
} -AsJob -ThrottleLimit 10
Write-Output ""
Write-output "Running unit tests and collecting code coverage..."
Write-Output ""
# Wait for all tasks to finish, collect all the output
$output = $job | Wait-Job | Receive-Job
Write-Output ""
Write-Output "-----------------------------COMPLETE----------------------------------"
Write-Output ""
# Coverlet.Collector puts the coverage results in a dynamic folder.
# Coverlet.MsBuild would not generate results at all.
# There is no way at this time to specify the output folder
# that coverlet.collector should use.
# However the console output of coverlet.collector reveals where the
# Test results are stored. The code below finds all the output paths
# of the tests detected in the Solution.
$found = $output -match '\w+.*coverage.cobertura.xml'
if ($null -eq $found) {
throw "No Coverage Results, please view logs for details. Exiting."
}
$output | Out-File -FilePath "$checkoutDir\testOutput.txt" -Force # For debugging
if(!(Test-Path $coverageOutputPath))
{ # Move-Item will not create the destination path if it does not
# exist and for some silly reason this is not an optional parameter.
# Here we ensure it exists so later we can move coverage reports here.
New-Item -Path $coverageOutputPath -ItemType Directory -Force | Out-Null
}
Write-output "Moving all coverage results:"
$found | % {
if([string]::IsNullOrWhitespace($_))
{
# Regex matches stuff and puts
# in blank results sometimes, no
# time to figure out why.
continue
}
$testResultPath = $_.Trim()
$foundTestNames = $testResultPath -match "unit\\(.*)\\TestResults"
$testProjectName = $matches[1] #matches is an automagic PS variable after a Regex match
Write-Output "Moving $testProjectName coverage results to: $coverageOutputPath\$testProjectName.cobertura.xml"
Move-Item -Path "$testResultPath" -Destination "$coverageOutputPath\$testProjectName.cobertura.xml"
}
Write-output "Generating TeamCity Report:"
& "$dotNetToolPath\reportgenerator.exe" "-targetdir:$coverageOutputPath" "-reports:$coverageOutputPath\*.xml" "-reporttypes:Html"
if($LastExitCode -eq 1) {
throw "Running Unit Tests and Gathering coverage of Nexus.Api.SDK failed, please view logs for details. Exiting."
}
}
catch
{
$Exception = $_;
# Display info about exception
$Exception | Format-List * -Force | Out-String ;
# Display info about invocation context that cause error
$Exception.InvocationInfo | Format-List * -Force | Out-String ;
# Recursively Display all Inner Exceptions depth-first
for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
{
Write-Output ("$i" * 80) ;
$Exception | Format-List * -Force | Out-String
}
throw
}
Let me know if there is an easier way to achieve the above!