|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Runs GTest executables and generates a markdown test report. |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + This script searches for GTest executables in a specified binary directory, |
| 7 | + runs them, and generates a comprehensive markdown report with test results. |
| 8 | +
|
| 9 | +.PARAMETER BinaryDirectory |
| 10 | + The directory containing the GTest executables. |
| 11 | +
|
| 12 | +.PARAMETER TestName |
| 13 | + The name pattern of the GTest executable(s). Supports wildcards (e.g., "*test*.exe"). |
| 14 | +
|
| 15 | +.PARAMETER OutputReport |
| 16 | + Optional. The path to the output markdown report file. |
| 17 | + Defaults to "test-report.md" in the current directory. |
| 18 | +
|
| 19 | +.PARAMETER FullTestOutput |
| 20 | + Optional. If specified, includes the full test output for failed tests instead of just error lines. |
| 21 | + Defaults to false (only error lines are included). |
| 22 | +
|
| 23 | +.PARAMETER ExcludeTests |
| 24 | + Optional. Pattern to exclude specific test executables. Supports wildcards (e.g., "*large_cases*"). |
| 25 | + Test executables matching this pattern will be filtered out and not executed. |
| 26 | +
|
| 27 | +.EXAMPLE |
| 28 | + .\run-tests.ps1 -BinaryDirectory "C:\build\bin" -TestName "test_*.exe" |
| 29 | +
|
| 30 | +.EXAMPLE |
| 31 | + .\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -OutputReport "test-results.md" |
| 32 | +
|
| 33 | +.EXAMPLE |
| 34 | + .\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -FullTestOutput |
| 35 | +
|
| 36 | +.EXAMPLE |
| 37 | + .\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -ExcludeTests "*large_cases*" |
| 38 | +#> |
| 39 | + |
| 40 | +param( |
| 41 | + [Parameter(Mandatory=$true)] |
| 42 | + [string]$BinaryDirectory, |
| 43 | + |
| 44 | + [Parameter(Mandatory=$true)] |
| 45 | + [string]$TestName, |
| 46 | + |
| 47 | + [Parameter(Mandatory=$false)] |
| 48 | + [string]$OutputReport = "test-report.md", |
| 49 | + |
| 50 | + [Parameter(Mandatory=$false)] |
| 51 | + [switch]$FullTestOutput, |
| 52 | + |
| 53 | + [Parameter(Mandatory=$false)] |
| 54 | + [string]$ExcludeTests = "" |
| 55 | +) |
| 56 | + |
| 57 | +# Validate binary directory exists |
| 58 | +if (-not (Test-Path -Path $BinaryDirectory -PathType Container)) { |
| 59 | + Write-Error "Binary directory does not exist: $BinaryDirectory" |
| 60 | + exit 1 |
| 61 | +} |
| 62 | + |
| 63 | +# Find all matching executables |
| 64 | +$executables = Get-ChildItem -Path $BinaryDirectory -Filter $TestName -File -Recurse -ErrorAction SilentlyContinue |
| 65 | + |
| 66 | +# Filter out excluded executables if ExcludeTests is specified |
| 67 | +if ($ExcludeTests) { |
| 68 | + $originalCount = $executables.Count |
| 69 | + $executables = $executables | Where-Object { $_.Name -notlike $ExcludeTests } |
| 70 | + $excludedCount = $originalCount - $executables.Count |
| 71 | + if ($excludedCount -gt 0) { |
| 72 | + Write-Host "Excluded $excludedCount executable(s) matching pattern '$ExcludeTests'" |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +if ($executables.Count -eq 0) { |
| 77 | + Write-Error "No executables found matching pattern '$TestName' (after exclusions) in directory '$BinaryDirectory'" |
| 78 | + exit 1 |
| 79 | +} |
| 80 | + |
| 81 | +Write-Host "Found $($executables.Count) executable(s) to run" |
| 82 | + |
| 83 | +# Initialize counters |
| 84 | +$totalTests = 0 |
| 85 | +$totalPassed = 0 |
| 86 | +$totalFailed = 0 |
| 87 | +$failedTestDetails = @() |
| 88 | +$executionResults = @() |
| 89 | + |
| 90 | +# Process each executable |
| 91 | +foreach ($exe in $executables) { |
| 92 | + Write-Host "Running: $($exe.FullName)" |
| 93 | + |
| 94 | + $exeResult = @{ |
| 95 | + Name = $exe.Name |
| 96 | + Path = $exe.FullName |
| 97 | + Tests = 0 |
| 98 | + Passed = 0 |
| 99 | + Failed = 0 |
| 100 | + Output = "" |
| 101 | + FailedTests = @() |
| 102 | + } |
| 103 | + |
| 104 | + try { |
| 105 | + # Run the GTest executable |
| 106 | + $output = & $exe.FullName --gtest_color=no 2>&1 | Out-String |
| 107 | + $exeResult.Output = $output |
| 108 | + |
| 109 | + # Extract total tests run |
| 110 | + if ($output -match '\[==========\] Running (\d+) test') { |
| 111 | + $exeResult.Tests = [int]$matches[1] |
| 112 | + $totalTests += $exeResult.Tests |
| 113 | + } |
| 114 | + |
| 115 | + # Extract passed tests |
| 116 | + if ($output -match '\[ PASSED \] (\d+) test') { |
| 117 | + $exeResult.Passed = [int]$matches[1] |
| 118 | + $totalPassed += $exeResult.Passed |
| 119 | + } |
| 120 | + |
| 121 | + # Extract failed tests count |
| 122 | + if ($output -match '\[ FAILED \] (\d+) test') { |
| 123 | + $exeResult.Failed = [int]$matches[1] |
| 124 | + $totalFailed += $exeResult.Failed |
| 125 | + } |
| 126 | + |
| 127 | + $failedTestPattern = '\[ FAILED \] ([^\r\n\(]+)' |
| 128 | + $failedMatches = [regex]::Matches($output, $failedTestPattern) |
| 129 | + |
| 130 | + foreach ($match in $failedMatches) { |
| 131 | + if ($match.Groups[1].Value -notmatch '^\d+ test') { |
| 132 | + $failedTestName = $match.Groups[1].Value.Trim() |
| 133 | + $exeResult.FailedTests += $failedTestName |
| 134 | + |
| 135 | + $parts = $failedTestName -split ", where " |
| 136 | + $escapedName = $parts[0] |
| 137 | + $runPattern = "\[\s+RUN\s+\]\s+$escapedName\s*[\r\n]+([\s\S]*?)\[\s+FAILED\s+\]\s+$escapedName.*" |
| 138 | + |
| 139 | + $detailsText = "" |
| 140 | + if ($output -match $runPattern) { |
| 141 | + $testSection = $matches[1] |
| 142 | + |
| 143 | + if ($FullTestOutput) { |
| 144 | + $detailsText = $testSection.Trim() |
| 145 | + } else { |
| 146 | + # Extract only lines containing "error" (case-insensitive) |
| 147 | + $errorLines = @() |
| 148 | + $lines = $testSection -split "`r?`n" |
| 149 | + foreach ($line in $lines) { |
| 150 | + if ($line -match 'error') { |
| 151 | + $errorLines += $line.Trim() |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + if ($errorLines.Count -gt 0) { |
| 156 | + $detailsText = $errorLines -join "`n" |
| 157 | + } else { |
| 158 | + # If no error lines found, show the full section (might contain other useful info) |
| 159 | + $detailsText = $testSection.Trim() |
| 160 | + if ($detailsText.Length -lt 10) { |
| 161 | + $detailsText = "Test failed without detailed error output." |
| 162 | + } |
| 163 | + } |
| 164 | + } |
| 165 | + } else { |
| 166 | + # If pattern doesn't match, provide a helpful message |
| 167 | + $detailsText = "Test failed without detailed error output." |
| 168 | + } |
| 169 | + |
| 170 | + $failedTestDetails += @{ |
| 171 | + Executable = $exe.Name |
| 172 | + TestName = $failedTestName |
| 173 | + Details = $detailsText |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + } catch { |
| 179 | + Write-Warning "Error running $($exe.Name): $_" |
| 180 | + $exeResult.Output = "Error: $_" |
| 181 | + } |
| 182 | + |
| 183 | + $executionResults += $exeResult |
| 184 | +} |
| 185 | + |
| 186 | +# Generate Markdown Report |
| 187 | +$reportContent = @" |
| 188 | +# GTest Execution Report |
| 189 | +
|
| 190 | +**Generated:** $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") |
| 191 | +
|
| 192 | +## Summary |
| 193 | +
|
| 194 | +| Metric | Count | |
| 195 | +|--------|-------| |
| 196 | +| **Total Tests Executed** | $totalTests | |
| 197 | +| **Tests Passed** | $totalPassed | |
| 198 | +| **Tests Failed** | $totalFailed | |
| 199 | +| **Success Rate** | $(if ($totalTests -gt 0) { [math]::Round(($totalPassed / $totalTests) * 100, 2) } else { 0 })% | |
| 200 | +
|
| 201 | +## Executable Results |
| 202 | +
|
| 203 | +"@ |
| 204 | + |
| 205 | +foreach ($result in $executionResults) { |
| 206 | + # Use emoji/symbols via string concatenation to avoid encoding issues |
| 207 | + $passSymbol = [char]0x2705 # ✅ white heavy check mark |
| 208 | + $failSymbol = [char]0x274C # ❌ cross mark |
| 209 | + $status = if ($result.Failed -eq 0) { "$passSymbol PASSED" } else { "$failSymbol FAILED" } |
| 210 | + |
| 211 | + $reportContent += "`n`n### $($result.Name) $status`n`n" |
| 212 | + $reportContent += "- **Tests Run:** $($result.Tests)`n" |
| 213 | + $reportContent += "- **Passed:** $($result.Passed)`n" |
| 214 | + $reportContent += "- **Failed:** $($result.Failed)`n" |
| 215 | + $reportContent += "- **Path:** ``$($result.Path)```n" |
| 216 | +} |
| 217 | + |
| 218 | +# Add failed test details section if there are failures |
| 219 | +if ($failedTestDetails.Count -gt 0) { |
| 220 | + $failSymbol = [char]0x274C # ❌ cross mark |
| 221 | + $reportContent += "`n`n---`n`n## Failed Test Details`n" |
| 222 | + |
| 223 | + foreach ($failure in $failedTestDetails) { |
| 224 | + $reportContent += "`n`n$failSymbol $($failure.TestName)`n`n" |
| 225 | + $reportContent += "$($failure.Details)`n" |
| 226 | + } |
| 227 | +} else { |
| 228 | + $celebrationSymbol = [char]0x1F389 # 🎉 party popper |
| 229 | + $reportContent += "`n`n---`n`n$celebrationSymbol All Tests Passed!`n`n" |
| 230 | + $reportContent += "No test failures detected.`n" |
| 231 | +} |
| 232 | + |
| 233 | +# Add footer |
| 234 | +$reportContent += "`n" |
| 235 | + |
| 236 | +# Write report to file |
| 237 | +$reportContent | Out-File -FilePath $OutputReport -Encoding UTF8 |
| 238 | + |
| 239 | +Write-Host "" |
| 240 | +Write-Host "========================================" -ForegroundColor Cyan |
| 241 | +Write-Host "Test Execution Complete" -ForegroundColor Cyan |
| 242 | +Write-Host "========================================" -ForegroundColor Cyan |
| 243 | +Write-Host "Total Tests: $totalTests" -ForegroundColor White |
| 244 | +Write-Host "Passed: $totalPassed" -ForegroundColor Green |
| 245 | +Write-Host "Failed: $totalFailed" -ForegroundColor $(if ($totalFailed -gt 0) { "Red" } else { "Green" }) |
| 246 | +Write-Host "Report saved: $((Get-Item $OutputReport).FullName)" -ForegroundColor Yellow |
| 247 | +Write-Host "========================================" -ForegroundColor Cyan |
| 248 | + |
| 249 | +# Exit with appropriate code |
| 250 | +if ($totalFailed -gt 0) { |
| 251 | + exit 1 |
| 252 | +} else { |
| 253 | + exit 0 |
| 254 | +} |
0 commit comments