Skip to content

Refactor and enhance Pester tests batch #9761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Aug 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eb48882
Refactor and standardize Pester test scripts
potatoqualitee Aug 9, 2025
1f17830
Enhance formatter to preserve alignment and avoid unnecessary writes
potatoqualitee Aug 9, 2025
49914b7
Merge branch 'formatfix' into b2
potatoqualitee Aug 9, 2025
b07ac96
Improve file handling in Invoke-DbatoolsFormatter
potatoqualitee Aug 9, 2025
4b4c3b5
Improve formatting comparison in Invoke-DbatoolsFormatter
potatoqualitee Aug 9, 2025
eae8cfd
Merge branch 'formatfix' into b2
potatoqualitee Aug 9, 2025
05eb897
Refactor Invoke-DbatoolsFormatter for improved formatting
potatoqualitee Aug 9, 2025
21da25c
Add progress reporting to Invoke-DbatoolsFormatter
potatoqualitee Aug 9, 2025
f6e53e9
Merge branch 'formatfix' into b2
potatoqualitee Aug 9, 2025
657d72c
Remove trailing space in parameter list
potatoqualitee Aug 9, 2025
79d6a6a
Normalize whitespace and formatting in test scripts
potatoqualitee Aug 9, 2025
bb2afe5
Merge branch 'development' into b2
potatoqualitee Aug 10, 2025
2384b6c
Remove SkipInvisibleOnly parameter from Invoke-DbatoolsFormatter
potatoqualitee Aug 10, 2025
f27a778
a
potatoqualitee Aug 10, 2025
9fabd29
a
potatoqualitee Aug 10, 2025
2a69932
Update Repair-PullRequestTest.ps1
potatoqualitee Aug 10, 2025
68d5561
Refactor and standardize Pester test files
potatoqualitee Aug 10, 2025
0def55d
Exclude Get-DbaDbMasterKey from test group
potatoqualitee Aug 10, 2025
5d3d5ff
Refactor tests to remove global variables
potatoqualitee Aug 10, 2025
accc193
Fix hashtable initialization spacing in formatter
potatoqualitee Aug 10, 2025
2bc1da2
Fix Export-DbaScript test assertions for output matching
potatoqualitee Aug 10, 2025
8e34302
Add CopyOnly flag to Repair-PullRequestTest
potatoqualitee Aug 10, 2025
8638398
cant fix these
potatoqualitee Aug 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions .aitools/module/Repair-PullRequestTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ function Repair-PullRequestTest {
Specific AppVeyor build number to target instead of automatically detecting from PR checks.
When specified, uses this build number directly rather than finding the latest build for the PR.

.PARAMETER CopyOnly
If specified, stops the repair process right after copying working test files
from the development branch to the current branch, without running Update-PesterTest
or committing any changes.

.NOTES
Tags: Testing, Pester, PullRequest, CI
Author: dbatools team
Expand All @@ -47,7 +52,8 @@ function Repair-PullRequestTest {
[int]$PRNumber,
[switch]$AutoCommit,
[int]$MaxPRs = 5,
[int]$BuildNumber
[int]$BuildNumber,
[switch]$CopyOnly
)

begin {
Expand Down Expand Up @@ -303,9 +309,27 @@ function Repair-PullRequestTest {
$workingTempPath = Join-Path $tempDir "working-$fileName"

if ($workingTestPath -and (Test-Path $workingTestPath)) {
Copy-Item $workingTestPath $workingTempPath -Force
$copiedFiles += $fileName
Write-Verbose "Copied working test: $fileName"
$maxAttempts = 2
$attempt = 0
$copied = $false
while (-not $copied -and $attempt -lt $maxAttempts) {
try {
$attempt++
Copy-Item -Path $workingTestPath -Destination $workingTempPath -Force -ErrorAction Stop
$copiedFiles += $fileName
Write-Verbose "Copied working test: $fileName (attempt $attempt)"
$copied = $true
} catch {
Write-Warning ("Attempt {0}: Failed to copy working test file for {1} from development branch: {2}" -f $attempt, $fileName, $_.Exception.Message)
if ($attempt -lt $maxAttempts) {
Start-Sleep -Seconds 1
}
}
}
if (-not $copied) {
Write-Error "Unable to copy working test file for $fileName after $maxAttempts attempts. Aborting repair process for this file."
break
}
} else {
Write-Warning "Could not find working test file in Development branch: tests/$fileName"
}
Expand Down Expand Up @@ -373,6 +397,12 @@ function Repair-PullRequestTest {
}
}

# If CopyOnly is specified, return immediately after copying
if ($CopyOnly) {
Write-Verbose "CopyOnly flag set - stopping after copying working tests to current branch"
return
}

# Now run Update-PesterTest in parallel with Start-Job (simplified approach)
Write-Verbose "Starting parallel Update-PesterTest jobs for $($fileErrorMap.Keys.Count) files"

Expand Down
205 changes: 95 additions & 110 deletions public/Invoke-DbatoolsFormatter.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ function Invoke-DbatoolsFormatter {
.PARAMETER Path
The path to the ps1 file that needs to be formatted

.PARAMETER SkipInvisibleOnly
Skip files that would only have invisible changes (BOM, line endings, trailing whitespace, tabs).
Use this to avoid unnecessary version control noise when only non-visible characters would change.

.PARAMETER EnableException
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
Expand All @@ -35,15 +31,14 @@ function Invoke-DbatoolsFormatter {
Reformats C:\dbatools\public\Get-DbaDatabase.ps1 to dbatools' standards

.EXAMPLE
PS C:\> Invoke-DbatoolsFormatter -Path C:\dbatools\public\*.ps1 -SkipInvisibleOnly
PS C:\> Get-ChildItem *.ps1 | Invoke-DbatoolsFormatter

Reformats all ps1 files but skips those that would only have BOM/line ending changes
Reformats all .ps1 files in the current directory, showing progress for the batch operation
#>
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[object[]]$Path,
[switch]$SkipInvisibleOnly,
[switch]$EnableException
)
begin {
Expand Down Expand Up @@ -72,147 +67,137 @@ function Invoke-DbatoolsFormatter {
$OSEOL = "`r`n"
}

function Test-OnlyInvisibleChanges {
param(
[string]$OriginalContent,
[string]$ModifiedContent
)

# Normalize line endings to Unix style for comparison
$originalNormalized = $OriginalContent -replace '\r\n', "`n" -replace '\r', "`n"
$modifiedNormalized = $ModifiedContent -replace '\r\n', "`n" -replace '\r', "`n"
# Collect all paths for progress tracking
$allPaths = @()
}
process {
if (Test-FunctionInterrupt) { return }
# Collect all paths from pipeline
$allPaths += $Path
}
end {
if (Test-FunctionInterrupt) { return }

# Split into lines
$originalLines = $originalNormalized -split "`n"
$modifiedLines = $modifiedNormalized -split "`n"
$totalFiles = $allPaths.Count
$currentFile = 0
$processedFiles = 0
$updatedFiles = 0

# Normalize each line: trim trailing whitespace and convert tabs to spaces
$originalLines = $originalLines | ForEach-Object { $_.TrimEnd().Replace("`t", " ") }
$modifiedLines = $modifiedLines | ForEach-Object { $_.TrimEnd().Replace("`t", " ") }
foreach ($p in $allPaths) {
$currentFile++

# Remove trailing empty lines from both
while ($originalLines.Count -gt 0 -and $originalLines[-1] -eq '') {
$originalLines = $originalLines[0..($originalLines.Count - 2)]
}
while ($modifiedLines.Count -gt 0 -and $modifiedLines[-1] -eq '') {
$modifiedLines = $modifiedLines[0..($modifiedLines.Count - 2)]
try {
$realPath = (Resolve-Path -Path $p -ErrorAction Stop).Path
} catch {
Write-Progress -Activity "Formatting PowerShell files" -Status "Error resolving path: $p" -PercentComplete (($currentFile / $totalFiles) * 100) -CurrentOperation "File $currentFile of $totalFiles"
Stop-Function -Message "Cannot find or resolve $p" -Continue
continue
}

# Compare the normalized content
if ($originalLines.Count -ne $modifiedLines.Count) {
return $false
# Skip directories
if (Test-Path -Path $realPath -PathType Container) {
Write-Progress -Activity "Formatting PowerShell files" -Status "Skipping directory: $realPath" -PercentComplete (($currentFile / $totalFiles) * 100) -CurrentOperation "File $currentFile of $totalFiles"
Write-Message -Level Verbose "Skipping directory: $realPath"
continue
}

for ($i = 0; $i -lt $originalLines.Count; $i++) {
if ($originalLines[$i] -ne $modifiedLines[$i]) {
return $false
$fileName = Split-Path -Leaf $realPath
Write-Progress -Activity "Formatting PowerShell files" -Status "Processing: $fileName" -PercentComplete (($currentFile / $totalFiles) * 100) -CurrentOperation "File $currentFile of $totalFiles"

$originalContent = Get-Content -Path $realPath -Raw -Encoding UTF8
$content = $originalContent

if ($OSEOL -eq "`r`n") {
# See #5830, we are in Windows territory here
# Is the file containing at least one `r ?
$containsCR = ($content -split "`r").Length -gt 1
if (-not($containsCR)) {
# If not, maybe even on Windows the user is using Unix-style endings, which are supported
$OSEOL = "`n"
}
}

return $true
}
#strip ending empty lines
$content = $content -replace "(?s)$OSEOL\s*$"

function Format-ScriptContent {
param(
[string]$Content,
[string]$LineEnding
)
# Preserve aligned assignments before formatting
# Look for patterns with multiple spaces before OR after the = sign
$alignedPatterns = [regex]::Matches($content, '(?m)^\s*(\$\w+|\w+)\s{2,}=\s*.+$|^\s*(\$\w+|\w+)\s*=\s{2,}.+$')
$placeholders = @{ }

# Strip ending empty lines
$Content = $Content -replace "(?s)$LineEnding\s*$"
foreach ($match in $alignedPatterns) {
$placeholder = "___ALIGNMENT_PLACEHOLDER_$($placeholders.Count)___"
$placeholders[$placeholder] = $match.Value
$content = $content.Replace($match.Value, $placeholder)
}

try {
# Save original lines before formatting
$originalLines = $Content -split "`n"

# Run the formatter
$formattedContent = Invoke-Formatter -ScriptDefinition $Content -Settings CodeFormattingOTBS -ErrorAction Stop

# Automatically restore spaces before = signs
$formattedLines = $formattedContent -split "`n"
for ($i = 0; $i -lt $formattedLines.Count; $i++) {
if ($i -lt $originalLines.Count) {
# Check if original had multiple spaces before =
if ($originalLines[$i] -match '^(\s*)(.+?)(\s{2,})(=)(.*)$') {
$indent = $matches[1]
$beforeEquals = $matches[2]
$spacesBeforeEquals = $matches[3]
$rest = $matches[4] + $matches[5]

# Apply the same spacing to the formatted line
if ($formattedLines[$i] -match '^(\s*)(.+?)(\s*)(=)(.*)$') {
$formattedLines[$i] = $matches[1] + $matches[2] + $spacesBeforeEquals + '=' + $matches[5]
}
}
}
$formattedContent = Invoke-Formatter -ScriptDefinition $content -Settings CodeFormattingOTBS -ErrorAction Stop
if ($formattedContent) {
$content = $formattedContent
}
$Content = $formattedLines -join "`n"
} catch {
Write-Message -Level Warning "Unable to format content"
# Just silently continue - the formatting might still work partially
}

# Match the ending indentation of CBH with the starting one
$CBH = $CBHRex.Match($Content).Value
# Restore the aligned patterns
foreach ($key in $placeholders.Keys) {
$content = $content.Replace($key, $placeholders[$key])
}

#match the ending indentation of CBH with the starting one, see #4373
$CBH = $CBHRex.Match($content).Value
if ($CBH) {
#get starting spaces
$startSpaces = $CBHStartRex.Match($CBH).Groups['spaces']
if ($startSpaces) {
#get end
$newCBH = $CBHEndRex.Replace($CBH, "$startSpaces#>")
if ($newCBH) {
$Content = $Content.Replace($CBH, $newCBH)
#replace the CBH
$content = $content.Replace($CBH, $newCBH)
}
}
}

# Apply case corrections and clean up lines
$correctCase = @('DbaInstanceParameter', 'PSCredential', 'PSCustomObject', 'PSItem')
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
$correctCase = @(
'DbaInstanceParameter'
'PSCredential'
'PSCustomObject'
'PSItem'
)
$realContent = @()
foreach ($line in $Content.Split("`n")) {
foreach ($line in $content.Split("`n")) {
foreach ($item in $correctCase) {
$line = $line -replace $item, $item
}
#trim whitespace lines
$realContent += $line.Replace("`t", " ").TrimEnd()
}

return ($realContent -Join $LineEnding)
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($p in $Path) {
try {
$realPath = (Resolve-Path -Path $p -ErrorAction Stop).Path
} catch {
Stop-Function -Message "Cannot find or resolve $p" -Continue
}
$newContent = $realContent -Join "$OSEOL"

# Read file once
$originalBytes = [System.IO.File]::ReadAllBytes($realPath)
$originalContent = [System.IO.File]::ReadAllText($realPath)
# Compare without empty lines to detect real changes
$originalNonEmpty = ($originalContent -split "[\r\n]+" | Where-Object { $_.Trim() }) -join ""
$newNonEmpty = ($newContent -split "[\r\n]+" | Where-Object { $_.Trim() }) -join ""

# Detect line ending style from original file
$detectedOSEOL = $OSEOL
if ($psVersionTable.Platform -ne 'Unix') {
# We're on Windows, check if file uses Unix endings
$containsCR = ($originalContent -split "`r").Length -gt 1
if (-not($containsCR)) {
$detectedOSEOL = "`n"
}
if ($originalNonEmpty -ne $newNonEmpty) {
[System.IO.File]::WriteAllText($realPath, $newContent, $Utf8NoBomEncoding)
Write-Message -Level Verbose "Updated: $realPath"
$updatedFiles++
} else {
Write-Message -Level Verbose "No changes needed: $realPath"
}

# Format the content
$formattedContent = Format-ScriptContent -Content $originalContent -LineEnding $detectedOSEOL
$processedFiles++
}

# If SkipInvisibleOnly is set, check if formatting would only change invisible characters
if ($SkipInvisibleOnly) {
if (Test-OnlyInvisibleChanges -OriginalContent $originalContent -ModifiedContent $formattedContent) {
Write-Verbose "Skipping $realPath - only invisible changes (BOM/line endings/whitespace)"
continue
}
}
# Complete the progress bar
Write-Progress -Activity "Formatting PowerShell files" -Status "Complete" -PercentComplete 100 -CurrentOperation "Processed $processedFiles files, updated $updatedFiles"
Start-Sleep -Milliseconds 500 # Brief pause to show completion
Write-Progress -Activity "Formatting PowerShell files" -Completed

# Save the formatted content
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllText($realPath, $formattedContent, $Utf8NoBomEncoding)
}
# Summary message
Write-Message -Level Verbose "Formatting complete: Processed $processedFiles files, updated $updatedFiles files"
}
}
Loading
Loading