diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 index 523de3beba..5804bec9a9 100644 --- a/.github/actions/test/process-pester-results/process-pester-results.ps1 +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -24,6 +24,7 @@ $testIgnoredCount = 0 $testSkippedCount = 0 $testInvalidCount = 0 +# Process test results and generate annotations for failures Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $results = [xml] (get-content $_.FullName) @@ -35,6 +36,61 @@ Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $testIgnoredCount += [int]$results.'test-results'.ignored $testSkippedCount += [int]$results.'test-results'.skipped $testInvalidCount += [int]$results.'test-results'.invalid + + # Generate GitHub Actions annotations for test failures + # Select failed test cases + if ("System.Xml.XmlDocumentXPathExtensions" -as [Type]) { + $failures = [System.Xml.XmlDocumentXPathExtensions]::SelectNodes($results.'test-results', './/test-case[@result = "Failure"]') + } + else { + $failures = $results.SelectNodes('.//test-case[@result = "Failure"]') + } + + foreach ($testfail in $failures) { + $description = $testfail.description + $testName = $testfail.name + $message = $testfail.failure.message + $stack_trace = $testfail.failure.'stack-trace' + + # Parse stack trace to get file and line info + $fileInfo = Get-PesterFailureFileInfo -StackTraceString $stack_trace + + if ($fileInfo.File) { + # Convert absolute path to relative path for GitHub Actions + $filePath = $fileInfo.File + + # GitHub Actions expects paths relative to the workspace root + if ($env:GITHUB_WORKSPACE) { + $workspacePath = $env:GITHUB_WORKSPACE + if ($filePath.StartsWith($workspacePath)) { + $filePath = $filePath.Substring($workspacePath.Length).TrimStart('/', '\') + # Normalize to forward slashes for consistency + $filePath = $filePath -replace '\\', '/' + } + } + + # Create annotation title + $annotationTitle = "Test Failure: $description / $testName" + + # Build the annotation message + $annotationMessage = $message -replace "`n", "%0A" -replace "`r" + + # Build and output the workflow command + $workflowCommand = "::error file=$filePath" + if ($fileInfo.Line) { + $workflowCommand += ",line=$($fileInfo.Line)" + } + $workflowCommand += ",title=$annotationTitle::$annotationMessage" + + Write-Host $workflowCommand + + # Output a link to the test run + if ($env:GITHUB_SERVER_URL -and $env:GITHUB_REPOSITORY -and $env:GITHUB_RUN_ID) { + $logUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)" + Write-Host "Test logs: $logUrl" + } + } + } } @" diff --git a/build.psm1 b/build.psm1 index 78d966c73a..ddd5014d0d 100644 --- a/build.psm1 +++ b/build.psm1 @@ -1846,6 +1846,69 @@ $stack_trace } +function Get-PesterFailureFileInfo +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$StackTraceString + ) + + # Parse stack trace to extract file path and line number + # Common patterns: + # "at line: 123 in C:\path\to\file.ps1" (Pester 4) + # "at C:\path\to\file.ps1:123" + # "at , C:\path\to\file.ps1: line 123" + # "at 1 | Should -Be 2, /path/to/file.ps1:123" (Pester 5) + # "at 1 | Should -Be 2, C:\path\to\file.ps1:123" (Pester 5 Windows) + + $result = @{ + File = $null + Line = $null + } + + if ([string]::IsNullOrWhiteSpace($StackTraceString)) { + return $result + } + + # Try pattern: "at line: 123 in " (Pester 4) + if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result + } + + # Try pattern: ", :123" (Pester 5 format) + # This handles both Unix paths (/path/file.ps1:123) and Windows paths (C:\path\file.ps1:123) + if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: "at :123" (without comma) + # Handle both absolute Unix and Windows paths + if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: ": line 123" + if ($StackTraceString -match '((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):\s*line\s+(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try to extract just the file path if no line number found + if ($StackTraceString -match '(?:at\s+|in\s+)?((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1)') { + $result.File = $matches[1].Trim() + } + + return $result +} + function Test-XUnitTestResults { param(