Skip to content

Commit b2beaa9

Browse files
authored
Merge branch 'main' into u/bill-long/public-folders-readme
2 parents c3e1a07 + 465d79f commit b2beaa9

284 files changed

Lines changed: 63254 additions & 299699 deletions

File tree

Some content is hidden

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

.build/Pester.ps1

Lines changed: 143 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseUsingScopeModifierInNewRunspaces', '', Justification = 'Variable passed via -ArgumentList param()')]
45
[CmdletBinding()]
56
param(
67
[switch]
@@ -18,11 +19,10 @@ begin {
1819
throw "Pester module could not be loaded"
1920
}
2021

21-
$jobsQueued = New-Object 'System.Collections.Generic.Queue[object]'
22-
$childIds = 1
2322
$jobsCompleted = @{}
2423
$jobsProgress = @{}
25-
$jobsRunning = @()
24+
$jobsRunning = New-Object 'System.Collections.Generic.List[PSCustomObject]'
25+
$childIds = 1
2626
# on Azure pipeline we have noticed 2 or 4 cores available. to get the most of out jobs, need at least a min of 2 threads running.
2727
$jobQueueMaxConcurrency = [System.Math]::Max(([System.Math]::Min(([System.Environment]::ProcessorCount - 1), 5)), 2)
2828
Write-Host "Max Job Threads: $jobQueueMaxConcurrency"
@@ -44,69 +44,139 @@ begin {
4444
$scripts = @(Get-ChildItem -Recurse $root |
4545
Where-Object { $_.Name -like "*.Tests.ps1" -and $_.FullName -notmatch "\.github" })
4646

47+
# Categorize test files by priority using comment tag: # PesterPriority: High
48+
$highPriorityQueue = New-Object 'System.Collections.Generic.Queue[object]'
49+
$lowPriorityQueue = New-Object 'System.Collections.Generic.Queue[object]'
50+
51+
foreach ($script in $scripts) {
52+
$firstLines = Get-Content $script.FullName -TotalCount 15
53+
if ($firstLines -match "# PesterPriority: High") {
54+
$highPriorityQueue.Enqueue($script)
55+
} else {
56+
$lowPriorityQueue.Enqueue($script)
57+
}
58+
}
59+
60+
Write-Host "High Priority Tests: $($highPriorityQueue.Count) | Low Priority Tests: $($lowPriorityQueue.Count)"
61+
62+
# Thread allocation: high priority gets up to (threads - 1) slots, always leaving at least 1 for low.
63+
# This ensures heavy tests start immediately while small tests still make progress.
64+
$highThreadMax = [System.Math]::Min($highPriorityQueue.Count, $jobQueueMaxConcurrency - 1)
65+
$lowThreadMax = $jobQueueMaxConcurrency - $highThreadMax
66+
67+
# If no high priority tests exist, all threads go to low priority
68+
if ($highPriorityQueue.Count -eq 0) {
69+
$lowThreadMax = $jobQueueMaxConcurrency
70+
$highThreadMax = 0
71+
}
72+
73+
Write-Host "Thread Allocation: High=$highThreadMax Low=$lowThreadMax"
74+
4775
$parentProgress = @{
4876
Id = 0
4977
Activity = "Running Pester Tests"
50-
Status = [string]::Empty
78+
Status = "Initializing"
5179
PercentComplete = 0
5280
}
5381

54-
$scripts | ForEach-Object {
55-
$jobsQueued.Enqueue(@{
56-
ScriptBlock = {
57-
param(
58-
[string]$FileName
59-
)
60-
return Invoke-Pester -Path $FileName -PassThru
61-
}
62-
ArgumentList = $_.FullName
63-
Name = $_.Name
64-
})
65-
}
66-
67-
$parentProgress.PercentComplete = ($jobsCompleted.Count / $scripts.Count * 100)
68-
$parentProgress.Status = "Number of Jobs Running $($jobsRunning.Count)"
69-
7082
if (-not $NoProgress) {
7183
Write-Progress @parentProgress
7284
}
7385

74-
while ($jobsQueued.Count -gt 0 -or $jobsRunning.Count -gt 0) {
86+
while ($highPriorityQueue.Count -gt 0 -or $lowPriorityQueue.Count -gt 0 -or $jobsRunning.Count -gt 0) {
87+
88+
# Determine if low priority work is fully drained (queue empty AND no low jobs running)
89+
$lowDrained = ($lowPriorityQueue.Count -eq 0 -and
90+
(@($jobsRunning | Where-Object { $_.Priority -eq "Low" }).Count -eq 0))
91+
92+
$runningHigh = @($jobsRunning | Where-Object { $_.Priority -eq "High" }).Count
93+
$runningLow = @($jobsRunning | Where-Object { $_.Priority -eq "Low" }).Count
94+
95+
# Once low priority is drained, all threads become available for high priority
96+
$effectiveHighMax = if ($lowDrained) { $jobQueueMaxConcurrency } else { $highThreadMax }
97+
$effectiveLowMax = if ($lowDrained) { 0 } else { $lowThreadMax }
98+
99+
# Start new jobs up to concurrency limit
100+
while ($jobsRunning.Count -lt $jobQueueMaxConcurrency) {
101+
$nextScript = $null
102+
$nextPriority = $null
103+
104+
# Try high priority first if under high thread limit
105+
if ($runningHigh -lt $effectiveHighMax -and $highPriorityQueue.Count -gt 0) {
106+
$nextScript = $highPriorityQueue.Dequeue()
107+
$nextPriority = "High"
108+
} elseif ($runningLow -lt $effectiveLowMax -and $lowPriorityQueue.Count -gt 0) {
109+
$nextScript = $lowPriorityQueue.Dequeue()
110+
$nextPriority = "Low"
111+
} elseif ($highPriorityQueue.Count -gt 0 -and $runningHigh -lt $jobQueueMaxConcurrency) {
112+
# Fill any remaining slots with high priority
113+
$nextScript = $highPriorityQueue.Dequeue()
114+
$nextPriority = "High"
115+
} elseif ($lowPriorityQueue.Count -gt 0 -and $runningLow -lt $jobQueueMaxConcurrency) {
116+
# Fill any remaining slots with low priority
117+
$nextScript = $lowPriorityQueue.Dequeue()
118+
$nextPriority = "Low"
119+
}
120+
121+
if ($null -eq $nextScript) { break }
122+
123+
if ($VerbosePreference -eq "Continue") {
124+
$elapsed = [math]::Round($stopWatch.Elapsed.TotalSeconds, 1)
125+
Write-Verbose "[DEBUG T+${elapsed}s] Starting [$nextPriority] $($nextScript.Name) (H:$runningHigh/$effectiveHighMax L:$runningLow/$effectiveLowMax)"
126+
}
127+
128+
$newJob = Start-Job -ScriptBlock {
129+
param([string]$FileName)
130+
return Invoke-Pester -Path $FileName -PassThru
131+
} -ArgumentList $nextScript.FullName -Name $nextScript.Name
132+
133+
$jobsRunning.Add([PSCustomObject]@{
134+
Job = $newJob
135+
Priority = $nextPriority
136+
Name = $nextScript.Name
137+
})
75138

76-
if ($jobsRunning.Count -lt $jobQueueMaxConcurrency -and $jobsQueued.Count -gt 0) {
77-
$jobArgs = $jobsQueued.Dequeue()
78-
# Using Start-Job instead of Start-ThreadJob as this is faster for this script block
79-
# If Start-ThreadJob is used, need to have $justFinished also use NotStarted State Filter
80-
$newJob = Start-Job @jobArgs
81-
$jobsRunning += $newJob
82139
$progress = @{
83140
Id = $childIds++
84141
ParentId = 0
85-
Activity = "Running: $($newJob.Name)"
142+
Activity = "Running [$nextPriority]: $($nextScript.Name)"
86143
}
87144
$jobsProgress.Add($newJob.Name, $progress)
88145

89146
if (-not $NoProgress) {
90147
Write-Progress @progress
91148
}
149+
150+
$runningHigh = @($jobsRunning | Where-Object { $_.Priority -eq "High" }).Count
151+
$runningLow = @($jobsRunning | Where-Object { $_.Priority -eq "Low" }).Count
92152
}
93153

94-
$justFinished = @($jobsRunning | Where-Object { $_.State -ne "Running" })
154+
# Check for completed jobs
155+
$justFinished = @($jobsRunning | Where-Object { $_.Job.State -ne "Running" })
95156

96157
if ($justFinished.Count -gt 0) {
97-
foreach ($job in $justFinished) {
98-
$result = Receive-Job $job
99-
$jobsCompleted.Add($job.Name, [PSCustomObject]@{
100-
Job = $job
101-
Result = $result
158+
foreach ($item in $justFinished) {
159+
$result = Receive-Job $item.Job
160+
$jobsCompleted.Add($item.Name, [PSCustomObject]@{
161+
Job = $item.Job
162+
Priority = $item.Priority
163+
Result = $result
102164
})
103-
$progress = $jobsProgress[$job.Name]
165+
$progress = $jobsProgress[$item.Name]
104166

105167
if (-not $NoProgress) {
106168
Write-Progress @progress -Completed
107169
}
108-
Write-Host $job.Name "job finished."
109-
Remove-Job $job -Force
170+
171+
$jobDuration = [math]::Round(($item.Job.PSEndTime - $item.Job.PSBeginTime).TotalSeconds, 1)
172+
Write-Host "[$($item.Priority)] $($item.Name) job finished. (${jobDuration}s)"
173+
174+
if ($VerbosePreference -eq "Continue") {
175+
$elapsed = [math]::Round($stopWatch.Elapsed.TotalSeconds, 1)
176+
Write-Verbose "[DEBUG T+${elapsed}s] Completed [$($item.Priority)] $($item.Name) - Pester: $([math]::Round($result.Duration.TotalSeconds, 1))s Job: ${jobDuration}s"
177+
}
178+
179+
Remove-Job $item.Job -Force
110180
$result
111181

112182
if ($result.Result -eq "Failed" -or
@@ -115,18 +185,28 @@ begin {
115185
}
116186
}
117187

118-
$jobsRunning = @($jobsRunning | Where-Object { -not $justFinished.Contains($_) })
188+
foreach ($item in $justFinished) {
189+
$jobsRunning.Remove($item) | Out-Null
190+
}
119191
}
120192

193+
$highRunning = @($jobsRunning | Where-Object { $_.Priority -eq "High" }).Count
194+
$lowRunning = @($jobsRunning | Where-Object { $_.Priority -eq "Low" }).Count
195+
$highCompleted = @($jobsCompleted.Values | Where-Object { $_.Priority -eq "High" }).Count
196+
$lowCompleted = @($jobsCompleted.Values | Where-Object { $_.Priority -eq "Low" }).Count
197+
$highTotal = $highCompleted + $highRunning + $highPriorityQueue.Count
198+
$lowTotal = $lowCompleted + $lowRunning + $lowPriorityQueue.Count
199+
121200
$parentProgress.PercentComplete = ($jobsCompleted.Count / $scripts.Count * 100)
122-
$parentProgress.Status = "Number of Jobs Running $($jobsRunning.Count)"
201+
$parentProgress.Status = "Running: $($jobsRunning.Count) | High: $highRunning running, $highCompleted/$highTotal done | Low: $lowRunning running, $lowCompleted/$lowTotal done"
123202

124203
if (-not $NoProgress) {
125204
Write-Progress @parentProgress
126205
}
127206

128-
if ($jobsRunning.Count -eq $jobQueueMaxConcurrency -or $jobsQueued.Count -eq 0) {
129-
Start-Sleep 1
207+
if ($jobsRunning.Count -ge $jobQueueMaxConcurrency -or
208+
($highPriorityQueue.Count -eq 0 -and $lowPriorityQueue.Count -eq 0)) {
209+
Start-Sleep -Milliseconds 500
130210
}
131211
}
132212
} end {
@@ -144,7 +224,7 @@ begin {
144224
$value = $jobsCompleted[$job]
145225
$totalSeconds = ($value.Job.PSEndTime - $value.Job.PSBeginTime).TotalSeconds
146226
$sumTotalPesterSeconds += $value.Result.Duration.TotalSeconds
147-
Write-Host "$job took $totalSeconds seconds to complete"
227+
Write-Host "[$($value.Priority)] $job took $totalSeconds seconds to complete"
148228
$sumTotalSeconds += $totalSeconds
149229

150230
if ($value.Result.Result -eq "Failed") {
@@ -157,9 +237,29 @@ begin {
157237
Write-Host
158238
Write-Host "Total seconds for jobs: $sumTotalSeconds"
159239
Write-Host "Total seconds for pester results: $sumTotalPesterSeconds"
160-
Write-Host "Average seconds per threads allowed: $($sumTotalSeconds/ $jobQueueMaxConcurrency)"
240+
Write-Host "Average seconds per threads allowed: $($sumTotalSeconds / $jobQueueMaxConcurrency)"
161241
Write-Host "Total Seconds script took: $($stopWatch.Elapsed.TotalSeconds)"
162242

243+
if ($VerbosePreference -eq "Continue") {
244+
$highJobs = $jobsCompleted.Values | Where-Object { $_.Priority -eq "High" }
245+
$lowJobs = $jobsCompleted.Values | Where-Object { $_.Priority -eq "Low" }
246+
$highSum = ($highJobs | ForEach-Object { ($_.Job.PSEndTime - $_.Job.PSBeginTime).TotalSeconds } | Measure-Object -Sum).Sum
247+
$lowSum = ($lowJobs | ForEach-Object { ($_.Job.PSEndTime - $_.Job.PSBeginTime).TotalSeconds } | Measure-Object -Sum).Sum
248+
$longestJob = $jobsCompleted.Values | Sort-Object { ($_.Job.PSEndTime - $_.Job.PSBeginTime).TotalSeconds } | Select-Object -Last 1
249+
$longestDuration = [math]::Round(($longestJob.Job.PSEndTime - $longestJob.Job.PSBeginTime).TotalSeconds, 1)
250+
$efficiency = [math]::Round(($sumTotalSeconds / ($stopWatch.Elapsed.TotalSeconds * $jobQueueMaxConcurrency)) * 100, 1)
251+
252+
Write-Verbose ""
253+
Write-Verbose "=== Scheduling Debug ==="
254+
Write-Verbose "High priority: $($highJobs.Count) jobs, $([math]::Round($highSum, 1))s total"
255+
Write-Verbose "Low priority: $($lowJobs.Count) jobs, $([math]::Round($lowSum, 1))s total"
256+
Write-Verbose "Longest job: [$($longestJob.Priority)] $($longestJob.Job.Name) (${longestDuration}s)"
257+
Write-Verbose "Thread utilization: $efficiency% (ideal=100%)"
258+
Write-Verbose "Theoretical minimum: $([math]::Round($sumTotalSeconds / $jobQueueMaxConcurrency, 1))s"
259+
Write-Verbose "Actual wall clock: $([math]::Round($stopWatch.Elapsed.TotalSeconds, 1))s"
260+
Write-Verbose "Overhead: $([math]::Round($stopWatch.Elapsed.TotalSeconds - ($sumTotalSeconds / $jobQueueMaxConcurrency), 1))s"
261+
}
262+
163263
if ($failPipeline) {
164264
throw "Failed Pester Testing Results"
165265
}

Diagnostics/HealthChecker/Analyzer/Tests/Get-URLRewriteRule.Tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ BeforeAll {
1111
Invoke-Expression $scriptContent
1212
function Invoke-CatchActions { throw "Called Invoke-CatchActions" }
1313

14-
$Script:mockDataRoot = "$PSScriptRoot\..\..\Tests\DataCollection\E19\Exchange\IIS"
14+
$Script:mockDataRoot = "$PSScriptRoot\..\..\Tests\DataCollection\ExchangeSE\Exchange\IIS"
1515
[xml]$Script:appHost = Get-Content "$Script:mockDataRoot\applicationHost.config" -Raw -Encoding UTF8
1616

1717
$Script:webConfigContent = @{
Binary file not shown.

Diagnostics/HealthChecker/Tests/DataCollection/E16/Exchange/ExSetup.xml

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)