Skip to content

Commit 313030a

Browse files
bolenscursoragent
andcommitted
fix(test): harden network utils CI flakes and serialize infra shard
Avoid DNS-hijackable HTTP targets in tools network tests, initialize Invoke-WithRetry timing locals under StrictMode, surface Quiet failure lines, and run unit-profile-infra serially to avoid nested-runspace races. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9e17107 commit 313030a

5 files changed

Lines changed: 28 additions & 24 deletions

File tree

profile.d/utilities-modules/network/utilities-network-advanced.ps1

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,20 @@ function Invoke-WithRetry {
8585

8686
$powershell = $null
8787
$runspacePool = $null
88+
# Initialize before try so StrictMode never sees an unset read if BeginInvoke fails mid-setup.
89+
$timeoutMs = [Math]::Max(0, $TimeoutSeconds) * 1000
90+
$pollIntervalMs = 50
91+
$elapsedMs = 0
92+
$completed = $false
8893

8994
try {
9095
# Use runspace for timeout operation (much faster than job)
9196
$runspacePool = [runspacefactory]::CreateRunspacePool(1, 1)
9297
$runspacePool.Open()
93-
98+
9499
$powershell = [PowerShell]::Create()
95100
$powershell.RunspacePool = $runspacePool
96-
101+
97102
# Wrap the scriptblock to accept arguments
98103
$wrapperScript = {
99104
param($ScriptBlock, $ArgumentList)
@@ -104,18 +109,13 @@ function Invoke-WithRetry {
104109
& $ScriptBlock
105110
}
106111
}
107-
112+
108113
$null = $powershell.AddScript($wrapperScript)
109114
$null = $powershell.AddArgument($ScriptBlock)
110115
$null = $powershell.AddArgument($ArgumentList)
111116
$handle = $powershell.BeginInvoke()
112117

113118
# Wait for completion or timeout using polling (STA-compatible)
114-
$timeoutMs = $TimeoutSeconds * 1000
115-
$pollIntervalMs = 50
116-
$elapsedMs = 0
117-
$completed = $false
118-
119119
while ($elapsedMs -lt $timeoutMs) {
120120
if ($handle.IsCompleted) {
121121
$completed = $true
@@ -134,14 +134,17 @@ function Invoke-WithRetry {
134134
throw "Operation timed out after $TimeoutSeconds seconds"
135135
}
136136

137-
# Get the result
137+
# Get the result (EndInvoke returns Collection[PSObject]; unwrap single values)
138138
$result = $powershell.EndInvoke($handle)
139139
$powershell.Dispose()
140140
$runspacePool.Close()
141141
$runspacePool.Dispose()
142142
$powershell = $null
143143
$runspacePool = $null
144144

145+
if ($null -ne $result -and $result -is [System.Collections.IList] -and $result.Count -eq 1) {
146+
return $result[0]
147+
}
145148
return $result
146149

147150
}

scripts/utils/code-quality/run-pester-ci-shard.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function Get-PesterCiShardDefinitions {
133133
'unit-profile-core-files' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = @('tests/unit/profile/files'); MaxParallelThreads = 1 }
134134
'unit-profile-core-boot-main' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = @('tests/unit/profile/bootstrap', 'tests/unit/profile/main'); MaxParallelThreads = 1 }
135135
'unit-profile-core-git-util-sys' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = @('tests/unit/profile/git', 'tests/unit/profile/utilities', 'tests/unit/profile/system'); MaxParallelThreads = 1 }
136-
'unit-profile-infra' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = $unitProfileInfra }
136+
'unit-profile-infra' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = $unitProfileInfra; MaxParallelThreads = 1 }
137137
'unit-profile-misc-a' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = $miscA; MaxParallelThreads = 1 }
138138
'unit-profile-misc-b' = @{ Kind = 'Pester'; Suite = 'Unit'; Paths = $miscB; MaxParallelThreads = 1 }
139139
'integration-tools-a' = @{ Kind = 'ToolsBatch'; NamePattern = '^[0-9a-d]' }

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ foreach ($file in $files) {
197197

198198
$color = if ($stats.Failed -gt 0) { 'Red' } elseif ($stats.Passed -ge 0) { 'Green' } else { 'Yellow' }
199199
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
203+
}
204+
}
200205
}
201206

202207
Write-Host ''

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,13 @@ Describe 'Network Utils Module' {
113113

114114
Context 'Invoke-HttpRequestWithRetry' {
115115
It 'function exists and can be called' {
116-
Get-Command Invoke-HttpRequestWithRetry -CommandType Function -ErrorAction SilentlyContinue | Should -Not -Be $null
117-
# Function uses System.Net.WebRequest directly, so we test that it handles errors gracefully
118-
# Test with invalid URL that will fail quickly
119-
$result = Invoke-HttpRequestWithRetry -Uri "http://invalid.url.invalid" -Method "GET" -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue
120-
# Result should be false (request failed) but function should not throw
121-
{ Invoke-HttpRequestWithRetry -Uri "http://invalid.url.invalid" -Method "GET" -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue } | Should -Not -Throw
116+
Get-Command Invoke-HttpRequestWithRetry -CommandType Function -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
117+
# Closed local port — avoid fake DNS names that CI resolvers may hijack.
118+
{ Invoke-HttpRequestWithRetry -Uri 'http://127.0.0.1:1/' -Method 'GET' -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue } | Should -Not -Throw
122119
}
123120

124121
It 'handles HTTP request failure gracefully' {
125-
# Test with invalid URL that will fail
126-
$result = Invoke-HttpRequestWithRetry -Uri "http://invalid.url.invalid" -Method "GET" -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue
122+
$result = Invoke-HttpRequestWithRetry -Uri 'http://127.0.0.1:1/' -Method 'GET' -TimeoutSeconds 1 -MaxRetries 1 -ErrorAction SilentlyContinue
127123
# Failure may surface as $false or $null depending on retry/timeout behavior
128124
@($false, $null) | Should -Contain $result
129125
}
@@ -148,7 +144,7 @@ Describe 'Network Utils Module' {
148144

149145
$result = Resolve-HostWithRetry -HostName 'example.com'
150146
$result | Should -Not -BeNullOrEmpty
151-
$result.HostName | Should -Match '(?i)^example\.com\.?$'
147+
@($result)[0].HostName | Should -Match '(?i)^example\.com\.?$'
152148
}
153149

154150
It 'handles DNS resolution failure' {
@@ -162,9 +158,9 @@ Describe 'Network Utils Module' {
162158
}
163159

164160
AfterEach {
165-
# Resolve-HostWithRetry stubs Invoke-WithRetry; clear so later files stay isolated.
166-
if (Get-Command Clear-CommandTestStubs -ErrorAction SilentlyContinue) {
167-
Clear-CommandTestStubs
161+
# Resolve-HostWithRetry stubs Invoke-WithRetry; restore the real implementation.
162+
if (Get-Command Restore-TestProfileFunctionStubs -ErrorAction SilentlyContinue) {
163+
Restore-TestProfileFunctionStubs
168164
}
169165
}
170166
}

tests/unit/profile/network/profile-network-utils-fragment-extended.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ Describe 'profile.d/network-utils.ps1 extended scenarios' {
4040
It 'Invoke-WithRetry executes without error for a simple script block' {
4141
. (Join-Path $script:ProfileDir 'network-utils.ps1')
4242

43-
$result = Invoke-WithRetry -ScriptBlock { 'network-utils probe' } -MaxRetries 1
44-
$result | Should -Be 'network-utils probe'
43+
$result = Invoke-WithRetry -ScriptBlock { 'network-utils probe' } -MaxRetries 1 -TimeoutSeconds 5 -RetryDelaySeconds 0
44+
@($result)[0] | Should -Be 'network-utils probe'
4545
}
4646

4747
It 'Skips re-initialization when network utilities are already loaded' {

0 commit comments

Comments
 (0)