Skip to content

Commit dcdf21a

Browse files
Refactor and enhance Pester tests batch (#9761)
1 parent 5072205 commit dcdf21a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2028
-1051
lines changed

.aitools/module/Repair-PullRequestTest.ps1

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ function Repair-PullRequestTest {
2121
Specific AppVeyor build number to target instead of automatically detecting from PR checks.
2222
When specified, uses this build number directly rather than finding the latest build for the PR.
2323
24+
.PARAMETER CopyOnly
25+
If specified, stops the repair process right after copying working test files
26+
from the development branch to the current branch, without running Update-PesterTest
27+
or committing any changes.
28+
2429
.NOTES
2530
Tags: Testing, Pester, PullRequest, CI
2631
Author: dbatools team
@@ -47,7 +52,8 @@ function Repair-PullRequestTest {
4752
[int]$PRNumber,
4853
[switch]$AutoCommit,
4954
[int]$MaxPRs = 5,
50-
[int]$BuildNumber
55+
[int]$BuildNumber,
56+
[switch]$CopyOnly
5157
)
5258

5359
begin {
@@ -303,9 +309,27 @@ function Repair-PullRequestTest {
303309
$workingTempPath = Join-Path $tempDir "working-$fileName"
304310

305311
if ($workingTestPath -and (Test-Path $workingTestPath)) {
306-
Copy-Item $workingTestPath $workingTempPath -Force
307-
$copiedFiles += $fileName
308-
Write-Verbose "Copied working test: $fileName"
312+
$maxAttempts = 2
313+
$attempt = 0
314+
$copied = $false
315+
while (-not $copied -and $attempt -lt $maxAttempts) {
316+
try {
317+
$attempt++
318+
Copy-Item -Path $workingTestPath -Destination $workingTempPath -Force -ErrorAction Stop
319+
$copiedFiles += $fileName
320+
Write-Verbose "Copied working test: $fileName (attempt $attempt)"
321+
$copied = $true
322+
} catch {
323+
Write-Warning ("Attempt {0}: Failed to copy working test file for {1} from development branch: {2}" -f $attempt, $fileName, $_.Exception.Message)
324+
if ($attempt -lt $maxAttempts) {
325+
Start-Sleep -Seconds 1
326+
}
327+
}
328+
}
329+
if (-not $copied) {
330+
Write-Error "Unable to copy working test file for $fileName after $maxAttempts attempts. Aborting repair process for this file."
331+
break
332+
}
309333
} else {
310334
Write-Warning "Could not find working test file in Development branch: tests/$fileName"
311335
}
@@ -373,6 +397,12 @@ function Repair-PullRequestTest {
373397
}
374398
}
375399

400+
# If CopyOnly is specified, return immediately after copying
401+
if ($CopyOnly) {
402+
Write-Verbose "CopyOnly flag set - stopping after copying working tests to current branch"
403+
return
404+
}
405+
376406
# Now run Update-PesterTest in parallel with Start-Job (simplified approach)
377407
Write-Verbose "Starting parallel Update-PesterTest jobs for $($fileErrorMap.Keys.Count) files"
378408

public/Invoke-DbatoolsFormatter.ps1

Lines changed: 95 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ function Invoke-DbatoolsFormatter {
99
.PARAMETER Path
1010
The path to the ps1 file that needs to be formatted
1111
12-
.PARAMETER SkipInvisibleOnly
13-
Skip files that would only have invisible changes (BOM, line endings, trailing whitespace, tabs).
14-
Use this to avoid unnecessary version control noise when only non-visible characters would change.
15-
1612
.PARAMETER EnableException
1713
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
1814
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
@@ -35,15 +31,14 @@ function Invoke-DbatoolsFormatter {
3531
Reformats C:\dbatools\public\Get-DbaDatabase.ps1 to dbatools' standards
3632
3733
.EXAMPLE
38-
PS C:\> Invoke-DbatoolsFormatter -Path C:\dbatools\public\*.ps1 -SkipInvisibleOnly
34+
PS C:\> Get-ChildItem *.ps1 | Invoke-DbatoolsFormatter
3935
40-
Reformats all ps1 files but skips those that would only have BOM/line ending changes
36+
Reformats all .ps1 files in the current directory, showing progress for the batch operation
4137
#>
4238
[CmdletBinding()]
4339
param (
4440
[parameter(Mandatory, ValueFromPipeline)]
4541
[object[]]$Path,
46-
[switch]$SkipInvisibleOnly,
4742
[switch]$EnableException
4843
)
4944
begin {
@@ -72,147 +67,137 @@ function Invoke-DbatoolsFormatter {
7267
$OSEOL = "`r`n"
7368
}
7469

75-
function Test-OnlyInvisibleChanges {
76-
param(
77-
[string]$OriginalContent,
78-
[string]$ModifiedContent
79-
)
80-
81-
# Normalize line endings to Unix style for comparison
82-
$originalNormalized = $OriginalContent -replace '\r\n', "`n" -replace '\r', "`n"
83-
$modifiedNormalized = $ModifiedContent -replace '\r\n', "`n" -replace '\r', "`n"
70+
# Collect all paths for progress tracking
71+
$allPaths = @()
72+
}
73+
process {
74+
if (Test-FunctionInterrupt) { return }
75+
# Collect all paths from pipeline
76+
$allPaths += $Path
77+
}
78+
end {
79+
if (Test-FunctionInterrupt) { return }
8480

85-
# Split into lines
86-
$originalLines = $originalNormalized -split "`n"
87-
$modifiedLines = $modifiedNormalized -split "`n"
81+
$totalFiles = $allPaths.Count
82+
$currentFile = 0
83+
$processedFiles = 0
84+
$updatedFiles = 0
8885

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

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

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

106-
for ($i = 0; $i -lt $originalLines.Count; $i++) {
107-
if ($originalLines[$i] -ne $modifiedLines[$i]) {
108-
return $false
104+
$fileName = Split-Path -Leaf $realPath
105+
Write-Progress -Activity "Formatting PowerShell files" -Status "Processing: $fileName" -PercentComplete (($currentFile / $totalFiles) * 100) -CurrentOperation "File $currentFile of $totalFiles"
106+
107+
$originalContent = Get-Content -Path $realPath -Raw -Encoding UTF8
108+
$content = $originalContent
109+
110+
if ($OSEOL -eq "`r`n") {
111+
# See #5830, we are in Windows territory here
112+
# Is the file containing at least one `r ?
113+
$containsCR = ($content -split "`r").Length -gt 1
114+
if (-not($containsCR)) {
115+
# If not, maybe even on Windows the user is using Unix-style endings, which are supported
116+
$OSEOL = "`n"
109117
}
110118
}
111119

112-
return $true
113-
}
120+
#strip ending empty lines
121+
$content = $content -replace "(?s)$OSEOL\s*$"
114122

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

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

124134
try {
125-
# Save original lines before formatting
126-
$originalLines = $Content -split "`n"
127-
128-
# Run the formatter
129-
$formattedContent = Invoke-Formatter -ScriptDefinition $Content -Settings CodeFormattingOTBS -ErrorAction Stop
130-
131-
# Automatically restore spaces before = signs
132-
$formattedLines = $formattedContent -split "`n"
133-
for ($i = 0; $i -lt $formattedLines.Count; $i++) {
134-
if ($i -lt $originalLines.Count) {
135-
# Check if original had multiple spaces before =
136-
if ($originalLines[$i] -match '^(\s*)(.+?)(\s{2,})(=)(.*)$') {
137-
$indent = $matches[1]
138-
$beforeEquals = $matches[2]
139-
$spacesBeforeEquals = $matches[3]
140-
$rest = $matches[4] + $matches[5]
141-
142-
# Apply the same spacing to the formatted line
143-
if ($formattedLines[$i] -match '^(\s*)(.+?)(\s*)(=)(.*)$') {
144-
$formattedLines[$i] = $matches[1] + $matches[2] + $spacesBeforeEquals + '=' + $matches[5]
145-
}
146-
}
147-
}
135+
$formattedContent = Invoke-Formatter -ScriptDefinition $content -Settings CodeFormattingOTBS -ErrorAction Stop
136+
if ($formattedContent) {
137+
$content = $formattedContent
148138
}
149-
$Content = $formattedLines -join "`n"
150139
} catch {
151-
Write-Message -Level Warning "Unable to format content"
140+
# Just silently continue - the formatting might still work partially
152141
}
153142

154-
# Match the ending indentation of CBH with the starting one
155-
$CBH = $CBHRex.Match($Content).Value
143+
# Restore the aligned patterns
144+
foreach ($key in $placeholders.Keys) {
145+
$content = $content.Replace($key, $placeholders[$key])
146+
}
147+
148+
#match the ending indentation of CBH with the starting one, see #4373
149+
$CBH = $CBHRex.Match($content).Value
156150
if ($CBH) {
151+
#get starting spaces
157152
$startSpaces = $CBHStartRex.Match($CBH).Groups['spaces']
158153
if ($startSpaces) {
154+
#get end
159155
$newCBH = $CBHEndRex.Replace($CBH, "$startSpaces#>")
160156
if ($newCBH) {
161-
$Content = $Content.Replace($CBH, $newCBH)
157+
#replace the CBH
158+
$content = $content.Replace($CBH, $newCBH)
162159
}
163160
}
164161
}
165-
166-
# Apply case corrections and clean up lines
167-
$correctCase = @('DbaInstanceParameter', 'PSCredential', 'PSCustomObject', 'PSItem')
162+
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
163+
$correctCase = @(
164+
'DbaInstanceParameter'
165+
'PSCredential'
166+
'PSCustomObject'
167+
'PSItem'
168+
)
168169
$realContent = @()
169-
foreach ($line in $Content.Split("`n")) {
170+
foreach ($line in $content.Split("`n")) {
170171
foreach ($item in $correctCase) {
171172
$line = $line -replace $item, $item
172173
}
174+
#trim whitespace lines
173175
$realContent += $line.Replace("`t", " ").TrimEnd()
174176
}
175177

176-
return ($realContent -Join $LineEnding)
177-
}
178-
}
179-
process {
180-
if (Test-FunctionInterrupt) { return }
181-
foreach ($p in $Path) {
182-
try {
183-
$realPath = (Resolve-Path -Path $p -ErrorAction Stop).Path
184-
} catch {
185-
Stop-Function -Message "Cannot find or resolve $p" -Continue
186-
}
178+
$newContent = $realContent -Join "$OSEOL"
187179

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

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

202-
# Format the content
203-
$formattedContent = Format-ScriptContent -Content $originalContent -LineEnding $detectedOSEOL
192+
$processedFiles++
193+
}
204194

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

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

0 commit comments

Comments
 (0)