From 652e601775c091c01a0b904cb309590f3cf45119 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 10 Aug 2026 10:53:02 -0600 Subject: [PATCH] Add -NoCoverage switch and improve crash diagnostics Adds a -NoCoverage switch to dotnet-test-cloud.ps1 for both the MTP and VSTest code paths. Code coverage instrumentation can destabilize a test run, and when it does there is currently no way for a repo to keep running tests on the affected leg without hand-editing this script. Also collect a crash report alongside the crash dump, and restrict the dump to the heap. On Linux the native crash report is often the only way to identify the faulting thread and instruction when a test host dies of an access violation, and a heap dump is considerably smaller than the default full dump while still containing everything needed to analyze managed state. Finally, stop discarding crash dumps that have no TRX attachment copy. The previous filter kept a .dmp only when it appeared under a TRX 'In' directory, so on GitHub Actions -- which does not request a TRX report -- every crash dump was dropped from the uploaded artifacts, making test host crashes impossible to diagnose. The dumps are now de-duplicated by file name instead, which still avoids uploading the same dump twice on Azure Pipelines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/artifacts/testResults.ps1 | 16 ++++++++++------ tools/dotnet-test-cloud.ps1 | 31 +++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/tools/artifacts/testResults.ps1 b/tools/artifacts/testResults.ps1 index 1817ca09..d96c8847 100644 --- a/tools/artifacts/testResults.ps1 +++ b/tools/artifacts/testResults.ps1 @@ -4,21 +4,25 @@ Param( $result = @{} +# Hang and crash dumps are copied into the TRX attachment directory when a TRX report is requested. +# Prefer that copy to avoid uploading the same dump twice, but keep the original when no attachment +# copy exists (e.g. GitHub Actions runs, which don't request a TRX report) so crashes stay diagnosable. +function Select-TestResultFile($files) { + $attachedDumpNames = @($files | Where-Object { $_.Extension -eq '.dmp' -and $_.FullName -match '[/\\]In[/\\]' } | ForEach-Object { $_.Name }) + $files | Where-Object { $_.Extension -ne '.dmp' -or $_.FullName -match '[/\\]In[/\\]' -or $attachedDumpNames -notcontains $_.Name } +} + $RepoRoot = Resolve-Path "$PSScriptRoot\..\.." $testRoot = Join-Path $RepoRoot test $legacyTestResults = Join-Path $testRoot TestResults if (Test-Path $legacyTestResults) { - $result[$testRoot] = Get-ChildItem $legacyTestResults -Recurse -Directory | - Get-ChildItem -Recurse -File | - Where-Object { $_.Extension -ne '.dmp' -or $_.FullName -match '[/\\]In[/\\]' } + $result[$testRoot] = Select-TestResultFile @(Get-ChildItem $legacyTestResults -Recurse -Directory | Get-ChildItem -Recurse -File) } $artifactStaging = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" $testlogsPath = Join-Path $artifactStaging "test_logs" if (Test-Path $testlogsPath) { - # Hang and crash dumps are copied into the TRX attachment directory. - $result[$testlogsPath] = Get-ChildItem $testlogsPath -Recurse | - Where-Object { $_.Extension -ne '.dmp' -or $_.FullName -match '[/\\]In[/\\]' } + $result[$testlogsPath] = Select-TestResultFile @(Get-ChildItem $testlogsPath -Recurse) } $result diff --git a/tools/dotnet-test-cloud.ps1 b/tools/dotnet-test-cloud.ps1 index 5d204819..da876cb9 100644 --- a/tools/dotnet-test-cloud.ps1 +++ b/tools/dotnet-test-cloud.ps1 @@ -13,6 +13,8 @@ A switch to run the tests in an x86 process. .PARAMETER dotnet32 The path to a 32-bit dotnet executable to use. +.PARAMETER NoCoverage + A switch to skip code coverage collection. #> [CmdletBinding()] Param( @@ -20,7 +22,8 @@ Param( [string]$Agent='Local', [switch]$PublishResults, [switch]$x86, - [string]$dotnet32 + [string]$dotnet32, + [switch]$NoCoverage ) $RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path @@ -60,10 +63,12 @@ if ($isMTP) { ,'--hangdump' ,'--hangdump-timeout','120s' ,'--crashdump' + ,'--crashdump-type','Heap' + # The native crash report accompanies the dump and is often the only way to identify the + # faulting thread and instruction when a test host dies of an access violation on Linux. + ,'--crash-report-if-supported' ) $mtpArgs = @( - ,'--coverage' - ,'--coverage-output-format','cobertura' ,'--diagnostic' ,'--diagnostic-output-directory',$testLogs ,'--diagnostic-verbosity','Information' @@ -71,12 +76,19 @@ if ($isMTP) { ,'--report-trx' ) + if (-not $NoCoverage) { + $mtpArgs += @( + ,'--coverage' + ,'--coverage-output-format','cobertura' + ,'--coverage-settings',"$PSScriptRoot/test.runsettings" + ) + } + & $dotnet test --solution $RepoRoot ` --no-build ` -c $Configuration ` -bl:"$testBinLog" ` --filter-not-trait 'TestCategory=FailsInCloudTest' ` - --coverage-settings "$PSScriptRoot/test.runsettings" ` @mtpArgs ` @dumpSwitches ` @extraArgs @@ -85,17 +97,24 @@ if ($isMTP) { $trxFiles = Get-ChildItem -Recurse -Path $testLogs\*.trx } else { $testDiagLog = Join-Path $ArtifactStagingFolder (Join-Path test_logs diag.log) + $coverageArgs = @() + if (-not $NoCoverage) { + $coverageArgs = @( + ,'--collect','Code Coverage;Format=cobertura' + ,'--settings',"$PSScriptRoot/test.runsettings" + ) + } + & $dotnet test $RepoRoot ` --no-build ` -c $Configuration ` --filter "TestCategory!=FailsInCloudTest" ` - --collect "Code Coverage;Format=cobertura" ` - --settings "$PSScriptRoot/test.runsettings" ` --blame-hang-timeout 60s ` --blame-crash ` -bl:"$testBinLog" ` --diag "$testDiagLog;TraceLevel=info" ` --logger trx ` + @coverageArgs ` @extraArgs if ($LASTEXITCODE -ne 0) { $failedTests += 1 }