Skip to content

Commit 21ede5f

Browse files
bolenscursoragent
andcommitted
fix(test): unblock CI flakes for aliases, conversion shards, and Windows
Set-AgentModeAlias no longer treats PATH Applications as collisions (so short aliases like `now` can register), conversion NamePattern shards use PerFile instead of staging under test-artifacts, and Windows perf/network assertions get more headroom and clearer failure reporting. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 313030a commit 21ede5f

7 files changed

Lines changed: 127 additions & 75 deletions

File tree

profile.d/bootstrap/FunctionRegistration.ps1

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ function global:Set-AgentModeFunction {
9898
.SYNOPSIS
9999
Registers a collision-safe alias in the global scope.
100100
.DESCRIPTION
101-
Creates an alias only when it does not already exist. Optionally returns
102-
the alias definition string for diagnostic scenarios.
101+
Creates an alias only when no alias, function, filter, or cmdlet already
102+
owns the name. External Application commands on PATH do not block
103+
registration (aliases take precedence). Optionally returns the alias
104+
definition string for diagnostic scenarios.
103105
.PARAMETER Name
104106
Alias name to register.
105107
.PARAMETER Target
@@ -131,7 +133,15 @@ function global:Set-AgentModeAlias {
131133
return $false
132134
}
133135

134-
if (Get-Command -Name $Name -ErrorAction SilentlyContinue) {
136+
# Skip when an alias already exists (idempotent / collision-safe).
137+
if (Get-Alias -Name $Name -ErrorAction SilentlyContinue) {
138+
return $false
139+
}
140+
141+
# Prefer profile aliases over PATH apps. Bare Get-Command also matches
142+
# Application and can permanently block short aliases like `now`.
143+
$blocking = @(Get-Command -Name $Name -CommandType Function, Filter, Cmdlet -ErrorAction SilentlyContinue)
144+
if ($blocking.Count -gt 0) {
135145
return $false
136146
}
137147

scripts/utils/code-quality/run-conversion-integration-batch.ps1

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
2727
.PARAMETER NamePattern
2828
Optional case-insensitive regex matched against each test file basename
29-
(e.g. '^[a-m]' for CI shard splits). When set, matching files are staged
30-
into a temp directory so single-session mode still uses one directory -Path.
29+
(e.g. '^[a-m]' for CI shard splits). When set, runs use -PerFile isolation
30+
(staging under test-artifacts is unsafe because Remove-TestArtifacts wipes it).
3131
3232
.EXAMPLE
3333
pwsh -NoProfile -File scripts/utils/code-quality/run-conversion-integration-batch.ps1 -RelativePath data/structured
@@ -205,37 +205,17 @@ function New-BatchRunnerArgs {
205205
return $args
206206
}
207207

208-
function New-NamePatternStagingDirectory {
209-
<#
210-
.SYNOPSIS
211-
Copies NamePattern-matched test files into a staging directory for single-session runs.
212-
213-
.DESCRIPTION
214-
run-pester accepts a directory -Path cleanly, but cannot take repeated -Path file
215-
flags (and comma-joined paths do not bind as [string[]] via arg arrays). Staging
216-
preserves single-session semantics while restricting the file set.
217-
#>
218-
param(
219-
[Parameter(Mandatory)]
220-
[System.IO.FileInfo[]]$Files,
221-
222-
[Parameter(Mandatory)]
223-
[string]$StagingRoot
224-
)
225-
226-
if (Test-Path -LiteralPath $StagingRoot) {
227-
Remove-Item -LiteralPath $StagingRoot -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue
228-
}
229-
$null = New-Item -ItemType Directory -Path $StagingRoot -Force
230-
foreach ($file in $Files) {
231-
Copy-Item -LiteralPath $file.FullName -Destination (Join-Path $StagingRoot $file.Name) -Force
232-
}
233-
return $StagingRoot
234-
}
235-
236208
$label = $RelativePath -replace '[/\\]', '/'
237209
if (-not [string]::IsNullOrWhiteSpace($NamePattern)) {
238210
$label = "$label (NamePattern=$NamePattern)"
211+
# Staging filtered files under tests/test-artifacts breaks conversion tests:
212+
# AfterEach Remove-TestArtifacts wipes the staged scripts mid-run, and
213+
# NamePattern hashes like ^markdown vs ^(?!markdown) collided on sanitized
214+
# artifact names. Per-file isolation avoids both issues.
215+
if (-not $PerFile) {
216+
$PerFile = $true
217+
Write-Host 'NamePattern set: using -PerFile (staging is unsafe for conversion tests)' -ForegroundColor DarkGray
218+
}
239219
}
240220
Write-Host "Batch: $label ($($files.Count) files)" -ForegroundColor Cyan
241221

@@ -245,14 +225,7 @@ if (-not $PerFile) {
245225

246226
$resultDir = Join-Path $RepoRoot 'tests' 'test-artifacts' 'conversion-batch'
247227
$null = New-Item -ItemType Directory -Path $resultDir -Force -ErrorAction SilentlyContinue
248-
$resultKey = if ([string]::IsNullOrWhiteSpace($NamePattern)) {
249-
($RelativePath -replace '[/\\]', '-')
250-
}
251-
else {
252-
# Stable artifact name for filtered shards
253-
$safePattern = ($NamePattern -replace '[^A-Za-z0-9]+', '-').Trim('-')
254-
'{0}-{1}' -f ($RelativePath -replace '[/\\]', '-'), $safePattern
255-
}
228+
$resultKey = ($RelativePath -replace '[/\\]', '-')
256229
# run-pester -TestResultPath expects a directory (not a .xml file path).
257230
$resultPath = Join-Path $resultDir $resultKey
258231
if (Test-Path -LiteralPath $resultPath) {
@@ -261,19 +234,8 @@ if (-not $PerFile) {
261234
$null = New-Item -ItemType Directory -Path $resultPath -Force -ErrorAction SilentlyContinue
262235
$resultXml = Join-Path $resultPath 'test-results.xml'
263236

264-
# When NamePattern filters the set, stage matching files into a temp directory
265-
# so run-pester still receives a single directory -Path (multi-file -Path
266-
# bindings are unsupported through the child pwsh arg array).
267-
$sessionTarget = if (-not [string]::IsNullOrWhiteSpace($NamePattern)) {
268-
$stagingRoot = Join-Path $resultDir ('staged-{0}' -f $resultKey)
269-
New-NamePatternStagingDirectory -Files $files -StagingRoot $stagingRoot
270-
}
271-
else {
272-
$testDir
273-
}
274-
275237
$sw = [System.Diagnostics.Stopwatch]::StartNew()
276-
$run = Invoke-ConversionBatchRunner -RunnerArgs (New-BatchRunnerArgs -TargetPath $sessionTarget -ResultPath $resultPath)
238+
$run = Invoke-ConversionBatchRunner -RunnerArgs (New-BatchRunnerArgs -TargetPath $testDir -ResultPath $resultPath)
277239
$sw.Stop()
278240

279241
$stats = Get-PesterRunStats -Output $run.Output -ResultXmlPath $resultXml

scripts/utils/code-quality/run-tools-integration-batch.ps1

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,50 @@ function New-BatchRunnerArgs {
149149
return $args
150150
}
151151

152+
function Get-PesterFailureLines {
153+
param(
154+
[string]$Output,
155+
[string]$ResultXmlPath
156+
)
157+
158+
$lines = [System.Collections.Generic.List[string]]::new()
159+
[regex]::Matches($Output, '(?m)^\s+\[-\].*') | ForEach-Object { $lines.Add($_.Value.Trim()) }
160+
161+
$xmlCandidates = @()
162+
if ($ResultXmlPath -and -not [string]::IsNullOrWhiteSpace($ResultXmlPath)) {
163+
if (Test-Path -LiteralPath $ResultXmlPath -PathType Leaf) {
164+
$xmlCandidates += $ResultXmlPath
165+
}
166+
elseif (Test-Path -LiteralPath $ResultXmlPath -PathType Container) {
167+
$xmlCandidates += @(Get-ChildItem -LiteralPath $ResultXmlPath -Filter '*.xml' -Recurse -File -ErrorAction SilentlyContinue |
168+
Select-Object -ExpandProperty FullName)
169+
}
170+
}
171+
172+
foreach ($xmlPath in $xmlCandidates) {
173+
try {
174+
[xml]$xml = Get-Content -LiteralPath $xmlPath -Raw -ErrorAction Stop
175+
foreach ($case in @($xml.SelectNodes('//test-case[@result="Failure" or @result="Error"]'))) {
176+
$name = [string]$case.GetAttribute('name')
177+
$messageNode = $case.SelectSingleNode('.//failure/message')
178+
$message = if ($messageNode) { [string]$messageNode.InnerText } else { '' }
179+
if ([string]::IsNullOrWhiteSpace($name) -and [string]::IsNullOrWhiteSpace($message)) {
180+
continue
181+
}
182+
$summary = if ($message) { "${name}: $message" } else { $name }
183+
if (-not [string]::IsNullOrWhiteSpace($summary) -and -not $lines.Contains($summary)) {
184+
$lines.Add($summary.Trim())
185+
}
186+
}
187+
}
188+
catch {
189+
# Ignore unreadable result XML; output-based lines may still be available.
190+
}
191+
}
192+
193+
return @($lines)
194+
}
195+
152196
$label = if ([string]::IsNullOrWhiteSpace($RelativePath)) { 'tools' } else { "tools/$RelativePath" }
153197
Write-Host "Batch: $label ($($files.Count) files)" -ForegroundColor Cyan
154198

@@ -181,12 +225,25 @@ if ($SingleSession) {
181225
Write-Host 'Mode: per-file (default for tools isolation)' -ForegroundColor DarkGray
182226
Write-Host ''
183227

228+
$resultDir = Join-Path $RepoRoot 'tests' 'test-artifacts' 'tools-batch'
229+
$null = New-Item -ItemType Directory -Path $resultDir -Force -ErrorAction SilentlyContinue
230+
184231
$results = @()
185232
foreach ($file in $files) {
186233
$relName = $file.FullName.Substring($toolsRoot.Length).TrimStart('/', '\')
187234
Write-Host "=== $relName ===" -ForegroundColor Cyan
188-
$run = Invoke-ToolsBatchRunner -RunnerArgs (New-BatchRunnerArgs -TargetPath $file.FullName)
189-
$stats = Get-PesterRunStats -Output $run.Output
235+
$fileResultDir = Join-Path $resultDir (($relName -replace '[\\/:\*\?"<>\|]', '-').Trim('-'))
236+
if (Test-Path -LiteralPath $fileResultDir) {
237+
Remove-Item -LiteralPath $fileResultDir -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue
238+
}
239+
$null = New-Item -ItemType Directory -Path $fileResultDir -Force -ErrorAction SilentlyContinue
240+
241+
$run = Invoke-ToolsBatchRunner -RunnerArgs (New-BatchRunnerArgs -TargetPath $file.FullName -ResultPath $fileResultDir)
242+
$stats = Get-PesterRunStats -Output $run.Output -ResultXmlPath (Join-Path $fileResultDir 'test-results.xml')
243+
if ($stats.Failed -lt 0) {
244+
$stats = Get-PesterRunStats -Output $run.Output -ResultXmlPath $fileResultDir
245+
}
246+
$failLines = @(Get-PesterFailureLines -Output $run.Output -ResultXmlPath $fileResultDir)
190247

191248
$results += [pscustomobject]@{
192249
File = $relName
@@ -195,11 +252,11 @@ foreach ($file in $files) {
195252
Skipped = $stats.Skipped
196253
}
197254

198-
$color = if ($stats.Failed -gt 0) { 'Red' } elseif ($stats.Passed -ge 0) { 'Green' } else { 'Yellow' }
255+
$color = if ($stats.Failed -gt 0 -or ($stats.Failed -lt 0 -and $run.ExitCode -ne 0)) { 'Red' } elseif ($stats.Passed -ge 0) { 'Green' } else { 'Yellow' }
199256
Write-Host " $($stats.Passed)P / $($stats.Failed)F / $($stats.Skipped)S" -ForegroundColor $color
200-
if ($stats.Failed -gt 0) {
201-
[regex]::Matches($run.Output, '(?m)^\s+\[-\].*') | Select-Object -First 5 | ForEach-Object {
202-
Write-Host " $($_.Value.Trim())" -ForegroundColor DarkRed
257+
if ($failLines.Count -gt 0) {
258+
$failLines | Select-Object -First 5 | ForEach-Object {
259+
Write-Host " $_" -ForegroundColor DarkRed
203260
}
204261
}
205262
}

tests/integration/tools/network/utils.tests.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,12 @@ Describe 'Network Utils Module' {
120120

121121
It 'handles HTTP request failure gracefully' {
122122
$result = Invoke-HttpRequestWithRetry -Uri 'http://127.0.0.1:1/' -Method 'GET' -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue
123-
# Failure may surface as $false or $null depending on retry/timeout behavior
124-
@($false, $null) | Should -Contain $result
123+
# Failure may surface as $false, $null, or an empty collection depending on
124+
# EndInvoke wrapping / retry timeout behavior across platforms.
125+
$failed = ($null -eq $result) -or ($result -eq $false) -or (
126+
$result -is [System.Collections.ICollection] -and $result.Count -eq 0
127+
)
128+
$failed | Should -BeTrue -Because "HTTP to a closed local port should not report success (got: $(ConvertTo-Json -InputObject $result -Compress -Depth 3 -ErrorAction SilentlyContinue))"
125129
}
126130
}
127131

tests/performance/profile/database-clients-performance.tests.ps1

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,21 @@ BeforeAll {
1616
}
1717
$script:ProfileDir = Get-TestPath -RelativePath 'profile.d' -StartPath $PSScriptRoot -EnsureExists
1818
$script:DatabaseClientsPath = Join-Path $script:ProfileDir 'database-clients.ps1'
19-
$script:MaxLoadTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_LOAD_MS' -Default 4500
20-
$script:MaxFunctionRegistrationTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_FUNCTION_MS' -Default 1000
21-
$script:MaxAliasResolutionTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_ALIAS_MS' -Default 1000
22-
$script:MaxIdempotencyTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_IDEMPOTENCY_MS' -Default 2000
19+
$loadDefault = 4500
20+
$functionDefault = 1000
21+
$aliasDefault = 1000
22+
$idempotencyDefault = 2000
23+
# Windows CI runners are slower for fragment loads / Get-Command sweeps.
24+
if ($IsWindows -or $env:OS -eq 'Windows_NT') {
25+
$loadDefault = 15000
26+
$functionDefault = 5000
27+
$aliasDefault = 5000
28+
$idempotencyDefault = 10000
29+
}
30+
$script:MaxLoadTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_LOAD_MS' -Default $loadDefault
31+
$script:MaxFunctionRegistrationTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_FUNCTION_MS' -Default $functionDefault
32+
$script:MaxAliasResolutionTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_ALIAS_MS' -Default $aliasDefault
33+
$script:MaxIdempotencyTimeMs = Get-PerformanceThreshold -EnvironmentVariable 'PS_PROFILE_DATABASE_CLIENTS_MAX_IDEMPOTENCY_MS' -Default $idempotencyDefault
2334

2435
. (Join-Path $script:ProfileDir 'bootstrap.ps1')
2536
}

tests/unit/profile/utilities/profile-utilities-datetime.tests.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ Describe 'utilities-datetime.ps1 - Get-DateTime and aliases' {
145145
@{ Name = 'now'; Target = 'Get-DateTime' }
146146
)
147147

148+
# Isolation resets may remove aliases while leaving UtilitiesLoaded set, so
149+
# re-apply datetime registrations before asserting.
150+
foreach ($case in $cases) {
151+
Remove-Item -Path "Alias:\$($case.Name)" -Force -ErrorAction SilentlyContinue
152+
Remove-Item -Path "Function:\global:$($case.Name)" -Force -ErrorAction SilentlyContinue
153+
}
154+
. (Join-Path $script:ProfileDir 'utilities-modules' 'data' 'utilities-datetime.ps1')
155+
148156
foreach ($case in $cases) {
149157
# Prefer Get-Alias: autocomplete proxies can make Get-Command return a Function
150158
# without ResolvedCommandName (StrictMode then throws PropertyNotFoundException).
@@ -153,4 +161,4 @@ Describe 'utilities-datetime.ps1 - Get-DateTime and aliases' {
153161
$alias.ResolvedCommandName | Should -Be $case.Target
154162
}
155163
}
156-
}
164+
}

tests/unit/utility/run/utility-run-conversion-integration-batch.tests.ps1

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ exit 1
133133
$result.Output | Should -Match '0P / 1F / 0S|failed'
134134
}
135135

136-
It 'Filters by NamePattern and passes matching files via a single -Path binding' {
136+
It 'Filters by NamePattern and runs matching files per-file' {
137137
$tempRoot = New-TestTempDirectory -Prefix 'conversion-batch-namepattern'
138138
$conversionDir = Join-Path $tempRoot 'tests' 'integration' 'conversion' 'np-batch'
139139
$runnerDir = Join-Path $tempRoot 'scripts' 'utils' 'code-quality'
@@ -149,17 +149,17 @@ param(
149149
[string[]]$TestFile
150150
)
151151
$path = @($TestFile)[0]
152-
if (-not $path -or -not (Test-Path -LiteralPath $path -PathType Container)) {
153-
Write-Error "expected staged directory -Path, got: $path"
152+
if (-not $path -or -not (Test-Path -LiteralPath $path -PathType Leaf)) {
153+
Write-Error "expected per-file -Path, got: $path"
154154
exit 2
155155
}
156-
$names = @(Get-ChildItem -LiteralPath $path -Filter '*.tests.ps1' -File | Select-Object -ExpandProperty Name)
157-
if ($names -contains 'zeta.tests.ps1') { Write-Error 'NamePattern leaked non-matching file'; exit 2 }
158-
if ($names -notcontains 'alpha.tests.ps1' -or $names -notcontains 'beta.tests.ps1') {
159-
Write-Error "expected alpha+beta, got: $($names -join ',')"
156+
$name = [System.IO.Path]::GetFileName($path)
157+
if ($name -eq 'zeta.tests.ps1') { Write-Error 'NamePattern leaked non-matching file'; exit 2 }
158+
if ($name -notin @('alpha.tests.ps1', 'beta.tests.ps1')) {
159+
Write-Error "unexpected file: $name"
160160
exit 2
161161
}
162-
Write-Host 'Tests Passed: 2, Failed: 0, Skipped: 0'
162+
Write-Host 'Tests Passed: 1, Failed: 0, Skipped: 0'
163163
exit 0
164164
'@
165165
Set-Content -LiteralPath (Join-Path $runnerDir 'run-pester.ps1') -Value $stubRunner -Encoding UTF8
@@ -174,7 +174,7 @@ exit 0
174174
$result.ExitCode | Should -Be 0
175175
$result.Output | Should -Match 'NamePattern=\^\[a-m\]'
176176
$result.Output | Should -Match '\(2 files\)'
177-
$result.Output | Should -Match '2P / 0F / 0S'
177+
$result.Output | Should -Match 'using -PerFile'
178178
$result.Output | Should -Match 'All tests passed in batch'
179179
}
180180

0 commit comments

Comments
 (0)